diff --git a/.travis.yml b/.travis.yml index 9dc60eaec53a..79af0ebf014f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ addons: packages: - unzip install: +- composer self-update --1 - composer install env: global: diff --git a/Modules/Blog/templates/default/tpl.blog_list.html b/Modules/Blog/templates/default/tpl.blog_list.html index e375c6a5c54b..bf1b0d14d010 100644 --- a/Modules/Blog/templates/default/tpl.blog_list.html +++ b/Modules/Blog/templates/default/tpl.blog_list.html @@ -3,7 +3,7 @@
-

{TXT_CURRENT_MONTH}

+

{TXT_CURRENT_MONTH}

diff --git a/Modules/BookingManager/Objects/classes/class.ilBookingObjectsTableGUI.php b/Modules/BookingManager/Objects/classes/class.ilBookingObjectsTableGUI.php index 91e0d4d07742..a118974dd84d 100644 --- a/Modules/BookingManager/Objects/classes/class.ilBookingObjectsTableGUI.php +++ b/Modules/BookingManager/Objects/classes/class.ilBookingObjectsTableGUI.php @@ -338,8 +338,7 @@ protected function fillRow($a_set) if ($has_booking) { $booking_possible = false; } - if ($a_set["nr_items"] <= $cnt - || empty(ilBookingParticipant::getAssignableParticipants($a_set["booking_object_id"]))) { + if ($a_set["nr_items"] <= $cnt) { $assign_possible = false; } } elseif (!$this->may_edit) { @@ -392,13 +391,17 @@ protected function fillRow($a_set) } if ($this->may_assign && $assign_possible) { - if (is_object($this->filter['period']['from'])) { - $ilCtrl->setParameter($this->parent_obj, 'sseed', $this->filter['period']['from']->get(IL_CAL_DATE)); - } + if (!empty(ilBookingParticipant::getAssignableParticipants($a_set["booking_object_id"]))) { + if (is_object($this->filter['period']['from'])) { + $ilCtrl->setParameterByClass("ilbookingprocessgui", '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->getLinkTargetByClass("ilbookingprocessgui", 'assignParticipants')); - $ilCtrl->setParameter($this->parent_obj, 'sseed', ''); + $ilCtrl->setParameterByClass("ilbookingprocessgui", 'sseed', ''); + } } if ($a_set['info_file']) { diff --git a/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsGUI.php b/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsGUI.php index 952482f97963..c7f17729eea0 100644 --- a/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsGUI.php +++ b/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsGUI.php @@ -88,7 +88,7 @@ public function executeCommand() switch ($next_class) { default: if (in_array($cmd, array("log", "logDetails", "changeStatusObject", "rsvConfirmCancelUser", "rsvCancelUser", - "applyLogFilter", "resetLogFilter", "rsvConfirmCancel", "rsvCancel", "back"))) { + "applyLogFilter", "resetLogFilter", "rsvConfirmCancel", "rsvCancel", "back", "rsvConfirmDelete", "rsvDelete"))) { $this->$cmd(); } } @@ -542,4 +542,68 @@ public function rsvCancel() $this->log(); return ""; } + + public function rsvConfirmDelete() + { + 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") + ); + + $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->pool->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 rsvDelete() + { + global $DIC; + $get = $DIC->http()->request()->getParsedBody()['rsv_ids']; + if ($get) { + foreach (explode(',', $get) as $id) { + $res = new ilBookingReservation($id); + $obj = new ilBookingObject($res->getObjectId()); + if ($obj->getPoolId() != $this->pool->getId() || !$this->checkPermissionBool("write")) { + ilUtil::sendFailure($this->lng->txt('permission_denied'), true); + $this->ctrl->redirect($this, 'log'); + } + if ($this->pool->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'), true); + $this->ctrl->redirect($this, 'log'); + } } diff --git a/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsTableGUI.php b/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsTableGUI.php index 7d1d2f7d568c..81c451c9afe8 100644 --- a/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsTableGUI.php +++ b/Modules/BookingManager/Reservations/classes/class.ilBookingReservationsTableGUI.php @@ -653,6 +653,18 @@ 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(); } } diff --git a/Modules/BookingManager/Reservations/templates/default/tpl.booking_reservation_row.html b/Modules/BookingManager/Reservations/templates/default/tpl.booking_reservation_row.html index 2db3439c794c..fb27b6b226ba 100644 --- a/Modules/BookingManager/Reservations/templates/default/tpl.booking_reservation_row.html +++ b/Modules/BookingManager/Reservations/templates/default/tpl.booking_reservation_row.html @@ -53,6 +53,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/Chatroom/chat/Model/Conversation.js b/Modules/Chatroom/chat/Model/Conversation.js index 623f5d86f407..922a3337a6c4 100644 --- a/Modules/Chatroom/chat/Model/Conversation.js +++ b/Modules/Chatroom/chat/Model/Conversation.js @@ -179,7 +179,25 @@ var Conversation = function Conversation(id, participants) id = val.getId(); } - if (id.toString() === participant.getId().toString()) { + if (typeof id === "undefined") { + return false; + } + + 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() === participantId.toString()) { return true; } diff --git a/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php b/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php index 6d7a181e3852..fce16c558b8d 100644 --- a/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php +++ b/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php @@ -102,7 +102,7 @@ private function showMessages($messages, $durationForm, $export = false, $psessi // should be able to grep templates if ($export) { - $roomTpl = new iGlobalTemplate('tpl.history_export.html', true, true, 'Modules/Chatroom'); + $roomTpl = new ilGlobalTemplate('tpl.history_export.html', true, true, 'Modules/Chatroom'); } else { $roomTpl = new ilTemplate('tpl.history.html', true, true, 'Modules/Chatroom'); } 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/CmiXapi/classes/class.ilCmiXapiAuthToken.php b/Modules/CmiXapi/classes/class.ilCmiXapiAuthToken.php index 59b238866712..ef8ba0b82219 100644 --- a/Modules/CmiXapi/classes/class.ilCmiXapiAuthToken.php +++ b/Modules/CmiXapi/classes/class.ilCmiXapiAuthToken.php @@ -296,16 +296,12 @@ public static function getInstanceByObjIdAndUsrId($objId, $usrId, $checkValid = global $DIC; /* @var \ILIAS\DI\Container $DIC */ $ilDB = $DIC->database(); + $query = "SELECT * FROM cmix_token WHERE obj_id = %s AND usr_id = %s"; + if ($checkValid) { - $checkValid .= "AND valid_until > CURRENT_TIMESTAMP"; + $query .= " AND valid_until > CURRENT_TIMESTAMP"; } - $query = " - SELECT * FROM cmix_token - WHERE obj_id = %s AND usr_id = %s - {$checkValid} - "; - $result = $ilDB->queryF($query, array('integer', 'integer'), array($objId, $usrId)); $row = $ilDB->fetchAssoc($result); diff --git a/Modules/CmiXapi/classes/class.ilCmiXapiContentUploadImporter.php b/Modules/CmiXapi/classes/class.ilCmiXapiContentUploadImporter.php index b4de51281a84..1ea6bb050c97 100644 --- a/Modules/CmiXapi/classes/class.ilCmiXapiContentUploadImporter.php +++ b/Modules/CmiXapi/classes/class.ilCmiXapiContentUploadImporter.php @@ -62,7 +62,7 @@ public function __construct(ilObjCmiXapi $object) /** * @throws \ILIAS\Filesystem\Exception\IOException */ - protected function ensureCreatedObjectDirectory() + public function ensureCreatedObjectDirectory() { global $DIC; /* @var \ILIAS\DI\Container $DIC */ @@ -264,7 +264,7 @@ protected function getAbsoluteObjectDirectory() /** * @return string */ - protected function getWebDataDirRelativeObjectDirectory() + public function getWebDataDirRelativeObjectDirectory() { return self::RELATIVE_CONTENT_DIRECTORY_NAMEBASE . $this->object->getId(); } diff --git a/Modules/CmiXapi/classes/class.ilCmiXapiLaunchGUI.php b/Modules/CmiXapi/classes/class.ilCmiXapiLaunchGUI.php index 1f765f9c07cb..c181607c200a 100644 --- a/Modules/CmiXapi/classes/class.ilCmiXapiLaunchGUI.php +++ b/Modules/CmiXapi/classes/class.ilCmiXapiLaunchGUI.php @@ -63,7 +63,7 @@ protected function buildLaunchLink() } foreach ($this->getLaunchParameters() as $paramName => $paramValue) { - $launchLink = iLUtil::appendUrlParameterString($launchLink, "{$paramName}={$paramValue}"); + $launchLink = ilUtil::appendUrlParameterString($launchLink, "{$paramName}={$paramValue}"); } return $launchLink; @@ -71,6 +71,8 @@ protected function buildLaunchLink() protected function getLaunchParameters() { + global $DIC; /* @var \ILIAS\DI\Container $DIC */ + $params = []; if ($this->object->isBypassProxyEnabled()) { @@ -93,11 +95,13 @@ protected function getLaunchParameters() } } - $params['activitiy_id'] = urlencode($this->object->getActivityId()); - $params['activitiyId'] = urlencode($this->object->getActivityId()); + $params['activity_id'] = urlencode($this->object->getActivityId()); + $params['activityId'] = urlencode($this->object->getActivityId()); $params['actor'] = urlencode($this->buildActorParameter()); - + + $params['registration'] = urlencode(ilCmiXapiUser::getRegistration($this->object, $DIC->user())); + return $params; } @@ -140,10 +144,17 @@ protected function buildAuthTokenFetchParam() protected function buildActorParameter() { global $DIC; /* @var \ILIAS\DI\Container $DIC */ - + + $name = ilCmiXapiUser::getName($this->object->getUserName(), $DIC->user()); +// $name = ($name === '') ? 'NO_NAME' : $name; return json_encode([ 'mbox' => $this->cmixUser->getUsrIdent(), - 'name' => ilCmiXapiUser::getName($this->object->getUserName(), $DIC->user()) + 'name' => $name, + 'objectType' => 'Agent', + 'account' => [ + 'homePage' => 'NO_PAGE', + 'name' => $name + ] ]); } @@ -178,4 +189,27 @@ protected function initCmixUser() ilLPStatusWrapper::_updateStatus($this->object->getId(), $DIC->user()->getId()); } } + + protected function getLaunchData() + { + $launchMethod = "AnyWindow"; // $this->object->getLaunchMethod(), + $moveOn = "Completed"; + return json_encode([ + "contextTemplate" => [ + "contextActivities" => [ + "grouping" => [ + "objectType" => "Activity", + "id" => "http://course-repository.example.edu/identifiers/courses/02baafcf/aus/4c07" + ] + ], + "extensions" => [ + "https://w3id.org/xapi/cmi5/context/extensions/sessionid" => "32e96d95-8e9c-4162-b3ac-66df22d171c5" + ] + ], + "launchMode" => $this->object->getLaunchMode(), +// "returnURL": + "launchMethod" => $launchMethod, + "moveOn" => $moveOn + ]); + } } diff --git a/Modules/CmiXapi/classes/class.ilCmiXapiLrsType.php b/Modules/CmiXapi/classes/class.ilCmiXapiLrsType.php index 2bb0826143f2..bcd84cdb7dd7 100755 --- a/Modules/CmiXapi/classes/class.ilCmiXapiLrsType.php +++ b/Modules/CmiXapi/classes/class.ilCmiXapiLrsType.php @@ -31,6 +31,7 @@ public static function getDbTableName() const USER_IDENT_IL_UUID_USER_ID = 'il_uuid_user_id'; const USER_IDENT_IL_UUID_LOGIN = 'il_uuid_login'; const USER_IDENT_IL_UUID_EXT_ACCOUNT = 'il_uuid_ext_account'; + const USER_IDENT_IL_UUID_RANDOM = 'il_uuid_random'; const USER_IDENT_REAL_EMAIL = 'real_email'; const USER_NAME_NONE = 'none'; @@ -420,4 +421,27 @@ public static function buildBasicAuth($lrsKey, $lrsSecret) { return 'Basic ' . base64_encode("{$lrsKey}:{$lrsSecret}"); } + + public static function getLaunchData($objId) + { + $launchMethod = "AnyWindow"; // $this->object->getLaunchMethod(), + $moveOn = "Completed"; + $launchMode = "Normal"; + return json_encode([ + "contextTemplate" => [ + "contextActivities" => [ + "grouping" => [ + "objectType" => "Activity", + "id" => "http://course-repository.example.edu/identifiers/courses/02baafcf/aus/4c07" + ] + ], + "extensions" => [ + "https://w3id.org/xapi/cmi5/context/extensions/sessionid" => "32e96d95-8e9c-4162-b3ac-66df22d171c5" + ] + ], + "launchMode" => $launchMode, + "launchMethod" => $launchMethod, + "moveOn" => $moveOn + ]); + } } diff --git a/Modules/CmiXapi/classes/class.ilCmiXapiSettingsGUI.php b/Modules/CmiXapi/classes/class.ilCmiXapiSettingsGUI.php index 44c73d1f4bea..8226ce728c41 100755 --- a/Modules/CmiXapi/classes/class.ilCmiXapiSettingsGUI.php +++ b/Modules/CmiXapi/classes/class.ilCmiXapiSettingsGUI.php @@ -268,6 +268,12 @@ protected function buildForm() ); $op->setInfo($DIC->language()->txt('conf_user_ident_il_uuid_ext_account_info')); $userIdent->addOption($op); + $op = new ilRadioOption( + $DIC->language()->txt('conf_user_ident_il_uuid_random'), + ilCmiXapiLrsType::USER_IDENT_IL_UUID_RANDOM + ); + $op->setInfo($DIC->language()->txt('conf_user_ident_il_uuid_random_info')); + $userIdent->addOption($op); $op = new ilRadioOption( $DIC->language()->txt('conf_user_ident_real_email'), ilCmiXapiLrsType::USER_IDENT_REAL_EMAIL diff --git a/Modules/CmiXapi/classes/class.ilCmiXapiUser.php b/Modules/CmiXapi/classes/class.ilCmiXapiUser.php index 9e78ab21feaf..1e98cd70eeed 100755 --- a/Modules/CmiXapi/classes/class.ilCmiXapiUser.php +++ b/Modules/CmiXapi/classes/class.ilCmiXapiUser.php @@ -243,6 +243,10 @@ public static function getIdent($userIdentMode, ilObjUser $user) return self::buildPseudoEmail($user->getExternalAccount(), self::getIliasUuid()); + case ilObjCmiXapi::USER_IDENT_IL_UUID_RANDOM: + + return self::buildPseudoEmail(self::getUserObjectUniqueId(), self::getIliasUuid()); + case ilObjCmiXapi::USER_IDENT_REAL_EMAIL: return $user->getEmail(); @@ -271,6 +275,10 @@ public static function getIdentAsId($userIdentMode, ilObjUser $user) return $user->getExternalAccount(); + case ilObjCmiXapi::USER_IDENT_IL_UUID_RANDOM: + + return self::getUserObjectUniqueId(); + case ilObjCmiXapi::USER_IDENT_REAL_EMAIL: return 'realemail' . $user->getId(); @@ -349,56 +357,6 @@ public static function getUsersForObject($objId) : array return $users; } - public static function getUserIdFromIdent($userIdentMode, $xapiUserIdent) - { - switch ($userIdentMode) { - case ilObjCmiXapi::USER_IDENT_REAL_EMAIL: - - /** - * TODO: lookup user by email - * - handle none found (e.g. when different email was used in external app) - * - handle multiple found and find fallback behaviour - */ - - $iliasUserId = 0; - - break; - - - case ilObjCmiXapi::USER_IDENT_IL_UUID_USER_ID: - - $setting = new ilSetting('cmix'); - $nicId = $setting->get('ilias_uuid'); - - $matches = null; - if (preg_match('/^mailto:(\d+)@' . $nicId . '\.ilias$/', $xapiUserIdent, $matches)) { - $iliasUserId = $matches[1]; - } else { - /** - * TODO: handle not parseable xapi user ident - */ - $iliasUserId = 0; - } - - break; - } - - return $iliasUserId; - } - - public static function getUserFromIdent($object, $xapiUserIdent) - { - if ($object instanceof ilObjCmiXapi) { - $userIdentMode = $object->getUserIdent(); - } else { - //BUG LTI - $userIdentMode = ilObjCmiXapi::USER_IDENT_IL_UUID_USER_ID; - } - - $iliasUserId = self::getUserIdFromIdent($userIdentMode, $xapiUserIdent); - return ilObjectFactory::getInstanceByObjId($iliasUserId, false); - } - public static function exists($objId, $usrId) { global $DIC; /* @var \ILIAS\DI\Container $DIC */ @@ -483,4 +441,67 @@ public static function lookupObjectIds($usrId, $type = '') return $objIds; } + /** + * @param int $length + * @return string + */ + public static function getUserObjectUniqueId( $length = 32 ) + { + $storedId = self::readUserObjectUniqueId(); + if( (bool)strlen($storedId) ) { + return strstr($storedId,'@', true); + } + + $getId = function( $length ) { + $multiplier = floor($length/8) * 2; + $uid = str_shuffle(str_repeat(uniqid(), $multiplier)); + + try { + $ident = bin2hex(random_bytes($length)); + } catch (Exception $e) { + $ident = $uid; + } + + $start = rand(0, strlen($ident) - $length - 1); + return substr($ident, $start, $length); + }; + + $id = $getId($length); + $exists = self::userObjectUniqueIdExists($id); + while( $exists ) { + $id = $getId($length); + $exists = self::userObjectUniqueIdExists($id); + } + + return $id; + + } + + private static function readUserObjectUniqueId() + { + global $DIC; /** @var Container */ + $obj_id = ilObject::_lookupObjId($_GET["ref_id"]); + + $query = "SELECT usr_ident FROM cmix_users". + " WHERE usr_id = " . $DIC->database()->quote($DIC->user()->getId(), 'integer') . + " AND obj_id = " . $DIC->database()->quote($obj_id, 'integer'); + $result = $DIC->database()->query($query); + return is_array($row = $DIC->database()->fetchAssoc($result)) ? $row['usr_ident'] : ''; + } + + private static function userObjectUniqueIdExists($id) + { + global $DIC; /** @var Container */ + + $query = "SELECT usr_ident FROM cmix_users WHERE " . $DIC->database()->like('usr_ident', 'text', $id . '@%'); + $result = $DIC->database()->query($query); + return (bool)$num = $DIC->database()->numRows($result); + } + + public static function getRegistration(ilObjCmiXapi $obj, ilObjUser $user) + { + return (new \Ramsey\Uuid\UuidFactory())->uuid3(self::getIliasUuid(),$obj->getRefId() . '-' . $user->getId()); + } + + } diff --git a/Modules/CmiXapi/classes/class.ilObjCmiXapi.php b/Modules/CmiXapi/classes/class.ilObjCmiXapi.php index d8182d5f5ab5..1fa5d6742db3 100755 --- a/Modules/CmiXapi/classes/class.ilObjCmiXapi.php +++ b/Modules/CmiXapi/classes/class.ilObjCmiXapi.php @@ -106,6 +106,7 @@ protected function dbTableName() const USER_IDENT_IL_UUID_USER_ID = 'il_uuid_user_id'; const USER_IDENT_IL_UUID_LOGIN = 'il_uuid_login'; const USER_IDENT_IL_UUID_EXT_ACCOUNT = 'il_uuid_ext_account'; + const USER_IDENT_IL_UUID_RANDOM = 'il_uuid_random'; /** * @var string @@ -1083,7 +1084,86 @@ public function getDataSetMapping() 'highscore_own_table' => (int) $this->getHighscoreOwnTable(), 'highscore_top_table' => (int) $this->getHighscoreTopTable(), 'highscore_top_num' => (int) $this->getHighscoreTopNum() + //'bypass_proxy' => (int) $this->isBypassProxyEnabled() ]; return $mapping; } + + /** + * Clone object + * + * @access public + * @param int ref_id of target container + * @param int copy id + * @return object new cmix object + */ + protected function doCloneObject($new_obj, $a_target_id, $a_copy_id = null, $a_omit_tree = false) + { + global $DIC; /* @var \ILIAS\DI\Container $DIC */ + + $this->cloneMetaData($new_obj); + + $new_obj->setLrsTypeId($this->getLrsTypeId()); + $new_obj->setContentType($this->getContentType()); + $new_obj->setSourceType($this->getSourceType()); + $new_obj->setActivityId($this->getActivityId()); + $new_obj->setInstructions($this->getInstructions()); + $new_obj->setLaunchUrl($this->getLaunchUrl()); + $new_obj->setAuthFetchUrlEnabled($this->isAuthFetchUrlEnabled()); + $new_obj->setLaunchMethod($this->getLaunchMethod()); + $new_obj->setLaunchMode($this->getLaunchMode()); + $new_obj->setMasteryScore($this->getMasteryScore()); + $new_obj->setKeepLpStatusEnabled($this->isKeepLpStatusEnabled()); + $new_obj->setUserIdent($this->getUserIdent()); + $new_obj->setUserName($this->getUserName()); + $new_obj->setUserPrivacyComment($this->getUserPrivacyComment()); + $new_obj->setStatementsReportEnabled($this->isStatementsReportEnabled()); + $new_obj->setXmlManifest($this->getXmlManifest()); + $new_obj->setVersion($this->getVersion()); + $new_obj->setHighscoreEnabled($this->getHighscoreEnabled()); + $new_obj->setHighscoreAchievedTS($this->getHighscoreAchievedTS()); + $new_obj->setHighscorePercentage($this->getHighscorePercentage()); + $new_obj->setHighscoreWTime($this->getHighscoreWTime()); + $new_obj->setHighscoreOwnTable($this->getHighscoreOwnTable()); + $new_obj->setHighscoreTopTable($this->getHighscoreTopTable()); + $new_obj->setHighscoreTopNum($this->getHighscoreTopNum()); + $new_obj->setBypassProxyEnabled($this->isBypassProxyEnabled()); + $new_obj->update(); + + if ($this->getSourceType() == self::SRC_TYPE_LOCAL) { + $dirUtil = new ilCmiXapiContentUploadImporter($new_obj); + $dirUtil->ensureCreatedObjectDirectory(); + $newDir = implode(DIRECTORY_SEPARATOR, [\ilUtil::getWebspaceDir(), $dirUtil->getWebDataDirRelativeObjectDirectory()]); + $dirUtil = new ilCmiXapiContentUploadImporter($this); + $thisDir = implode(DIRECTORY_SEPARATOR, [\ilUtil::getWebspaceDir(), $dirUtil->getWebDataDirRelativeObjectDirectory()]); + ilUtil::rCopy($thisDir, $newDir); + } + } + + protected function doDelete() + { + global $DIC; + $ilDB = $DIC['ilDB']; + + // delete file data entry + $q = "DELETE FROM cmix_settings WHERE obj_id = " . $ilDB->quote($this->getId(), 'integer'); + $this->ilias->db->query($q); + + // delete history entries + require_once("./Services/History/classes/class.ilHistory.php"); + ilHistory::_removeEntriesForObject($this->getId()); + + + // delete entire directory and its content + $dirUtil = new ilCmiXapiContentUploadImporter($this); + $thisDir = implode(DIRECTORY_SEPARATOR, [\ilUtil::getWebspaceDir(), $dirUtil->getWebDataDirRelativeObjectDirectory()]); + if (is_dir($thisDir)) { + ilUtil::delDir($thisDir); + } + + // delete meta data + $this->deleteMetaData(); + } + + } diff --git a/Modules/CmiXapi/classes/class.ilObjCmiXapiAdministrationGUI.php b/Modules/CmiXapi/classes/class.ilObjCmiXapiAdministrationGUI.php index 703d4c69665d..47d1af59bf79 100755 --- a/Modules/CmiXapi/classes/class.ilObjCmiXapiAdministrationGUI.php +++ b/Modules/CmiXapi/classes/class.ilObjCmiXapiAdministrationGUI.php @@ -250,6 +250,12 @@ protected function buildLrsTypeForm(ilCmiXapiLrsType $lrsType) ); $op->setInfo($DIC->language()->txt('conf_user_ident_il_uuid_ext_account_info')); $item->addOption($op); + $op = new ilRadioOption( + $DIC->language()->txt('conf_user_ident_il_uuid_random'), + ilCmiXapiLrsType::USER_IDENT_IL_UUID_RANDOM + ); + $op->setInfo($DIC->language()->txt('conf_user_ident_il_uuid_random_info')); + $item->addOption($op); $op = new ilRadioOption( $DIC->language()->txt('conf_user_ident_real_email'), ilCmiXapiLrsType::USER_IDENT_REAL_EMAIL @@ -280,7 +286,12 @@ protected function buildLrsTypeForm(ilCmiXapiLrsType $lrsType) $item->setInfo($DIC->language()->txt('conf_user_name_info')); $item->setRequired(false); $form->addItem($item); + + + + + $item = new ilRadioGroupInputGUI($DIC->language()->txt('conf_privacy_setting_conf'), 'force_privacy_setting'); $op = new ilRadioOption($DIC->language()->txt('conf_privacy_setting_default'), 0); $item->addOption($op); diff --git a/Modules/CmiXapi/xapiproxy.php b/Modules/CmiXapi/xapiproxy.php index d38ec63d3fd2..f3a7f7533b35 100755 --- a/Modules/CmiXapi/xapiproxy.php +++ b/Modules/CmiXapi/xapiproxy.php @@ -8,6 +8,28 @@ use \GuzzleHttp\RequestOptions; use \GuzzleHttp\Psr7\Uri; use \Zend\HttpHandlerRunner\Emitter\SapiEmitter; +use \GuzzleHttp\Exception\GuzzleException; + +// ToDo: json_decode(obj,true) as assoc array might be faster? +$specificAllowedStatements = NULL; +/* +$specificAllowedStatements = array( + "http://adlnet.gov/expapi/verbs/completed", + "http://adlnet.gov/expapi/verbs/passed", + "http://adlnet.gov/expapi/verbs/initialized", + "http://adlnet.gov/expapi/verbs/terminated", + "http://adlnet.gov/expapi/verbs/launched" +); +*/ +$replacedValues = NULL; + +/* +$replacedValues = array( + 'timestamp' => '1970-01-01T00:00:00.000Z', + 'result.duration' => 'PT00.000S' +); +*/ +$blockSubStatements = false; // check options requests if (strtoupper($_SERVER["REQUEST_METHOD"]) == "OPTIONS") { @@ -27,12 +49,13 @@ $client = $basicAuth[0]; $token = $basicAuth[1]; } else { - //$log->info("no credentials:\nREQUEST_METHOD=".$_SERVER["REQUEST_METHOD"]."\nPHP_AUTH_USER=".$_SERVER['PHP_AUTH_USER']."\nPHP_AUTH_PW=".$_SERVER['PHP_AUTH_PW']); + //$log->debug("no credentials:\nREQUEST_METHOD=".$_SERVER["REQUEST_METHOD"]."\nPHP_AUTH_USER=".$_SERVER['PHP_AUTH_USER']."\nPHP_AUTH_PW=".$_SERVER['PHP_AUTH_PW']); header('HTTP/1.1 401 Authorization Required'); exit; } require_once 'Modules/CmiXapi/classes/XapiProxy/DataService.php'; + \XapiProxy\DataService::initIlias($client); $dic = $GLOBALS['DIC']; @@ -42,7 +65,8 @@ try { $authToken = ilCmiXapiAuthToken::getInstanceByToken($token); $lrsType = new ilCmiXapiLrsType($authToken->getLrsTypeId()); - + $objId = $authToken->getObjId(); + if (!$lrsType->isAvailable()) { throw new ilCmiXapiException( 'lrs endpoint (id=' . $authToken->getLrsTypeId() . ') unavailable (responded 401-unauthorized)' @@ -58,111 +82,367 @@ $request = $dic->http()->request(); -/* - * async +/** + * set all important params globally at once for multiple usage */ +$method = strtolower($request->getMethod()); +$log->debug("Request-Method: " . $method); +$partsReg = '/^(.*?xapiproxy\.php)(\/([^\?]+)?\??.*)/'; +preg_match($partsReg, $request->getUri(), $cmdParts); +$queryParams = $request->getQueryParams(); -handleRequest($request); - +try { + handleRequest($request); +} +catch(GuzzleException $e) { // ToDo: clean exception handling + $log->error($e->getMessage()); +} /** * handle main request + * @param ServerRequestInterface $request + * @throws GuzzleException */ function handleRequest(ServerRequestInterface $request) { - global $log; - $method = strtolower($request->getMethod()); - $log->debug("Request Method: " . $method); - switch ($method) { - case "post": - case "put": - handlePostPutRequest($request); - break; - default: + global $log, $cmdParts; + + if (count($cmdParts) === 4) { + if ($cmdParts[3] === "statements") { + $log->debug("handleStatementsRequest"); + handleStatementsRequest($request); + } elseif ($cmdParts[3] === "activities/state") { + $log->debug("handleStateRequest"); + handleStateRequest($request); + } elseif ($cmdParts[3] === "agents/profile") { + $log->debug("handleProfileRequest"); + handleProfileRequest($request); + } else { + $log->info("Not handled xApi Query: " . $cmdParts[3]); handleProxy($request); + } + } else { + $log->error("Wrong xApi Query: " . $request->getUri()); + handleProxy($request); + } +} + +/** + * handle statements request + * @param ServerRequestInterface $request + * @throws GuzzleException + */ +function handleStatementsRequest(ServerRequestInterface $request) { + global $method; + if ($method === "post" || $method === "put") { + handlePostPutStatementsRequest($request); + } + else { + // get Method is not handled yet + handleProxy($request); } } /** * handle request for body sniffing, only post put requests + * @param ServerRequestInterface $request + * @throws GuzzleException */ -function handlePostPutRequest(ServerRequestInterface $request) +function handlePostPutStatementsRequest(ServerRequestInterface $request) { global $log; $body = $request->getBody()->getContents(); if (empty($body)) { $log->warning("empty body in handlePostPutRequest"); handleProxy($request); - } else { + } + else { + try { + $log->debug("process statements"); + $retArr = processStatements($request, $body); + if (is_array($retArr)) { + $body = json_encode($retArr[0]); // new body with allowed statements + $fakePostBody = $retArr[1]; // fake post php array of ALL statments as if all statements were processed + } + } + catch(Exception $e) { + $log->error($e->getMessage()); + exitProxyError(); + } try { $body = modifyBody($body); - $log->debug($body); - $changes = array( + // $log->debug($body); + $changes = array ( "body" => $body ); $req = \GuzzleHttp\Psr7\modify_request($request, $changes); - handleProxy($req); - } catch (Exception $e) { // ToDo: Errorhandling + handleProxy($req, $fakePostBody); + } + catch(Exception $e) { $log->error($e->getMessage()); - handleProxy($request); + handleProxy($request, $fakePostBody); + } + } +} + +/** + * process statements + * @param ServerRequestInterface $request + * @return array[]|null + */ +function processStatements(ServerRequestInterface $request, $body) { + global $log, $specificAllowedStatements, $blockSubStatements; + // everything is allowed + if (!is_array($specificAllowedStatements) && !$blockSubStatements) { + $log->debug("all statement are allowed"); + return NULL; + } + $obj = json_decode($body, false); + // single statement object + if (is_object($obj) && isset($obj->verb)) { + $log->debug("json is object and statement"); + $isSubStatement = isSubStatementCheck($obj); + $verb = $obj->verb->id; + if ($blockSubStatements && $isSubStatement) { + $log->debug("sub-statement is NOT allowed, fake response - " . $verb); + fakeResponseBlocked(NULL); + } + // $specificAllowedStatements + if (!is_array($specificAllowedStatements)) { + return NULL; + } + if (in_array($verb,$specificAllowedStatements)) { + $log->debug("statement is allowed, do nothing - " . $verb); + return NULL; + } + else { + $log->debug("statement is NOT allowed, fake response - " . $verb); + fakeResponseBlocked(NULL); + } + } + // array of statement objects + if (is_array($obj) && count($obj) > 0 && isset($obj[0]->verb)) { + $log->debug("json is array of statements"); + $ret = array(); + $up = array(); + for ($i=0; $iid); // push every statementid for fakePostResponse + $isSubStatement = isSubStatementCheck($obj[$i]); + $verb = $obj[$i]->verb->id; + if ($blockSubStatements && $isSubStatement) { + $log->debug("sub-statement is NOT allowed - " .$verb); + } + else { + if (!is_array($specificAllowedStatements) || (is_array($specificAllowedStatements) && in_array($verb,$specificAllowedStatements))) { + $log->debug("statement is allowed - " . $verb); + array_push($up,$obj[$i]); + } + } + } + if (count($up) === 0) { // nothing allowed + $log->debug("no allowed statements in array - fake response..."); + fakeResponseBlocked($ret); + } + elseif (count($up) !== count($ret)) { // mixed request with allowed and not allowed statements + $log->debug("mixed with allowed and unallowed statements"); + return array($up,$ret); + } + else { + // just return nothing + return NULL; } } } +/** + * @param $xapiStatement + * @throws ilDatabaseException + * @throws ilObjectNotFoundException + */ +function handleStatementEvaluation($xapiStatement) +{ + global $authToken, $log; + + /* @var ilObjCmiXapi $object */ + $object = ilObjectFactory::getInstanceByObjId($authToken->getObjId()); + + if( (string)$object->getLaunchMode() === (string)ilObjCmiXapi::LAUNCH_MODE_NORMAL ) { + // ToDo: check function hasContextActivitiesParentNotEqualToObject! + $statementEvaluation = new ilXapiStatementEvaluation($log, $object); + $statementEvaluation->evaluateStatement($xapiStatement, $authToken->getUsrId()); + + ilLPStatusWrapper::_updateStatus( + $authToken->getObjId(), + $authToken->getUsrId() + ); + } +} + +/** + * @param $obj + * @param $path + * @param $value + */ +function setValue(&$obj, $path, $value) { + global $log; + $path_components = explode('.', $path); + if (count($path_components) == 1) { + if (property_exists($obj,$path_components[0])) { + $obj->{$path_components[0]} = $value; + } + } + else { + if (property_exists($obj, $path_components[0])) { + setValue($obj->{array_shift($path_components)}, implode('.', $path_components), $value); + } + } +} + +/** + * @param $obj + * @return bool + */ +function isSubStatementCheck($obj) { + global $log; + if ( + isset($obj->context) && + isset($obj->context->contextActivities) && + is_array($obj->context->contextActivities->parent) + ) { + $log->debug("is Substatement"); + return true; + } + else { + $log->debug("is not Substatement"); + return false; + } +} + +/** + * handle state request + * @param ServerRequestInterface $request + * @throws GuzzleException + */ +function handleStateRequest(ServerRequestInterface $request) { + global $method; + if ($method === "get") { + handleStateGetRequest($request); + } + else { + // post | put Methods are not handled yet + handleProxy($request); + } +} + +/** + * handle state get request + * @param ServerRequestInterface $request + * @throws GuzzleException + */ +function handleStateGetRequest(ServerRequestInterface $request) { + global $log, $objId, $queryParams, $lrsType; + $stateId = strtolower($queryParams["stateId"]); + if ($stateId === "lms.launchdata") { + sendData($lrsType::getLaunchData($objId)); // ToDo: get real LaunchData + } + elseif ($stateId === "status") { + sendData("{\"completion\":null,\"success\":null,\"score\":null,\"launchModes\":[]}"); // ToDo: get real status + } + else { + $log->debug("not handled stateId: " . $stateId); + handleProxy($request); + } +} + +/** + * @param ServerRequestInterface $request + * @throws GuzzleException + */ +function handleProfileRequest(ServerRequestInterface $request) { + global $method; + if ($method === "get") { + handleProfileGetRequest($request); + } + else { + // post | put Methods are not handled yet + handleProxy($request); + } +} + +/** + * @param ServerRequestInterface $request + * @throws GuzzleException + */ +function handleProfileGetRequest(ServerRequestInterface $request) { + global $queryParams; + $profileId = strtolower($queryParams["profileId"]); + if ($profileId === "cmi5learnerpreferences") { + sendData("{\"languagePreference\":\"de-DE\",\"audioPreference\":\"on\"}"); // ToDo: get real preferences + } + else { + // not handled + $log->debug("not handled profileId: " . $profileId); + handleProxy($request); + } +} + /** * handle proxy request + * @param ServerRequestInterface $request + * @param null $fakePostBody */ -function handleProxy(ServerRequestInterface $request) +function handleProxy(ServerRequestInterface $request, $fakePostBody = NULL) { - global $log, $authToken, $lrsType; - + global $log, $lrsType, $cmdParts; + $endpoint = $lrsType->getLrsEndpoint(); $log->debug("Endpoint: " . $endpoint); - //$endpoint = "https://lrs.example.com/lrs.php"; $auth = 'Basic ' . base64_encode($lrsType->getLrsKey() . ':' . $lrsType->getLrsSecret()); $req_opts = array( RequestOptions::VERIFY => false, - RequestOptions::CONNECT_TIMEOUT => 5 + RequestOptions::CONNECT_TIMEOUT => 7 ); - $full_uri = $request->getUri(); - $serverParams = $request->getServerParams(); - $queryParams = $request->getQueryParams(); - $parts_reg = '/^(.*?xapiproxy\.php)(.+)/'; // ToDo: replace hard coded regex? - preg_match($parts_reg, $full_uri, $cmd_parts); - - if (count($cmd_parts) === 3) { // should always - try { - $cmd = $cmd_parts[2]; - $upstream = $endpoint . $cmd; - $uri = new Uri($upstream); - $changes = array( - 'uri' => $uri, - 'set_headers' => array('Cache-Control' => 'no-cache, no-store, must-revalidate', 'Authorization' => $auth) - ); - $req = \GuzzleHttp\Psr7\modify_request($request, $changes); - $httpclient = new Client(); - $promise = $httpclient->sendAsync($req, $req_opts); - $response = $promise->wait(); - handleResponse($request, $response); - } catch (Exception $e) { // ToDo: Errorhandling - header("HTTP/1.1 500 XapiProxy Error"); - echo "HTTP/1.1 500 XapiProxy Error"; - exit; - } - } else { - $log->warning("Wrong command parts!"); - header("HTTP/1.1 412 Wrong Request Parameter"); - echo "HTTP/1.1 412 Wrong Request Parameter"; + + try { + $cmd = $cmdParts[2]; + $upstream = $endpoint . $cmd; + $uri = new Uri($upstream); + $changes = array( + 'uri' => $uri, + 'set_headers' => array('Cache-Control' => 'no-cache, no-store, must-revalidate', 'Authorization' => $auth) + ); + $req = \GuzzleHttp\Psr7\modify_request($request, $changes); + $httpclient = new Client(); + $promise = $httpclient->sendAsync($req, $req_opts); + $response = $promise->wait(); + handleResponse($request, $response, $fakePostBody); + } catch (Exception $e) { // ToDo: Errorhandling! + $log->error($e->getMessage()); + header("HTTP/1.1 500 XapiProxy Error"); + echo "HTTP/1.1 500 XapiProxy Error"; exit; } } -function handleResponse(ServerRequestInterface $request, ResponseInterface $response) +/** + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param null $fakePostBody + */ +function handleResponse(ServerRequestInterface $request, ResponseInterface $response, $fakePostBody = NULL) { global $log; + if ($fakePostBody !== NULL) { + $origBody = $response->getBody(); + $log->debug("orig body: " . $origBody); + $log->debug("fake body: " . json_encode($fakePostBody)); + // because there is a real response object, it should also be possible to override the response stream... + // but this does the job as well: + fakeResponseBlocked($fakePostBody); + } // check transfer encoding bug $headers = $response->getHeaders(); if (array_key_exists('Transfer-Encoding', $headers) && $headers['Transfer-Encoding'][0] == "chunked") { - $log->info("sniff response transfer-encoding for unallowed Content-length"); + $log->debug("sniff response transfer-encoding for unallowed Content-length"); $body = $response->getBody(); $status = $response->getStatusCode(); unset($headers['Transfer-Encoding']); @@ -174,54 +454,115 @@ function handleResponse(ServerRequestInterface $request, ResponseInterface $resp } } +/** + * @param null $post + */ +function fakeResponseBlocked($post=NULL) { + global $log; + $log->debug("fakeResponseFromBlockedRequest"); + if ($post===NULL) { + $log->debug("post === NULL"); + header('HTTP/1.1 204 No Content'); + header('Access-Control-Allow-Origin: '.$_SERVER["HTTP_ORIGIN"]); + header('Access-Control-Allow-Credentials: true'); + header('X-Experience-API-Version: 1.0.3'); + exit; + } + else { + $ids = json_encode($post); + $log->debug("post: " . $ids); + header('HTTP/1.1 200 Ok'); + header('Access-Control-Allow-Origin: '.$_SERVER["HTTP_ORIGIN"]); + header('Access-Control-Allow-Credentials: true'); + header('X-Experience-API-Version: 1.0.3'); + header('Content-Length: ' . strlen($ids)); + header('Content-Type: application/json; charset=utf-8'); + echo $ids; + exit; + } +} + +/** + * @param $body + * @return false|string + * @throws ilDatabaseException + * @throws ilObjectNotFoundException + */ function modifyBody($body) { - global $log; + global $log, $replacedValues; $obj = json_decode($body, false); - + if (json_last_error() != JSON_ERROR_NONE) { // JSON is not valid $log->error(json_last_error_msg()); return $body; } - - $log->debug(json_encode($obj, JSON_PRETTY_PRINT)); // only in DEBUG mode for better performance - + + // $log->debug(json_encode($obj, JSON_PRETTY_PRINT)); // only in DEBUG mode for better performance + if (is_object($obj)) { - $log->debug(""); - + if (is_array($replacedValues)) { + foreach ($replacedValues as $key => $value) { + setValue($obj,$key,$value); + } + } handleStatementEvaluation($obj); } - + if (is_array($obj)) { for ($i = 0; $i < count($obj); $i++) { + if (is_array($replacedValues)) { + foreach ($replacedValues as $key => $value) { + setValue($obj[$i],$key,$value); + } + } handleStatementEvaluation($obj[$i]); } } - + return json_encode($obj); } -function handleStatementEvaluation($xapiStatement) -{ - global $authToken, $log; - - /* @var ilObjCmiXapi $object */ - $object = ilObjectFactory::getInstanceByObjId($authToken->getObjId()); +/** + * @param $obj + */ +function sendData($obj) { + global $log; + $log->debug("senData: " . $obj); + header('HTTP/1.1 200 Ok'); + header('Access-Control-Allow-Origin: '.$_SERVER["HTTP_ORIGIN"]); + header('Access-Control-Allow-Credentials: true'); + header('X-Experience-API-Version: 1.0.3'); + header('Content-Length: ' . strlen($obj)); + header('Content-Type: application/json; charset=utf-8'); + echo $obj; + exit; +} - if( (string)$object->getLaunchMode() === (string)ilObjCmiXapi::LAUNCH_MODE_NORMAL ) { - $statementEvaluation = new ilXapiStatementEvaluation($log, $object); - $statementEvaluation->evaluateStatement($xapiStatement, $authToken->getUsrId()); +/** + * + */ +function exitResponseError() { + header("HTTP/1.1 412 Wrong Response"); + echo "HTTP/1.1 412 Wrong Response"; + exit; +} - ilLPStatusWrapper::_updateStatus( - $authToken->getObjId(), - $authToken->getUsrId() - ); - } +/** + * + */ +function exitProxyError() { + header("HTTP/1.1 500 XapiProxy Error (Ask For Logs)"); + echo "HTTP/1.1 500 XapiProxy Error (Ask For Logs)"; + exit; } // use only for debugging states before ILIAS Init +/** + * @param $txt + */ function _log($txt) { if (DEVMODE) { diff --git a/Modules/ContentPage/classes/class.ilObjContentPage.php b/Modules/ContentPage/classes/class.ilObjContentPage.php index 36cba06b02cb..f1fba534a224 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPage.php +++ b/Modules/ContentPage/classes/class.ilObjContentPage.php @@ -59,12 +59,12 @@ protected function doCloneObject($new_obj, $a_target_id, $a_copy_id = null) if (\ilContentPagePage::_exists($this->getType(), $this->getId())) { $originalPageObject = new \ilContentPagePage($this->getId()); - $originalXML = $originalPageObject->getXMLContent(); + $copiedXML = $originalPageObject->copyXmlContent(); $duplicatePageObject = new \ilContentPagePage(); $duplicatePageObject->setId($new_obj->getId()); $duplicatePageObject->setParentId($new_obj->getId()); - $duplicatePageObject->setXMLContent($originalXML); + $duplicatePageObject->setXMLContent($copiedXML); $duplicatePageObject->createFromXML(); } diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index 4fc6c071b130..267635bdc991 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -72,6 +72,9 @@ class ilObjContentPageGUI extends \ilObject2GUI implements \ilContentPageObjectC */ protected $infoScreenEnabled = false; + /** @var ilHelp */ + protected $help; + /** * @inheritdoc */ @@ -91,6 +94,7 @@ public function __construct($a_id = 0, $a_id_type = self::REPOSITORY_NODE_ID, $a $this->obj_service = $this->dic->object(); $this->navHistory = $this->dic['ilNavigationHistory']; $this->error = $this->dic['ilErr']; + $this->help = $DIC['ilHelp']; $this->lng->loadLanguageModule('copa'); $this->lng->loadLanguageModule('style'); @@ -158,6 +162,8 @@ public function getType() */ public function setTabs() { + $this->help->setScreenIdComponent($this->object->getType()); + if ($this->checkPermissionBool('read')) { $this->tabs->addTab( self::UI_TAB_ID_CONTENT, @@ -238,12 +244,18 @@ public function executeCommand() false ); $style_gui->omitLocator(); - if ($cmd == 'create' || $_GET['new_type'] == 'sty') { + if ($cmd === 'create' || $_GET['new_type'] === 'sty') { $style_gui->setCreationMode(true); } + + if ($cmd === 'confirmedDelete') { + $this->object->setStyleSheetId(0); + $this->object->update(); + } + $ret = $this->ctrl->forwardCommand($style_gui); - if ($cmd == 'save' || $cmd == 'copyStyle' || $cmd == 'importStyle') { + if ($cmd === 'save' || $cmd === 'copyStyle' || $cmd === 'importStyle') { $styleId = $ret; $this->object->setStyleSheetId($styleId); $this->object->update(); diff --git a/Modules/Course/classes/Timings/class.ilTimingCache.php b/Modules/Course/classes/Timings/class.ilTimingCache.php index e0e7861f5203..22b144af4c32 100644 --- a/Modules/Course/classes/Timings/class.ilTimingCache.php +++ b/Modules/Course/classes/Timings/class.ilTimingCache.php @@ -29,11 +29,124 @@ * @version $Id$ * */ -include_once 'Services/Object/classes/class.ilObjectActivation.php'; -include_once 'Modules/Course/classes/Timings/class.ilTimingPlaned.php'; - class ilTimingCache { + /** + * @var null | ilTimingCache + */ + private static $instances = array(); + + /** + * @var int + */ + private $ref_id = 0; + + /** + * @var int + */ + private $obj_id = 0; + + /** + * @var bool + */ + private $timings_active = false; + + /** + * @var array + */ + private $timings = array(); + + /** + * @var array + */ + private $timings_user = array(); + + /** + * @var array + */ + private $collection_items = array(); + + /** + * @var array + */ + private $completed_users = array(); + + /** + * ilTimingCache constructor. + */ + public function __construct($ref_id) + { + $this->ref_id = $ref_id; + $this->obj_id = ilObject::_lookupObjId($this->ref_id); + $this->readObjectInformation(); + } + + /** + * @param $ref_id + * @return ilTimingCache + */ + public static function getInstanceByRefId($ref_id) + { + if (!isset(self::$instances[$ref_id])) { + self::$instances[$ref_id] = new self($ref_id); + } + return self::$instances[$ref_id]; + } + + /** + * @param int $usr_id + * @return bool + */ + public function isWarningRequired($usr_id) + { + if (in_array($usr_id, $this->completed_users)) { + return false; + } + foreach ($this->collection_items as $item) { + $item_instance = self::getInstanceByRefId($item); + if ($item_instance->isWarningRequired($usr_id)) { + return true; + } + } + if (!$this->timings_active) { + return false; + } + + // check constraints + if ($this->timings['changeable'] && isset($this->timings_user[$usr_id]['end'])) { + $end = $this->timings_user[$usr_id]['end']; + } else { + $end = $this->timings['suggestion_end']; + } + return $end < time(); + } + + /** + * Read timing information for object + */ + protected function readObjectInformation() + { + $this->timings = ilObjectActivation::getItem($this->ref_id); + $this->timings_active = false; + if ($this->timings['timing_type'] == ilObjectActivation::TIMINGS_PRESETTING) { + $this->timings_active = true; + $this->timings_user = ilTimingPlaned::_getPlanedTimingsByItem($this->ref_id); + } + + $olp = ilObjectLP::getInstance($this->obj_id); + $collection = $olp->getCollectionInstance(); + if ($collection instanceof ilLPCollectionOfRepositoryObjects) { + $this->collection_items = $collection->getItems(); + } + $this->completed_users = ilLPStatus::_getCompleted($this->obj_id); + } + + + /** + * @deprecated 7 + * @param $a_ref_id + * @return mixed + */ public static function &_getTimings($a_ref_id) { static $cache = array(); @@ -47,6 +160,12 @@ public static function &_getTimings($a_ref_id) return $cache[$a_ref_id]; } + /** + * @deprecated 7 + * @param $a_ref_id + * @param $a_usr_id + * @return bool + */ public static function _showWarning($a_ref_id, $a_usr_id) { global $DIC; diff --git a/Modules/Course/classes/class.ilCourseInfoFileTableGUI.php b/Modules/Course/classes/class.ilCourseInfoFileTableGUI.php index 3f8feec5ba4f..bee05c9c086c 100644 --- a/Modules/Course/classes/class.ilCourseInfoFileTableGUI.php +++ b/Modules/Course/classes/class.ilCourseInfoFileTableGUI.php @@ -65,7 +65,20 @@ public function __construct($a_parent_obj, $a_parent_cmd = '') $this->setDefaultOrderField("filename"); $this->setDefaultOrderDirection("desc"); } - + + /** + * @param string $a_field + * @return bool + */ + public function numericOrdering($a_field) + { + switch ($a_field) { + case 'filesize': + return true; + } + return parent::numericOrdering($a_field); + } + /** * Fill row * 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 fa5a68b1ba0d..7328f0e21723 100644 --- a/Modules/Course/classes/class.ilCourseXMLParser.php +++ b/Modules/Course/classes/class.ilCourseXMLParser.php @@ -365,6 +365,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 @@ -374,6 +378,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'])) { @@ -403,6 +412,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') { @@ -411,6 +424,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 c409f9b11c37..e9fbb8fa8287 100755 --- a/Modules/Course/classes/class.ilCourseXMLWriter.php +++ b/Modules/Course/classes/class.ilCourseXMLWriter.php @@ -194,6 +194,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'); @@ -214,6 +215,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/Course/classes/class.ilObjCourseGUI.php b/Modules/Course/classes/class.ilObjCourseGUI.php index 28595f8c7980..9f2781c6c14a 100755 --- a/Modules/Course/classes/class.ilObjCourseGUI.php +++ b/Modules/Course/classes/class.ilObjCourseGUI.php @@ -571,7 +571,7 @@ public function editInfoObject(ilPropertyFormGUI $a_form = null) } include_once("./Modules/Course/classes/class.ilCourseInfoFileTableGUI.php"); - $table_gui = new ilCourseInfoFileTableGUI($this, "edit"); + $table_gui = new ilCourseInfoFileTableGUI($this, 'editInfo'); $table_gui->setTitle($this->lng->txt("crs_info_download")); $table_gui->setData($rows); $table_gui->addCommandButton("cancel", $this->lng->txt("cancel")); diff --git a/Modules/DataCollection/classes/class.ilDataCollectionGlobalTemplate.php b/Modules/DataCollection/classes/class.ilDataCollectionGlobalTemplate.php index ddf9ca08a08f..3f816addb7f3 100755 --- a/Modules/DataCollection/classes/class.ilDataCollectionGlobalTemplate.php +++ b/Modules/DataCollection/classes/class.ilDataCollectionGlobalTemplate.php @@ -775,7 +775,7 @@ public function loadStandardTemplate() * * Will override the header_page_title. */ - public function setTitle($a_title) + public function setTitle($a_title, $hidden = false) { $this->title = $a_title; $this->header_page_title = $a_title; diff --git a/Modules/Exercise/classes/class.ilExSubmission.php b/Modules/Exercise/classes/class.ilExSubmission.php index 585ff3865e4c..deb09c7fef0d 100644 --- a/Modules/Exercise/classes/class.ilExSubmission.php +++ b/Modules/Exercise/classes/class.ilExSubmission.php @@ -951,7 +951,7 @@ protected function downloadMultipleFiles($a_filenames, $a_user_id, $a_multi_user } chdir($tmpdir); - $zipcmd = $zip . " " . ilUtil::escapeShellArg($tmpzipfile) . " " . join($parsed_files, " "); + $zipcmd = $zip . " " . ilUtil::escapeShellArg($tmpzipfile) . " " . join(" ", $parsed_files); exec($zipcmd); ilUtil::delDir($tmpdir); diff --git a/Modules/Exercise/templates/default/tpl.assignment_head.html b/Modules/Exercise/templates/default/tpl.assignment_head.html index ee21c6bc8683..9884ae09dd24 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}   + \ No newline at end of file diff --git a/Modules/Forum/classes/GUI/class.ilForumThreadFormGUI.php b/Modules/Forum/classes/GUI/class.ilForumThreadFormGUI.php index 3e5f7f624707..e7366b856d10 100644 --- a/Modules/Forum/classes/GUI/class.ilForumThreadFormGUI.php +++ b/Modules/Forum/classes/GUI/class.ilForumThreadFormGUI.php @@ -155,7 +155,7 @@ protected function initForm() $this->addItem($captcha); } - if (\ilForumPostDraft::isSavePostDraftAllowed()) { + if (\ilForumPostDraft::isSavePostDraftAllowed() && !$this->user->isAnonymous()) { $this->ctrl->setParameter($this->delegatingGui, 'draft_id', $this->draftId); if (in_array($this->ctrl->getCmd(), ['publishThreadDraft', 'editThreadDraft', 'updateThreadDraft'])) { $this->addCommandButton('publishThreadDraft', $this->lng->txt('publish')); diff --git a/Modules/Forum/classes/Notification/class.ilMailEventNotificationSender.php b/Modules/Forum/classes/Notification/class.ilMailEventNotificationSender.php index af4de4660171..08e7a9864aee 100644 --- a/Modules/Forum/classes/Notification/class.ilMailEventNotificationSender.php +++ b/Modules/Forum/classes/Notification/class.ilMailEventNotificationSender.php @@ -371,6 +371,8 @@ private function getSecurePostMessage() $pos_message = $this->provider->getPostMessage(); if (strip_tags($pos_message) != $pos_message) { $pos_message = preg_replace("/\n/i", "", $pos_message); + $pos_message = preg_replace("/]*)>/i", "\n", $pos_message); + $pos_message = preg_replace("/<\/ul([^>]*)>(?!\s*?(\n", $pos_message); $pos_message = preg_replace("//i", "\n", $pos_message); $pos_message = preg_replace("/]*)>/i", "\n\n", $pos_message); $pos_message = preg_replace("/<\/p([^>]*)>/i", '', $pos_message); diff --git a/Modules/Forum/classes/class.ilForumCronNotificationDataProvider.php b/Modules/Forum/classes/class.ilForumCronNotificationDataProvider.php index 01a3eda1fed5..65d444362876 100644 --- a/Modules/Forum/classes/class.ilForumCronNotificationDataProvider.php +++ b/Modules/Forum/classes/class.ilForumCronNotificationDataProvider.php @@ -457,8 +457,6 @@ public function getPostUpdateUserName(\ilLanguage $user_lang) )); } - return $this->update_user_name; - // Possible Fix for #25432 if ($this->objPost->getUserAlias() && $this->objPost->getDisplayUserId() == 0 && $this->objPost->getPosAuthorId() == $this->objPost->getUpdateUserId()) { diff --git a/Modules/Forum/classes/class.ilForumExplorerGUI.php b/Modules/Forum/classes/class.ilForumExplorerGUI.php index 7064ada0d1f6..a3511452ee25 100644 --- a/Modules/Forum/classes/class.ilForumExplorerGUI.php +++ b/Modules/Forum/classes/class.ilForumExplorerGUI.php @@ -133,6 +133,14 @@ public function getChildren($record, $environment = null) : array return $this->getChildsOfNode((int) $record['pos_pk']); } + /** + * @return string + */ + public function getTreeLabel() + { + return $this->lng->txt("frm_posts"); + } + /** * @inheritDoc */ @@ -152,7 +160,7 @@ public function getTreeComponent() : Tree ]; $tree = $this->ui->factory()->tree() - ->expandable($this) + ->expandable($this->getTreeLabel(), $this) ->withData($rootNode) ->withHighlightOnNodeClick(false); diff --git a/Modules/Forum/classes/class.ilForumExportGUI.php b/Modules/Forum/classes/class.ilForumExportGUI.php index 735b2214584b..a69530999cc7 100755 --- a/Modules/Forum/classes/class.ilForumExportGUI.php +++ b/Modules/Forum/classes/class.ilForumExportGUI.php @@ -134,7 +134,7 @@ public function ensureThreadBelongsToForum(int $objId, \ilForumTopic $thread) } /** - * @param \ilTemplate $tpl + * @param \ilGlobalTemplate $tpl * @param ilForumPost $post * @param int $counter * @param int $mode @@ -338,11 +338,14 @@ public function exportHTML() ilDatePresentation::setUseRelativeDates(false); - $tpl = new ilTemplate('tpl.forums_export_html.html', true, true, 'Modules/Forum'); + $tpl = new ilGlobalTemplate('tpl.forums_export_html.html', true, true, 'Modules/Forum'); $location_stylesheet = ilUtil::getStyleSheetLocation(); $tpl->setVariable('LOCATION_STYLESHEET', $location_stylesheet); $tpl->setVariable('BASE', (substr(ILIAS_HTTP_PATH, -1) == '/' ? ILIAS_HTTP_PATH : ILIAS_HTTP_PATH . '/')); + iljQueryUtil::initjQuery($tpl); + ilMathJax::getInstance()->includeMathJax($tpl); + $threads = []; $isModerator = $this->is_moderator; $postIds = (array) $_POST['thread_ids']; diff --git a/Modules/Forum/classes/class.ilForumExporter.php b/Modules/Forum/classes/class.ilForumExporter.php index 92a0c573a94e..3229a24774c4 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 'entity' => 'common', 'ids' => $a_ids ]; + + // news settings + $deps[] = [ + "component" => "Services/News", + "entity" => "news_settings", + "ids" => $a_ids + ]; } return $deps; diff --git a/Modules/Forum/classes/class.ilForumMailNotification.php b/Modules/Forum/classes/class.ilForumMailNotification.php index aea3eae16419..fa0d50f5efd4 100644 --- a/Modules/Forum/classes/class.ilForumMailNotification.php +++ b/Modules/Forum/classes/class.ilForumMailNotification.php @@ -282,6 +282,8 @@ private function getSecurePostMessage() $pos_message = $this->provider->getPostMessage(); if (strip_tags($pos_message) != $pos_message) { $pos_message = preg_replace("/\n/i", "", $pos_message); + $pos_message = preg_replace("/]*)>/i", "\n", $pos_message); + $pos_message = preg_replace("/<\/ul([^>]*)>(?!\s*?(\n", $pos_message); $pos_message = preg_replace("//i", "\n", $pos_message); $pos_message = preg_replace("/]*)>/i", "\n\n", $pos_message); $pos_message = preg_replace("/<\/p([^>]*)>/i", '', $pos_message); diff --git a/Modules/Forum/classes/class.ilForumPostDraft.php b/Modules/Forum/classes/class.ilForumPostDraft.php index 52e5a5aed09f..cf0d68b736d8 100644 --- a/Modules/Forum/classes/class.ilForumPostDraft.php +++ b/Modules/Forum/classes/class.ilForumPostDraft.php @@ -60,6 +60,9 @@ class ilForumPostDraft * @var int */ protected $notify = 0; + /** + * @var int + */ protected $post_notify = 0; /** @@ -384,7 +387,55 @@ protected static function readDrafts($user_id) self::$instances[$user_id]['draft_ids'][$tmp_obj->getDraftId()] = $tmp_obj; } } - + + /** + * @param int $usrId + * @param int $threadId + * @param int $sorting + * @return ilForumPostDraft[]|array + */ + public static function getSortedDrafts( + int $usrId, + int $threadId, + int $sorting = ilForumProperties::VIEW_DATE_ASC + ) : array { + global $DIC; + $ilDB = $DIC->database(); + + $drafts = []; + + $orderColumn = ' '; + $orderDirection = ' '; + + if ($sorting !== ilForumProperties::VIEW_TREE) { + $orderColumn = ' ORDER BY post_date '; + $orderDirection = 'ASC'; + if ($sorting === ilForumProperties::VIEW_DATE_DESC) { + $orderDirection = 'DESC'; + } + } + + $res = $ilDB->queryF( + 'SELECT * FROM frm_posts_drafts WHERE post_author_id = %s AND thread_id = %s' . + $orderColumn . $orderDirection, + ['integer', 'integer'], + [$usrId, $threadId] + ); + + while ($row = $ilDB->fetchAssoc($res)) { + $draft = new ilForumPostDraft(); + self::populateWithDatabaseRecord($draft, $row); + $drafts[] = $draft; + self::$instances[$usrId][$threadId][$draft->getPostId()][] = $draft; + } + + if (ilForumProperties::VIEW_TREE === $sorting) { + return self::$instances[$usrId][$threadId] ?? []; + } + + return $drafts; + } + /** * @param int $user_id * @return \ilForumPostDraft[] @@ -495,12 +546,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.ilObjForumGUI.php b/Modules/Forum/classes/class.ilObjForumGUI.php index 033e1fdce64c..5dce48b372cd 100755 --- a/Modules/Forum/classes/class.ilObjForumGUI.php +++ b/Modules/Forum/classes/class.ilObjForumGUI.php @@ -703,7 +703,7 @@ public function getContent() } // Mark all topics as read button - if ($this->user->getId() != ANONYMOUS_USER_ID && !(int) strlen($this->confirmation_gui_html)) { + if (!$this->user->isAnonymous() && !(int) strlen($this->confirmation_gui_html)) { $this->toolbar->addButton( $this->lng->txt('forums_mark_read'), $this->ctrl->getLinkTarget($this, 'markAllRead'), @@ -763,100 +763,127 @@ public function getContent() /** * @param ilTemplate $tpl * @param string $action - * @param bool $render_drafts - * @param $node - * @param null $edit_draft_id - * @return bool + * @param ilForumPost $referencePosting + * @param ilForumPostDraft[] * @throws ilSplitButtonException */ - protected function renderDraftContent(ilTemplate $tpl, string $action, bool $render_drafts, $node, $edit_draft_id = null) - { - if (!$render_drafts) { - return false; - } - + protected function renderDraftContent( + ilTemplate $tpl, + string $action, + ilForumPost $referencePosting, + array $drafts + ) : void { $frm = $this->object->Forum; - $draftsObjects = ilForumPostDraft::getInstancesByUserIdAndThreadId( - $this->user->getId(), - $this->objCurrentTopic->getId() - ); - $drafts = $draftsObjects[$node->getId()]; + foreach ($drafts as $draft) { + $tmp_file_obj = new ilFileDataForumDrafts($this->object->getId(), $draft->getDraftId()); + $filesOfDraft = $tmp_file_obj->getFilesOfPost(); + ksort($filesOfDraft); + + if (count($filesOfDraft)) { + if ($action !== 'showdraft') { + foreach ($filesOfDraft as $file) { + $tpl->setCurrentBlock('attachment_download_row'); + $this->ctrl->setParameter($this, 'draft_id', $tmp_file_obj->getDraftId()); + $this->ctrl->setParameter($this, 'file', $file['md5']); + $tpl->setVariable('HREF_DOWNLOAD', $this->ctrl->getLinkTarget($this, 'viewThread')); + $tpl->setVariable('TXT_FILENAME', $file['name']); + $this->ctrl->setParameter($this, 'file', ''); + $this->ctrl->setParameter($this, 'draft_id', ''); + $this->ctrl->clearParameters($this); + $tpl->parseCurrentBlock(); + } - if (is_array($drafts)) { - foreach ($drafts as $draft) { - if (!$draft instanceof ilForumPostDraft) { - continue 1; + $tpl->setCurrentBlock('attachments'); + $tpl->setVariable('TXT_ATTACHMENTS_DOWNLOAD', $this->lng->txt('forums_attachments')); + $tpl->setVariable( + 'DOWNLOAD_IMG', + ilGlyphGUI::get(ilGlyphGUI::ATTACHMENT, $this->lng->txt('forums_download_attachment')) + ); + if (count($filesOfDraft) > 1) { + $download_zip_button = ilLinkButton::getInstance(); + $download_zip_button->setCaption($this->lng->txt('download'), false); + $this->ctrl->setParameter($this, 'draft_id', $draft->getDraftId()); + $download_zip_button->setUrl($this->ctrl->getLinkTarget($this, 'deliverDraftZipFile')); + $this->ctrl->setParameter($this, 'draft_id', ''); + $tpl->setVariable('DOWNLOAD_ZIP', $download_zip_button->render()); + } + $tpl->parseCurrentBlock(); } + } - if (isset($edit_draft_id) && $edit_draft_id == $node->getId()) { - // do not render a draft that is in 'edit'-mode - return false; - } + $this->renderSplitButton( + $tpl, + $action, + false, + $referencePosting, + (int) $this->httpRequest->getQueryParams()['page'], + $draft + ); - $tmp_file_obj = new ilFileDataForumDrafts($this->object->getId(), $draft->getDraftId()); - $filesOfDraft = $tmp_file_obj->getFilesOfPost(); - ksort($filesOfDraft); - - if (count($filesOfDraft)) { - if ($action !== 'showdraft') { - foreach ($filesOfDraft as $file) { - $tpl->setCurrentBlock('attachment_download_row'); - $this->ctrl->setParameter($this, 'draft_id', $tmp_file_obj->getDraftId()); - $this->ctrl->setParameter($this, 'file', $file['md5']); - $tpl->setVariable('HREF_DOWNLOAD', $this->ctrl->getLinkTarget($this, 'viewThread')); - $tpl->setVariable('TXT_FILENAME', $file['name']); - $this->ctrl->setParameter($this, 'file', ''); - $this->ctrl->setParameter($this, 'draft_id', ''); - $this->ctrl->clearParameters($this); - $tpl->parseCurrentBlock(); - } + $rowCol = 'tblrowmarked'; + $tpl->setVariable('ROWCOL', ' ' . $rowCol); + $tpl->setVariable('DEPTH', (int) ($referencePosting->getDepth() - 1)); - $tpl->setCurrentBlock('attachments'); - $tpl->setVariable('TXT_ATTACHMENTS_DOWNLOAD', $this->lng->txt('forums_attachments')); - $tpl->setVariable( - 'DOWNLOAD_IMG', - ilGlyphGUI::get(ilGlyphGUI::ATTACHMENT, $this->lng->txt('forums_download_attachment')) - ); - if (count($filesOfDraft) > 1) { - $download_zip_button = ilLinkButton::getInstance(); - $download_zip_button->setCaption($this->lng->txt('download'), false); - $this->ctrl->setParameter($this, 'draft_id', $draft->getDraftId()); - $download_zip_button->setUrl($this->ctrl->getLinkTarget($this, 'deliverDraftZipFile')); - $this->ctrl->setParameter($this, 'draft_id', ''); - $tpl->setVariable('DOWNLOAD_ZIP', $download_zip_button->render()); - } - $tpl->parseCurrentBlock(); - } - } + $this->ctrl->setParameter($this, 'pos_pk', $referencePosting->getId()); + $this->ctrl->setParameter($this, 'thr_pk', $referencePosting->getThreadId()); + $this->ctrl->setParameter($this, 'draft_id', $draft->getDraftId()); - $this->renderSplitButton( - $tpl, - $action, - false, - $node, - (int) $this->httpRequest->getQueryParams()['page'], - $draft - ); + $backurl = urlencode($this->ctrl->getLinkTarget($this, 'viewThread', $referencePosting->getId())); - $rowCol = 'tblrowmarked'; - $tpl->setVariable('ROWCOL', ' ' . $rowCol); - $tpl->setVariable('DEPTH', (int) ($node->getDepth() - 1)); + $this->ctrl->setParameter($this, 'backurl', $backurl); + $this->ctrl->setParameter($this, 'thr_pk', $referencePosting->getThreadId()); + $this->ctrl->setParameter($this, 'user', $draft->getPostDisplayUserId()); - // Author - $this->ctrl->setParameter($this, 'pos_pk', $node->getId()); - $this->ctrl->setParameter($this, 'thr_pk', $node->getThreadId()); - $this->ctrl->setParameter($this, 'draft_id', $draft->getDraftId()); + $authorinfo = new ilForumAuthorInformation( + $draft->getPostAuthorId(), + $draft->getPostDisplayUserId(), + $draft->getPostUserAlias(), + '', + array( + 'href' => $this->ctrl->getLinkTarget($this, 'showUser') + ) + ); + + $this->ctrl->clearParameters($this); + + if ($authorinfo->hasSuffix()) { + $tpl->setVariable('AUTHOR', $authorinfo->getSuffix()); + $tpl->setVariable('USR_NAME', $draft->getPostUserAlias()); + } else { + $tpl->setVariable('AUTHOR', $authorinfo->getLinkedAuthorShortName()); + if ($authorinfo->getAuthorName(true) && !$this->objProperties->isAnonymized()) { + $tpl->setVariable('USR_NAME', $authorinfo->getAuthorName(true)); + } + } + $tpl->setVariable('DRAFT_ANCHOR', 'draft_' . $draft->getDraftId()); - $backurl = urlencode($this->ctrl->getLinkTarget($this, 'viewThread', $node->getId())); + $tpl->setVariable('USR_IMAGE', $authorinfo->getProfilePicture()); + $tpl->setVariable('USR_ICON_ALT', ilUtil::prepareFormOutput($authorinfo->getAuthorShortName())); + if ($authorinfo->getAuthor()->getId() && ilForum::_isModerator( + (int) $_GET['ref_id'], + $draft->getPostAuthorId() + )) { + if ($authorinfo->getAuthor()->getGender() == 'f') { + $tpl->setVariable('ROLE', $this->lng->txt('frm_moderator_f')); + } elseif ($authorinfo->getAuthor()->getGender() == 'm') { + $tpl->setVariable('ROLE', $this->lng->txt('frm_moderator_m')); + } elseif ($authorinfo->getAuthor()->getGender() == 'n') { + $tpl->setVariable('ROLE', $this->lng->txt('frm_moderator_n')); + } + } + + if ($draft->getUpdateUserId() > 0) { + $draft->setPostUpdate($draft->getPostUpdate()); $this->ctrl->setParameter($this, 'backurl', $backurl); - $this->ctrl->setParameter($this, 'thr_pk', $node->getThreadId()); - $this->ctrl->setParameter($this, 'user', $draft->getPostDisplayUserId()); + $this->ctrl->setParameter($this, 'thr_pk', $referencePosting->getThreadId()); + $this->ctrl->setParameter($this, 'user', $referencePosting->getUpdateUserId()); + $this->ctrl->setParameter($this, 'draft_id', $draft->getDraftId()); $authorinfo = new ilForumAuthorInformation( $draft->getPostAuthorId(), - $draft->getPostDisplayUserId(), + $draft->getUpdateUserId(), $draft->getPostUserAlias(), '', array( @@ -866,117 +893,63 @@ protected function renderDraftContent(ilTemplate $tpl, string $action, bool $ren $this->ctrl->clearParameters($this); - if ($authorinfo->hasSuffix()) { - $tpl->setVariable('AUTHOR', $authorinfo->getSuffix()); - $tpl->setVariable('USR_NAME', $draft->getPostUserAlias()); - } else { - $tpl->setVariable('AUTHOR', $authorinfo->getLinkedAuthorShortName()); - if ($authorinfo->getAuthorName(true) && !$this->objProperties->isAnonymized()) { - $tpl->setVariable('USR_NAME', $authorinfo->getAuthorName(true)); - } - } - $tpl->setVariable('DRAFT_ANCHOR', 'draft_' . $draft->getDraftId()); - - $tpl->setVariable('USR_IMAGE', $authorinfo->getProfilePicture()); - $tpl->setVariable('USR_ICON_ALT', ilUtil::prepareFormOutput($authorinfo->getAuthorShortName())); - if ($authorinfo->getAuthor()->getId() && ilForum::_isModerator( - (int) $_GET['ref_id'], - $draft->getPostAuthorId() - )) { - if ($authorinfo->getAuthor()->getGender() == 'f') { - $tpl->setVariable('ROLE', $this->lng->txt('frm_moderator_f')); - } elseif ($authorinfo->getAuthor()->getGender() == 'm') { - $tpl->setVariable('ROLE', $this->lng->txt('frm_moderator_m')); - } elseif ($authorinfo->getAuthor()->getGender() == 'n') { - $tpl->setVariable('ROLE', $this->lng->txt('frm_moderator_n')); - } + $tpl->setVariable( + 'POST_UPDATE_TXT', + $this->lng->txt('edited_on') . ': ' . $frm->convertDate($draft->getPostUpdate()) . ' - ' . strtolower($this->lng->txt('by')) + ); + $tpl->setVariable('UPDATE_AUTHOR', $authorinfo->getLinkedAuthorShortName()); + if ($authorinfo->getAuthorName(true) && !$this->objProperties->isAnonymized() && !$authorinfo->hasSuffix()) { + $tpl->setVariable('UPDATE_USR_NAME', $authorinfo->getAuthorName(true)); } + } - // get create- and update-dates - if ($draft->getUpdateUserId() > 0) { - $spanClass = 'small'; - - if (ilForum::_isModerator($this->ref_id, $node->getUpdateUserId())) { - $spanClass = 'moderator_small'; - } - - $draft->setPostUpdate($draft->getPostUpdate()); + $draft->setPostMessage($frm->prepareText($draft->getPostMessage())); - $this->ctrl->setParameter($this, 'backurl', $backurl); - $this->ctrl->setParameter($this, 'thr_pk', $node->getThreadId()); - $this->ctrl->setParameter($this, 'user', $node->getUpdateUserId()); - $this->ctrl->setParameter($this, 'draft_id', $draft->getDraftId()); + $tpl->setVariable('SUBJECT', $draft->getPostSubject()); + $tpl->setVariable('POST_DATE', $frm->convertDate($draft->getPostDate())); - $authorinfo = new ilForumAuthorInformation( - $draft->getPostAuthorId(), - $draft->getUpdateUserId(), - $draft->getPostUserAlias(), - '', - array( - 'href' => $this->ctrl->getLinkTarget($this, 'showUser') - ) - ); + if (!$referencePosting->isCensored() || ($this->objCurrentPost->getId() == $referencePosting->getId() && $action === 'censor')) { + $spanClass = ''; + if (ilForum::_isModerator($this->ref_id, $draft->getPostDisplayUserId())) { + $spanClass = 'moderator'; + } - $this->ctrl->clearParameters($this); + if ($draft->getPostMessage() == strip_tags($draft->getPostMessage())) { + // We can be sure, that there are not html tags + $draft->setPostMessage(nl2br($draft->getPostMessage())); + } + if ($spanClass != "") { $tpl->setVariable( - 'POST_UPDATE_TXT', - $this->lng->txt('edited_on') . ': ' . $frm->convertDate($draft->getPostUpdate()) . ' - ' . strtolower($this->lng->txt('by')) + 'POST', + "" . ilRTE::_replaceMediaObjectImageSrc( + $draft->getPostMessage(), + 1 + ) . "" ); - $tpl->setVariable('UPDATE_AUTHOR', $authorinfo->getLinkedAuthorShortName()); - if ($authorinfo->getAuthorName(true) && !$this->objProperties->isAnonymized() && !$authorinfo->hasSuffix()) { - $tpl->setVariable('UPDATE_USR_NAME', $authorinfo->getAuthorName(true)); - } + } else { + $tpl->setVariable('POST', ilRTE::_replaceMediaObjectImageSrc($draft->getPostMessage(), 1)); } - // Author end - - // prepare post - $draft->setPostMessage($frm->prepareText($draft->getPostMessage())); - - $tpl->setVariable('SUBJECT', $draft->getPostSubject()); - $tpl->setVariable('POST_DATE', $frm->convertDate($draft->getPostDate())); - - if (!$node->isCensored() || ($this->objCurrentPost->getId() == $node->getId() && $action === 'censor')) { - $spanClass = ""; - - if (ilForum::_isModerator($this->ref_id, $draft->getPostDisplayUserId())) { - $spanClass = 'moderator'; - } - - if ($draft->getPostMessage() == strip_tags($draft->getPostMessage())) { - // We can be sure, that there are not html tags - $draft->setPostMessage(nl2br($draft->getPostMessage())); - } + } - if ($spanClass != "") { - $tpl->setVariable( - 'POST', - "" . ilRTE::_replaceMediaObjectImageSrc( - $draft->getPostMessage(), - 1 - ) . "" - ); - } else { - $tpl->setVariable('POST', ilRTE::_replaceMediaObjectImageSrc($draft->getPostMessage(), 1)); - } + if (!$this->objCurrentTopic->isClosed() && $action === 'deletedraft') { + if (!$this->user->isAnonymous() && (int) $draft->getDraftId() === (int) $_GET['draft_id']) { + // confirmation: delete + $tpl->setVariable('FORM', $this->getDeleteDraftFormHTML()); } + } elseif ($action === 'editdraft' && (int) $draft->getDraftId() === (int) $_GET['draft_id']) { + $oEditReplyForm = $this->getReplyEditForm(); - if (!$this->objCurrentTopic->isClosed() && $action === 'deletedraft') { - if ($this->user->getId() != ANONYMOUS_USER_ID && $draft->getDraftId() == (int) $_GET['draft_id']) { - // confirmation: delete - $tpl->setVariable('FORM', $this->getDeleteDraftFormHTML()); - } - } elseif ($action === 'editdraft' && (int) $draft->getDraftId() == (int) $_GET['draft_id']) { - $oEditReplyForm = $this->getReplyEditForm(); - $tpl->setVariable('EDIT_DRAFT_ANCHOR', 'draft_edit_' . $draft->getDraftId()); - $tpl->setVariable('DRAFT_FORM', $oEditReplyForm->getHTML() . $this->modal_history); + if (!$this->objCurrentTopic->isClosed() && in_array($this->requestAction, ['showdraft', 'editdraft'])) { + $this->renderPostingForm($tpl, $frm, $referencePosting, $this->requestAction); } - $tpl->parseCurrentBlock(); + $tpl->setVariable('EDIT_DRAFT_ANCHOR', 'draft_edit_' . $draft->getDraftId()); + $tpl->setVariable('DRAFT_FORM', $oEditReplyForm->getHTML() . $this->modal_history); } - return true; + + $tpl->parseCurrentBlock(); } - return true; } /** @@ -1966,7 +1939,7 @@ private function initReplyEditForm() if ( $this->isWritingWithPseudonymAllowed() && - in_array($this->requestAction, ['showreply', 'ready_showreply']) + in_array($this->requestAction, ['showreply', 'ready_showreply', 'editdraft']) ) { $oAnonymousNameGUI = new ilTextInputGUI($this->lng->txt('forums_your_name'), 'alias'); $oAnonymousNameGUI->setMaxLength(64); @@ -2100,27 +2073,26 @@ private function initReplyEditForm() )); $this->replyEditForm->addItem($draftInfoGUI); } - - $selected_draft_id = (int) $_GET['draft_id']; - $draftObj = new ilForumPostDraft( - $this->user->getId(), - $this->objCurrentPost->getId(), - $selected_draft_id - ); - if ($draftObj->getDraftId() > 0) { - $oFDForumDrafts = new ilFileDataForumDrafts(0, $draftObj->getDraftId()); - if (count($oFDForumDrafts->getFilesOfPost())) { - $oExistingAttachmentsGUI = new ilCheckboxGroupInputGUI( - $this->lng->txt('forums_delete_file'), - 'del_file' - ); - foreach ($oFDForumDrafts->getFilesOfPost() as $file) { - $oAttachmentGUI = new ilCheckboxInputGUI($file['name'], 'del_file'); - $oAttachmentGUI->setValue($file['md5']); - $oExistingAttachmentsGUI->addOption($oAttachmentGUI); - } - $this->replyEditForm->addItem($oExistingAttachmentsGUI); + } + $selected_draft_id = (int) $_GET['draft_id']; + $draftObj = new ilForumPostDraft( + $this->user->getId(), + $this->objCurrentPost->getId(), + $selected_draft_id + ); + if ($draftObj->getDraftId() > 0) { + $oFDForumDrafts = new ilFileDataForumDrafts(0, $draftObj->getDraftId()); + if (count($oFDForumDrafts->getFilesOfPost())) { + $oExistingAttachmentsGUI = new ilCheckboxGroupInputGUI( + $this->lng->txt('forums_delete_file'), + 'del_file' + ); + foreach ($oFDForumDrafts->getFilesOfPost() as $file) { + $oAttachmentGUI = new ilCheckboxInputGUI($file['name'], 'del_file'); + $oAttachmentGUI->setValue($file['md5']); + $oExistingAttachmentsGUI->addOption($oAttachmentGUI); } + $this->replyEditForm->addItem($oExistingAttachmentsGUI); } } @@ -2549,7 +2521,7 @@ public function savePostObject() } else { if ((!$this->is_moderator && !$this->objCurrentPost->isOwner($this->user->getId())) || $this->objCurrentPost->isCensored() || - $this->user->getId() == ANONYMOUS_USER_ID) { + $this->user->isAnonymous()) { $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); } @@ -2620,7 +2592,7 @@ public function savePostObject() ilRTE::_replaceMediaObjectImageSrc($frm->prepareText( $this->objCurrentPost->getMessage(), 0 - ), 1) + ), 1) ); if ($this->objCurrentPost->getMessage() != strip_tags($this->objCurrentPost->getMessage())) { @@ -2633,6 +2605,11 @@ public function savePostObject() $oFDForum = $oForumObjects['file_obj']; + $file2delete = $oReplyEditForm->getInput('del_file'); + if (is_array($file2delete) && count($file2delete)) { + $oFDForum->unlinkFilesByMD5Filenames($file2delete); + } + if ($this->objProperties->isFileUploadAllowed()) { $file = $_FILES['userfile']; if (is_array($file) && !empty($file)) { @@ -2640,11 +2617,6 @@ public function savePostObject() } } - $file2delete = $oReplyEditForm->getInput('del_file'); - if (is_array($file2delete) && count($file2delete)) { - $oFDForum->unlinkFilesByMD5Filenames($file2delete); - } - $GLOBALS['ilAppEventHandler']->raise( 'Modules/Forum', 'updatedPost', @@ -2916,7 +2888,7 @@ public function viewThreadObject() ]) ? ilForumProperties::VIEW_DATE : ilForumProperties::VIEW_TREE); } $threadContentTemplate->setVariable('LIST_TYPE', $this->viewModeOptions[$currentViewMode]); - + $numberOfPostings = 0; // get forum- and thread-data @@ -3028,26 +3000,43 @@ public function viewThreadObject() )); } - // assistance val for anchor-links - $render_drafts = false; - $draftsObjects = null; - - if (ilForumPostDraft::isSavePostDraftAllowed() && !$this->user->isAnonymous()) { - $draftsObjects = ilForumPostDraft::getInstancesByUserIdAndThreadId( - $this->user->getId(), - $this->objCurrentTopic->getId() + $doRenderDrafts = ilForumPostDraft::isSavePostDraftAllowed() && !$this->user->isAnonymous(); + $draftsObjects = []; + if ($doRenderDrafts) { + $draftsObjects = ilForumPostDraft::getSortedDrafts( + (int) $this->user->getId(), + (int) $this->objCurrentTopic->getId(), + (int) $currentViewMode ); - if (count($draftsObjects) > 0) { - $render_drafts = true; - } } $pagedPostings = array_slice($subtree_nodes, $pageIndex * $pageSize, $pageSize); $this->ensureValidPageForCurrentPosting($subtree_nodes, $pagedPostings, $pageSize, $firstNodeInThread); + if ( + $doRenderDrafts && 0 === $pageIndex && + $currentViewMode === ilForumProperties::VIEW_DATE && + $currentSortation === ilForumProperties::VIEW_DATE_DESC + ) { + foreach ($draftsObjects as $draft) { + $referencePosting = array_values(array_filter( + $subtree_nodes, + static function (ilForumPost $post) use ($draft) : bool { + return $draft->getPostId() == $post->getId(); + } + ))[0] ?? $firstNodeInThread; + + $this->renderDraftContent( + $threadContentTemplate, + $this->requestAction, + $referencePosting, + [$draft] + ); + } + } + foreach ($pagedPostings as $node) { - /** @var $node ilForumPost */ $this->ctrl->clearParameters($this); if (!$this->isTopLevelReplyCommand() && $this->objCurrentPost->getId() == $node->getId()) { @@ -3055,8 +3044,6 @@ public function viewThreadObject() if (!$this->objCurrentTopic->isClosed() && in_array($this->requestAction, [ 'showreply', 'showedit', - 'showdraft', - 'editdraft' ])) { $this->renderPostingForm($threadContentTemplate, $frm, $node, $this->requestAction); } elseif (!$this->objCurrentTopic->isClosed() && $this->requestAction === 'delete') { @@ -3067,7 +3054,7 @@ public function viewThreadObject() ) { $threadContentTemplate->setVariable('FORM', $this->getDeleteFormHTML()); } - } elseif (!$this->objCurrentTopic->isClosed() && $this->requestAction === 'censor') { + } elseif (!$this->objCurrentTopic->isClosed() && $this->requestAction === 'censor') { if ($this->is_moderator) { $threadContentTemplate->setVariable('FORM', $this->getCensorshipFormHTML()); } @@ -3078,22 +3065,51 @@ public function viewThreadObject() } } } + $this->renderPostContent($threadContentTemplate, $node, $this->requestAction, $pageIndex, $postIndex); - $this->renderDraftContent($threadContentTemplate, $this->requestAction, $render_drafts, $node, $selected_draft_id); + if ($doRenderDrafts && $currentViewMode === ilForumProperties::VIEW_TREE) { + $this->renderDraftContent( + $threadContentTemplate, + $this->requestAction, + $node, + $draftsObjects[$node->getId()] ?? [] + ); + } $postIndex++; } - if ($firstNodeInThread instanceof \ilForumPost) { - if (!$this->objCurrentTopic->isClosed() && in_array($this->requestAction, ['showdraft', 'editdraft'])) { - $this->renderPostingForm($threadContentTemplate, $frm, $firstNodeInThread, $this->requestAction); + if ( + $doRenderDrafts && $pageIndex === (int) (ceil($numberOfPostings / $pageSize) - 1) && + $currentViewMode === ilForumProperties::VIEW_DATE && + $currentSortation === ilForumProperties::VIEW_DATE_ASC + ) { + foreach ($draftsObjects as $draft) { + $referencePosting = array_values(array_filter( + $subtree_nodes, + static function (ilForumPost $post) use ($draft) : bool { + return $draft->getPostId() == $post->getId(); + } + ))[0] ?? $firstNodeInThread; + + $this->renderDraftContent( + $threadContentTemplate, + $this->requestAction, + $referencePosting, + [$draft] + ); } + } + + if ( + $firstNodeInThread instanceof ilForumPost && $doRenderDrafts && + $currentViewMode === ilForumProperties::VIEW_TREE + ) { $this->renderDraftContent( $threadContentTemplate, $this->requestAction, - $render_drafts, $firstNodeInThread, - $selected_draft_id + $draftsObjects[$firstNodeInThread->getId()] ?? [] ); } @@ -4505,17 +4521,18 @@ protected function saveThreadAsDraftObject() \ilForumUtil::moveMediaObjects($form->getInput('message'), 'frm~d:html', $draftId, 'frm~d:html', $draftId); + $draftFileData = new \ilFileDataForumDrafts($this->object->getId(), $draftId); + + $files2delete = $form->getInput('del_file'); + if (is_array($files2delete) && count($files2delete) > 0) { + $draftFileData->unlinkFilesByMD5Filenames($files2delete); + } + if ($this->objProperties->isFileUploadAllowed()) { - $draftFileData = new \ilFileDataForumDrafts($this->object->getId(), $draftId); $file = $_FILES['userfile']; if (is_array($file) && !empty($file)) { $draftFileData->storeUploadedFile($file); } - - $files2delete = $form->getInput('del_file'); - if (is_array($files2delete) && count($files2delete) > 0) { - $draftFileData->unlinkFilesByMD5Filenames($files2delete); - } } \ilUtil::sendSuccess($this->lng->txt('save_draft_successfully'), true); @@ -4580,17 +4597,18 @@ protected function updateThreadDraftObject() $draft->getDraftId() ); + $draftFileData = new \ilFileDataForumDrafts($this->object->getId(), $draft->getDraftId()); + + $files2delete = $form->getInput('del_file'); + if (is_array($files2delete) && count($files2delete) > 0) { + $draftFileData->unlinkFilesByMD5Filenames($files2delete); + } + if ($this->objProperties->isFileUploadAllowed()) { - $draftFileData = new \ilFileDataForumDrafts($this->object->getId(), $draft->getDraftId()); $file = $_FILES['userfile']; if (is_array($file) && !empty($file)) { $draftFileData->storeUploadedFile($file); } - - $files2delete = $form->getInput('del_file'); - if (is_array($files2delete) && count($files2delete) > 0) { - $draftFileData->unlinkFilesByMD5Filenames($files2delete); - } } \ilUtil::sendSuccess($this->lng->txt('save_draft_successfully'), true); @@ -4833,19 +4851,20 @@ public function updateDraftObject() $update_draft->getDraftId() ); + $oFDForumDrafts = new ilFileDataForumDrafts($forumObj->getId(), $update_draft->getDraftId()); + + $file2delete = $oReplyEditForm->getInput('del_file'); + if (is_array($file2delete) && count($file2delete)) { + $oFDForumDrafts->unlinkFilesByMD5Filenames($file2delete); + } + if ($this->objProperties->isFileUploadAllowed()) { - $oFDForumDrafts = new ilFileDataForumDrafts($forumObj->getId(), $update_draft->getDraftId()); $file = $_FILES['userfile']; if (is_array($file) && !empty($file)) { $oFDForumDrafts->storeUploadedFile($file); } } - $file2delete = $oReplyEditForm->getInput('del_file'); - if (is_array($file2delete) && count($file2delete)) { - $oFDForumDrafts->unlinkFilesByMD5Filenames($file2delete); - } - $_SESSION['frm'][(int) $_GET['thr_pk']]['openTreeNodes'][] = (int) $this->objCurrentPost->getId(); ilUtil::sendSuccess($this->lng->txt('save_draft_successfully'), true); } @@ -5025,8 +5044,8 @@ private function renderSplitButton( !in_array( $action, ['showreply', 'showedit', 'censor', 'delete'] - ) && !$this->displayConfirmPostActivation() - )) { + ) && !$this->displayConfirmPostActivation() + )) { if ($this->is_moderator || $node->isActivated() || $node->isOwner($this->user->getId())) { if (!$this->objCurrentTopic->isClosed() && $node->isActivated() && $this->access->checkAccess('add_reply', '', (int) $this->object->getRefId()) && @@ -5056,7 +5075,7 @@ private function renderSplitButton( if (!$this->objCurrentTopic->isClosed() && ($node->isOwner($this->user->getId()) || $this->is_moderator) && !$node->isCensored() && - $this->user->getId() != ANONYMOUS_USER_ID + !$this->user->isAnonymous() ) { $this->ctrl->setParameter($this, 'action', 'showedit'); $this->ctrl->setParameter($this, 'pos_pk', $node->getId()); @@ -5073,7 +5092,7 @@ private function renderSplitButton( $this->ctrl->clearParameters($this); } - if ($this->user->getId() != ANONYMOUS_USER_ID && !$node->isPostRead()) { + if (!$this->user->isAnonymous() && !$node->isPostRead()) { $this->ctrl->setParameter($this, 'pos_pk', $node->getId()); $this->ctrl->setParameter($this, 'thr_pk', $node->getThreadId()); $this->ctrl->setParameter($this, 'page', $pageIndex); @@ -5093,8 +5112,7 @@ private function renderSplitButton( $this->ctrl->clearParameters($this); } - if ($this->user->getId() != ANONYMOUS_USER_ID && - $node->isPostRead() + if (!$this->user->isAnonymous() && $node->isPostRead() ) { $this->ctrl->setParameter($this, 'pos_pk', $node->getId()); $this->ctrl->setParameter($this, 'thr_pk', $node->getThreadId()); @@ -5128,7 +5146,7 @@ private function renderSplitButton( if (!$this->objCurrentTopic->isClosed() && ($this->is_moderator || ($node->isOwner($this->user->getId()) && !$node->hasReplies())) && - $this->user->getId() != ANONYMOUS_USER_ID + !$this->user->isAnonymous() ) { $this->ctrl->setParameter($this, 'action', 'delete'); $this->ctrl->setParameter($this, 'pos_pk', $node->getId()); @@ -5192,14 +5210,7 @@ private function renderSplitButton( } } } - } else { - if (!isset($draft)) { - $draftsObjects = ilForumPostDraft::getInstancesByUserIdAndThreadId( - $this->user->getId(), - $this->objCurrentTopic->getId() - ); - $draft = $draftsObjects[$node->getId()]; - } + } elseif ($_GET['draft_id'] != $draft->getDraftId() || !in_array($action, ['deletedraft', 'editdraft'])) { // get actions for drafts $this->ctrl->setParameter($this, 'action', 'publishdraft'); $this->ctrl->setParameter($this, 'pos_pk', $node->getId()); @@ -5239,14 +5250,10 @@ private function renderSplitButton( ); $actions['delete'] = $this->ctrl->getLinkTarget($this, 'viewThread', $node->getId()); $this->ctrl->clearParameters($this); - - if (isset($_GET['draft_id']) && $action === 'editdraft') { - $actions = array(); - } } $tpl->setCurrentBlock('posts_row'); - if (count($actions) > 0) { + if (count($actions) > 0 && !$this->objCurrentTopic->isClosed()) { $action_button = ilSplitButtonGUI::getInstance(); $i = 0; @@ -5267,11 +5274,7 @@ private function renderSplitButton( } } - if ($is_post) { - $tpl->setVariable('COMMANDS', $action_button->render()); - } elseif (!in_array($action, ['deletedraft', 'editdraft']) && !$this->objCurrentTopic->isClosed()) { - $tpl->setVariable('COMMANDS', $action_button->render()); - } + $tpl->setVariable('COMMANDS', $action_button->render()); } } @@ -5330,8 +5333,8 @@ private function doHistoryCheck($draftId) $this->uiFactory->button()->standard( $this->lng->txt('restore'), $this->ctrl->getLinkTarget($this, 'restoreFromHistory') - ) - )); + ) + )); $form_tpl->setVariable('ACC_AUTO_SAVE', $accordion->getHtml()); $form_tpl->parseCurrentBlock(); @@ -5375,7 +5378,7 @@ private function renderPostingForm(ilTemplate $tpl, ilForum $frm, ilForumPost $n $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->getMessage()); } - $tpl->setVariable('REPLY_ANKER', 'reply_' .$this->objCurrentPost->getId()); + $tpl->setVariable('REPLY_ANKER', 'reply_' . $this->objCurrentPost->getId()); $oEditReplyForm = $this->getReplyEditForm(); if ($action !== 'editdraft') { switch ($this->objProperties->getSubjectSetting()) { @@ -5461,28 +5464,26 @@ private function renderPostingForm(ilTemplate $tpl, ilForum $frm, ilForumPost $n case 'editdraft': if (in_array($this->ctrl->getCmd(), array('saveDraft', 'updateDraft', 'publishDraft'))) { $oEditReplyForm->setValuesByPost(); - } else { - if (isset($_GET['draft_id']) && (int) $_GET['draft_id'] > 0) { - /** - * @var object $draftObjects ilForumPost - */ - $draftObject = new ilForumPostDraft( - $this->user->getId(), - $this->objCurrentPost->getId(), - (int) $_GET['draft_id'] - ); - $oEditReplyForm->setValuesByArray(array( - 'alias' => $draftObject->getPostUserAlias(), - 'subject' => $draftObject->getPostSubject(), - 'message' => ilRTE::_replaceMediaObjectImageSrc($frm->prepareText( - $draftObject->getPostMessage(), - 2 - ), 1), - 'notify' => $draftObject->getNotify() ? true : false, - 'userfile' => '', - 'del_file' => [] - )); - } + } elseif (isset($_GET['draft_id']) && (int) $_GET['draft_id'] > 0) { + /** + * @var object $draftObjects ilForumPost + */ + $draftObject = new ilForumPostDraft( + $this->user->getId(), + $this->objCurrentPost->getId(), + (int) $_GET['draft_id'] + ); + $oEditReplyForm->setValuesByArray(array( + 'alias' => $draftObject->getPostUserAlias(), + 'subject' => $draftObject->getPostSubject(), + 'message' => ilRTE::_replaceMediaObjectImageSrc($frm->prepareText( + $draftObject->getPostMessage(), + 2 + ), 1), + 'notify' => $draftObject->getNotify() ? true : false, + 'userfile' => '', + 'del_file' => [] + )); } break; } diff --git a/Modules/Glossary/classes/class.ilGlossaryDataSet.php b/Modules/Glossary/classes/class.ilGlossaryDataSet.php index 5d54f464d1e5..3ff2efae1c51 100644 --- a/Modules/Glossary/classes/class.ilGlossaryDataSet.php +++ b/Modules/Glossary/classes/class.ilGlossaryDataSet.php @@ -10,6 +10,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 */ @@ -39,7 +40,7 @@ public function __construct() */ public function getSupportedVersions() { - return array("5.1.0"); + return array("5.1.0", "5.4.0"); } /** @@ -65,6 +66,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", @@ -81,6 +83,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", @@ -94,6 +97,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", @@ -107,6 +111,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", @@ -114,6 +119,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" + ); + } + } } /** @@ -133,6 +148,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) " . @@ -144,17 +160,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")); @@ -165,12 +199,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; + } + } } /** @@ -182,7 +233,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": @@ -220,6 +272,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; @@ -318,6 +373,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 11ee83ceaf23..bb097f21839c 100644 --- a/Modules/Glossary/classes/class.ilGlossaryExporter.php +++ b/Modules/Glossary/classes/class.ilGlossaryExporter.php @@ -46,7 +46,17 @@ 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) { @@ -161,8 +171,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.ilGlossaryTermGUI.php b/Modules/Glossary/classes/class.ilGlossaryTermGUI.php index 05412af70ecd..9456263a483d 100755 --- a/Modules/Glossary/classes/class.ilGlossaryTermGUI.php +++ b/Modules/Glossary/classes/class.ilGlossaryTermGUI.php @@ -527,28 +527,15 @@ public function confirmDefinitionDeletion() $this->setTabs(); $ilTabs->activateTab("definitions"); - // content style - $this->tpl->setCurrentBlock("ContentStyle"); - $this->tpl->setVariable( - "LOCATION_CONTENT_STYLESHEET", - ilObjStyleSheet::getContentStylePath($this->term_glossary->getStyleSheetId()) - ); - $this->tpl->parseCurrentBlock(); - - // syntax style - $this->tpl->setCurrentBlock("SyntaxStyle"); - $this->tpl->setVariable( - "LOCATION_SYNTAX_STYLESHEET", - ilObjStyleSheet::getSyntaxStylePath() - ); - $this->tpl->parseCurrentBlock(); + $this->tpl->addCss(ilObjStyleSheet::getContentStylePath($this->term_glossary->getStyleSheetId())); + $this->tpl->addCss(ilObjStyleSheet::getSyntaxStylePath()); $this->tpl->setTitle( $this->lng->txt("cont_term") . ": " . $this->term->getTerm() ); $this->tpl->setTitleIcon(ilUtil::getImagePath("icon_glo.svg")); - $this->tpl->addBlockfile("ADM_CONTENT", "def_list", "tpl.glossary_definition_delete.html", true); + $dtpl = new ilTemplate("tpl.glossary_definition_delete.html", true, true, "Modules/Glossary"); ilUtil::sendQuestion($this->lng->txt("info_delete_sure")); $this->tpl->setVariable("TXT_TERM", $this->term->getTerm()); @@ -562,20 +549,22 @@ public function confirmDefinitionDeletion() $page_gui->setFullscreenLink("ilias.php?baseClass=ilGlossaryPresentationGUI&ref_id=" . $_GET["ref_id"]); $output = $page_gui->preview(); - $this->tpl->setCurrentBlock("definition"); - $this->tpl->setVariable("PAGE_CONTENT", $output); - $this->tpl->setVariable("TXT_CANCEL", $this->lng->txt("cancel")); - $this->tpl->setVariable( + $dtpl->setCurrentBlock("definition"); + $dtpl->setVariable("PAGE_CONTENT", $output); + $dtpl->setVariable("TXT_CANCEL", $this->lng->txt("cancel")); + $dtpl->setVariable( "LINK_CANCEL", $this->ctrl->getLinkTarget($this, "cancelDefinitionDeletion") ); - $this->tpl->setVariable("TXT_CONFIRM", $this->lng->txt("confirm")); + $dtpl->setVariable("TXT_CONFIRM", $this->lng->txt("confirm")); $this->ctrl->setParameter($this, "def", $definition->getId()); - $this->tpl->setVariable( + $dtpl->setVariable( "LINK_CONFIRM", $this->ctrl->getLinkTarget($this, "deleteDefinition") ); - $this->tpl->parseCurrentBlock(); + $dtpl->parseCurrentBlock(); + + $this->tpl->setContent($dtpl->get()); } public function cancelDefinitionDeletion() diff --git a/Modules/Glossary/classes/class.ilObjGlossary.php b/Modules/Glossary/classes/class.ilObjGlossary.php index 36310c73c9b1..600a9e2b5d8f 100755 --- a/Modules/Glossary/classes/class.ilObjGlossary.php +++ b/Modules/Glossary/classes/class.ilObjGlossary.php @@ -356,15 +356,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/Glossary/templates/default/tpl.glossary_definition_delete.html b/Modules/Glossary/templates/default/tpl.glossary_definition_delete.html index 65548a6d7bf1..0d7367ba1f0b 100755 --- a/Modules/Glossary/templates/default/tpl.glossary_definition_delete.html +++ b/Modules/Glossary/templates/default/tpl.glossary_definition_delete.html @@ -1,6 +1,6 @@ + diff --git a/Services/Tracking/classes/class.ilLPTableBaseGUI.php b/Services/Tracking/classes/class.ilLPTableBaseGUI.php index 14ad7612ed84..d5e4b2dd0d34 100644 --- a/Services/Tracking/classes/class.ilLPTableBaseGUI.php +++ b/Services/Tracking/classes/class.ilLPTableBaseGUI.php @@ -706,8 +706,8 @@ protected function fillMetaCSV($a_csv) protected function showTimingsWarning($a_ref_id, $a_user_id) { - include_once 'Modules/Course/classes/Timings/class.ilTimingCache.php'; - if (ilTimingCache::_showWarning($a_ref_id, $a_user_id)) { + $timing_cache = ilTimingCache::getInstanceByRefId($a_ref_id); + if ($timing_cache->isWarningRequired($a_user_id)) { $timings = ilTimingCache::_getTimings($a_ref_id); if ($timings['item']['changeable'] && $timings['user'][$a_user_id]['end']) { $end = $timings['user'][$a_user_id]['end']; diff --git a/Services/Tracking/classes/class.ilLPXmlWriter.php b/Services/Tracking/classes/class.ilLPXmlWriter.php index 5f2a53a14a40..9a04050f3ad2 100644 --- a/Services/Tracking/classes/class.ilLPXmlWriter.php +++ b/Services/Tracking/classes/class.ilLPXmlWriter.php @@ -164,7 +164,7 @@ public function addLPInformation() array( 'UserId' => $rec["usr_id"], 'ObjId' => $rec["obj_id"], - 'RefIds' => implode($ref_ids, ","), + 'RefIds' => implode(",", $ref_ids), 'Timestamp' => $rec["status_changed"], 'LPStatus' => $rec["status"] ) diff --git a/Services/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php b/Services/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php index bacd1abb7c67..b0b02a6d363b 100644 --- a/Services/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php +++ b/Services/Tracking/classes/repository_statistics/class.ilTrMatrixTableGUI.php @@ -56,7 +56,6 @@ public function __construct($a_parent_obj, $a_parent_cmd, $ref_id) parent::__construct($a_parent_obj, $a_parent_cmd); - $this->setLimit(9999); $this->parseTitle($this->obj_id, "trac_matrix"); $this->setEnableHeader(true); diff --git a/Services/UIComponent/Explorer2/classes/class.ilTreeExplorerGUI.php b/Services/UIComponent/Explorer2/classes/class.ilTreeExplorerGUI.php index a0966714aa49..04d393687480 100644 --- a/Services/UIComponent/Explorer2/classes/class.ilTreeExplorerGUI.php +++ b/Services/UIComponent/Explorer2/classes/class.ilTreeExplorerGUI.php @@ -20,6 +20,7 @@ abstract class ilTreeExplorerGUI extends ilExplorerBaseGUI implements \ILIAS\UI\ protected $httpRequest; protected $tree = null; + protected $tree_label = ""; protected $order_field = ""; protected $order_field_numeric = false; protected $type_white_list = array(); @@ -439,6 +440,14 @@ public function build( return $node; } + /** + * @return string + */ + public function getTreeLabel() + { + return $this->tree_label; + } + /** * Get Tree UI * @@ -453,7 +462,12 @@ public function getTreeComponent() $tree->getNodeData($tree->readRootId()) ); - $tree = $f->tree()->expandable($this) + $label = $this->getTreeLabel(); + if ($this->getTreeLabel() == "" && $this->getNodeContent($this->getRootNode())) { + $label = $this->getNodeContent($this->getRootNode()); + } + + $tree = $f->tree()->expandable($label, $this) ->withData($data) ->withHighlightOnNodeClick(true); diff --git a/Services/UIComponent/GroupedList/templates/default/tpl.grouped_list.html b/Services/UIComponent/GroupedList/templates/default/tpl.grouped_list.html index 99271716cdcc..a8a8d032d6ad 100644 --- a/Services/UIComponent/GroupedList/templates/default/tpl.grouped_list.html +++ b/Services/UIComponent/GroupedList/templates/default/tpl.grouped_list.html @@ -1,7 +1,7 @@
    • - +
    • {TXT_ENTRY}
    • {TXT_ENTRY2}
    • diff --git a/Services/UIComponent/Panel/templates/default/tpl.panel.html b/Services/UIComponent/Panel/templates/default/tpl.panel.html index ac54cf9b1326..364487a157e4 100644 --- a/Services/UIComponent/Panel/templates/default/tpl.panel.html +++ b/Services/UIComponent/Panel/templates/default/tpl.panel.html @@ -1,5 +1,5 @@
      -

      {HEADING}

      +

      {HEADING}

      {BODY}
      diff --git a/Services/UICore/classes/MetaTemplate/PageContentGUI.php b/Services/UICore/classes/MetaTemplate/PageContentGUI.php index 1c1c15751056..5d65b8634799 100644 --- a/Services/UICore/classes/MetaTemplate/PageContentGUI.php +++ b/Services/UICore/classes/MetaTemplate/PageContentGUI.php @@ -15,6 +15,10 @@ class PageContentGUI */ private $template_file; + /** + * @var bool + */ + private $hiddenTitle = false; /** * @inheritDoc @@ -231,10 +235,12 @@ public function getBanner() /** * @param mixed $title + * @param bool $hidden */ - public function setTitle($title) + public function setTitle($title, bool $hidden = false) { $this->title = $title; + $this->hiddenTitle = $hidden; } @@ -595,11 +601,14 @@ private function fillHeader() if ($this->title != "") { $title = \ilUtil::stripScriptHTML($this->title); $this->template_file->setVariable("HEADER", $title); + if ($this->hiddenTitle) { + $this->template_file->touchBlock("hidden_title"); + } $header = true; } - if ($header) { + if ($header && !$this->hiddenTitle) { $this->template_file->setCurrentBlock("header_image"); $this->template_file->parseCurrentBlock(); } diff --git a/Services/UICore/classes/class.ilGlobalPageTemplate.php b/Services/UICore/classes/class.ilGlobalPageTemplate.php index b0e1c9da8a0b..50d0bcd45c11 100644 --- a/Services/UICore/classes/class.ilGlobalPageTemplate.php +++ b/Services/UICore/classes/class.ilGlobalPageTemplate.php @@ -219,9 +219,9 @@ public function setFilter(string $filter) /** * @inheritDoc */ - public function setTitle($a_title) + public function setTitle($a_title, $hidden = false) { - $this->legacy_content_template->setTitle((string) $a_title); + $this->legacy_content_template->setTitle((string) $a_title, $hidden); $short_title = (string) $this->il_settings->get('short_inst_name'); if (trim($short_title) === "") { diff --git a/Services/UICore/classes/class.ilGlobalTemplate.php b/Services/UICore/classes/class.ilGlobalTemplate.php index b3187824efc3..724dc2fb85b3 100755 --- a/Services/UICore/classes/class.ilGlobalTemplate.php +++ b/Services/UICore/classes/class.ilGlobalTemplate.php @@ -864,7 +864,7 @@ public function loadStandardTemplate() * * Will override the header_page_title. */ - public function setTitle($a_title) + public function setTitle($a_title, $hidden = false) { $this->title = $a_title; $this->header_page_title = $a_title; diff --git a/Services/UICore/interfaces/interface.ilGlobalTemplateInterface.php b/Services/UICore/interfaces/interface.ilGlobalTemplateInterface.php index bd3ba080d25c..e0aa2a15b604 100644 --- a/Services/UICore/interfaces/interface.ilGlobalTemplateInterface.php +++ b/Services/UICore/interfaces/interface.ilGlobalTemplateInterface.php @@ -93,7 +93,7 @@ public function loadStandardTemplate(); * * Will override the header_page_title. */ - public function setTitle($a_title); + public function setTitle($a_title, $hidden = false); /** diff --git a/Services/User/classes/Provider/UserMetaBarProvider.php b/Services/User/classes/Provider/UserMetaBarProvider.php index 3ed96170505b..2182b925c562 100644 --- a/Services/User/classes/Provider/UserMetaBarProvider.php +++ b/Services/User/classes/Provider/UserMetaBarProvider.php @@ -48,7 +48,7 @@ public function getMetaBarItems() : array // "User"-Menu $item[] = $mb->topParentItem($id('user')) ->withSymbol($this->dic->user()->getAvatar()) - ->withTitle("User") + ->withTitle($this->dic->language()->txt("personal_picture")) ->withPosition(4) ->withVisibilityCallable( function () { diff --git a/Services/User/classes/class.ilObjUserGUI.php b/Services/User/classes/class.ilObjUserGUI.php index a5dda98493b3..4d3c0b84c1b4 100755 --- a/Services/User/classes/class.ilObjUserGUI.php +++ b/Services/User/classes/class.ilObjUserGUI.php @@ -2346,7 +2346,7 @@ public function __sendProfileMail() $body .= ($usr_lang->txt("reg_mail_body_text3") . "\n"); $body .= $this->object->getProfileAsString($usr_lang); - $mmail->Subject($subject); + $mmail->Subject($subject, true); $mmail->Body($body); $mmail->Send(); diff --git a/Services/User/classes/class.ilUserCronCheckAccounts.php b/Services/User/classes/class.ilUserCronCheckAccounts.php index 84f6628dbfbd..e2f02867bad7 100644 --- a/Services/User/classes/class.ilUserCronCheckAccounts.php +++ b/Services/User/classes/class.ilUserCronCheckAccounts.php @@ -144,13 +144,13 @@ protected function checkNotConfirmedUserAccounts() $oRegSettigs = new ilRegistrationSettings(); $query = 'SELECT usr_id FROM usr_data ' - . 'WHERE reg_hash IS NOT NULL ' + . 'WHERE (reg_hash IS NOT NULL AND reg_hash != %s)' . 'AND active = %s ' . 'AND create_date < %s'; $res = $ilDB->queryF( $query, - array('integer', 'timestamp'), - array(0, date('Y-m-d H:i:s', time() - (int) $oRegSettigs->getRegistrationHashLifetime())) + array('text', 'integer', 'timestamp'), + array('', 0, date('Y-m-d H:i:s', time() - (int) $oRegSettigs->getRegistrationHashLifetime())) ); while ($row = $ilDB->fetchAssoc($res)) { $oUser = ilObjectFactory::getInstanceByObjId((int) $row['usr_id']); diff --git a/Services/User/classes/class.ilUserImportParser.php b/Services/User/classes/class.ilUserImportParser.php index 936c11a584a2..f3a4c43f0198 100755 --- a/Services/User/classes/class.ilUserImportParser.php +++ b/Services/User/classes/class.ilUserImportParser.php @@ -534,12 +534,11 @@ public function importBeginTag($a_xml_parser, $a_name, $a_attribs) case "saml": case "ldap": if (strcmp('saml', $a_attribs['type']) === 0) { - require_once './Services/Saml/classes/class.ilSamlIdp.php'; $list = ilSamlIdp::getActiveIdpList(); - if (count($list) == 1) { + if (count($list) === 1) { $this->auth_mode_set = true; - $ldap_id = current($list); - $this->userObj->setAuthMode('saml_' . $ldap_id); + $idp = current($list); + $this->userObj->setAuthMode('saml_' . $idp->getIdpId()); } break; } @@ -673,9 +672,8 @@ public function verifyBeginTag($a_xml_parser, $a_name, $a_attribs) case "saml": case "ldap": if (strcmp('saml', $a_attribs['type']) === 0) { - require_once './Services/Saml/classes/class.ilSamlIdp.php'; $list = ilSamlIdp::getActiveIdpList(); - if (count($list) != 1) { + if (count($list) !== 1) { $this->logFailure( $this->userObj->getImportId(), sprintf($lng->txt("usrimport_xml_attribute_value_illegal"), "AuthMode", "type", $a_attribs['type']) diff --git a/Services/User/classes/class.ilUserProfile.php b/Services/User/classes/class.ilUserProfile.php index 4faf2681bd91..4d3297590541 100755 --- a/Services/User/classes/class.ilUserProfile.php +++ b/Services/User/classes/class.ilUserProfile.php @@ -46,8 +46,8 @@ class ilUserProfile private static $user_field = array( "username" => array( "input" => "login", - "maxlength" => 64, - "size" => 40, + "maxlength" => 190, + "size" => 190, "method" => "getLogin", "course_export_fix_value" => 1, "group_export_fix_value" => 1, @@ -524,7 +524,7 @@ public function addStandardFieldsToForm($a_form, $a_user = null, array $custom_f $val->setValue($a_user->getLogin()); } $val->setMaxLength($p['maxlength']); - $val->setSize(40); + $val->setSize(255); $val->setRequired(true); } else { // user account name diff --git a/Services/User/classes/class.ilUserUtil.php b/Services/User/classes/class.ilUserUtil.php index 55e30358605b..6dc2c2e4808d 100755 --- a/Services/User/classes/class.ilUserUtil.php +++ b/Services/User/classes/class.ilUserUtil.php @@ -403,7 +403,7 @@ public static function getStartingPointAsUrl() default: $map = array( self::START_PD_OVERVIEW => 'ilias.php?baseClass=ilDashboardGUI&cmd=jumpToSelectedItems', - self::START_PD_SUBSCRIPTION => 'ilias.php?baseClass=ilDashboardGUI&cmd=jumpToMemberships', + self::START_PD_SUBSCRIPTION => 'ilias.php?baseClass=ilMembershipOverviewGUI', self::START_PD_WORKSPACE => 'ilias.php?baseClass=ilDashboardGUI&cmd=jumpToWorkspace', self::START_PD_CALENDAR => 'ilias.php?baseClass=ilDashboardGUI&cmd=jumpToCalendar', self::START_PD_MYSTAFF => 'ilias.php?baseClass=' . ilDashboardGUI::class . '&cmd=' . ilDashboardGUI::CMD_JUMP_TO_MY_STAFF diff --git a/Services/Utilities/classes/class.ilFileUtils.php b/Services/Utilities/classes/class.ilFileUtils.php index 58f19f5f894f..df34a04ec05c 100644 --- a/Services/Utilities/classes/class.ilFileUtils.php +++ b/Services/Utilities/classes/class.ilFileUtils.php @@ -669,6 +669,7 @@ public static function getDefaultValidExtensionWhiteList() 'gif', // IMAGE__GIF, 'gl', // VIDEO__GL, 'gan', + 'ggb', // GEOGEBRA 'gsd', // AUDIO__X_GSM, 'gsm', // AUDIO__X_GSM, 'gtar', // APPLICATION__X_GTAR, diff --git a/Services/WebDAV/classes/class.ilWebDAVObjDAVHelper.php b/Services/WebDAV/classes/class.ilWebDAVObjDAVHelper.php index c6739bc058fd..58d48e99bb2b 100644 --- a/Services/WebDAV/classes/class.ilWebDAVObjDAVHelper.php +++ b/Services/WebDAV/classes/class.ilWebDAVObjDAVHelper.php @@ -26,19 +26,24 @@ public function __construct(ilWebDAVRepositoryHelper $repo_helper) /** * Check if given object (either obj_id or ref_id) is compatible to be represented as a WebDAV object - * - * @param $id + * @param $id * @param bool $is_reference + * @param bool $do_name_check * @return bool */ - public function isDAVableObject($id, $is_reference = true, &$webdav_problems = null) + public function isDAVableObject($id, $is_reference = true, bool $do_name_check = true) { $obj_id = $is_reference ? $this->repo_helper->getObjectIdFromRefId($id) : $id; $type = $this->repo_helper->getObjectTypeFromObjId($obj_id); $title = $this->repo_helper->getObjectTitleFromObjId($obj_id); - $is_davable = $this->isDAVableObjType($type) && $this->isDAVableObjTitle($title, $webdav_problems); + if($do_name_check) { + $is_davable = $this->isDAVableObjType($type) && $this->isDAVableObjTitle($title); + } else { + $is_davable = $this->isDAVableObjType($type); + } + return $is_davable; } @@ -68,12 +73,9 @@ public function isDAVableObjType(string $type) : bool * @param $title * @return bool */ - public function isDAVableObjTitle(string $title, &$webdav_problems = null) : bool + public function isDAVableObjTitle(string $title) : bool { if ($this->hasTitleForbiddenChars($title)) { - if (!is_null($webdav_problems)) { - $webdav_problems['characters'][] = $title; - } return false; } if ($this->hasInvalidPrefixInTitle($title)) { @@ -81,6 +83,7 @@ public function isDAVableObjTitle(string $title, &$webdav_problems = null) : boo } return true; } + /** * Check for forbidden chars in title that are making trouble if displayed in WebDAV * diff --git a/Services/WebDAV/classes/dav/class.ilClientNodeDAV.php b/Services/WebDAV/classes/dav/class.ilClientNodeDAV.php index 248ca844aabe..041d4fc6747f 100644 --- a/Services/WebDAV/classes/dav/class.ilClientNodeDAV.php +++ b/Services/WebDAV/classes/dav/class.ilClientNodeDAV.php @@ -196,7 +196,7 @@ protected function checkIfRefIdIsValid($ref_id) { if ($ref_id > 0 && $this->repo_helper->objectWithRefIdExists($ref_id) - && $this->dav_helper->isDAVableObject($ref_id, true)) { + && $this->dav_helper->isDAVableObject($ref_id, true, false)) { return $ref_id; } } diff --git a/Services/WebServices/FileManager/templates/default/tpl.fm_launch_ws.html b/Services/WebServices/FileManager/templates/default/tpl.fm_launch_ws.html index 5b7b47490226..170f3081d0a8 100644 --- a/Services/WebServices/FileManager/templates/default/tpl.fm_launch_ws.html +++ b/Services/WebServices/FileManager/templates/default/tpl.fm_launch_ws.html @@ -1,5 +1,5 @@ - + + ``` #### Babel -[Babel](http://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively. +[Babel](https://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively. ```js import $ from "jquery"; @@ -29,39 +29,34 @@ import $ from "jquery"; #### Browserify/Webpack -There are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documention. In the script, including jQuery will usually look like this... +There are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documentation. In the script, including jQuery will usually look like this... ```js -var $ = require("jquery"); +var $ = require( "jquery" ); ``` #### AMD (Asynchronous Module Definition) -AMD is a module format built for the browser. For more information, we recommend [require.js' documentation](http://requirejs.org/docs/whyamd.html). +AMD is a module format built for the browser. For more information, we recommend [require.js' documentation](https://requirejs.org/docs/whyamd.html). ```js -define(["jquery"], function($) { +define( [ "jquery" ], function( $ ) { -}); +} ); ``` ### Node -To include jQuery in [Node](nodejs.org), first install with npm. +To include jQuery in [Node](https://nodejs.org/), first install with npm. ```sh npm install jquery ``` -For jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/tmpvar/jsdom). This can be useful for testing purposes. +For jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/jsdom/jsdom). This can be useful for testing purposes. ```js -require("jsdom").env("", function(err, window) { - if (err) { - console.error(err); - return; - } - - var $ = require("jquery")(window); -}); +const { JSDOM } = require( "jsdom" ); +const { window } = new JSDOM( "" ); +const $ = require( "jquery" )( window ); ``` diff --git a/libs/bower/bower_components/jquery/dist/core.js b/libs/bower/bower_components/jquery/dist/core.js deleted file mode 100644 index 0e95274cf48d..000000000000 --- a/libs/bower/bower_components/jquery/dist/core.js +++ /dev/null @@ -1,476 +0,0 @@ -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - -define( [ - "./var/arr", - "./var/document", - "./var/getProto", - "./var/slice", - "./var/concat", - "./var/push", - "./var/indexOf", - "./var/class2type", - "./var/toString", - "./var/hasOwn", - "./var/fnToString", - "./var/ObjectFunctionString", - "./var/support", - "./core/DOMEval" -], function( arr, document, getProto, slice, concat, push, indexOf, - class2type, toString, hasOwn, fnToString, ObjectFunctionString, - support, DOMEval ) { - -"use strict"; - -var - version = "3.2.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} - -return jQuery; -} ); diff --git a/libs/bower/bower_components/jquery/dist/jquery.js b/libs/bower/bower_components/jquery/dist/jquery.js index d2d8ca4790e5..50937333b99a 100644 --- a/libs/bower/bower_components/jquery/dist/jquery.js +++ b/libs/bower/bower_components/jquery/dist/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v3.2.1 + * jQuery JavaScript Library v3.5.1 * https://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * https://jquery.org/license * - * Date: 2017-03-20T18:59Z + * Date: 2020-05-04T22:49Z */ ( function( global, factory ) { @@ -47,13 +47,16 @@ var arr = []; -var document = window.document; - var getProto = Object.getPrototypeOf; var slice = arr.slice; -var concat = arr.concat; +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + var push = arr.push; @@ -71,16 +74,72 @@ var ObjectFunctionString = fnToString.call( Object ); var support = {}; +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; - function DOMEval( code, doc ) { + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { doc = doc || document; - var script = doc.createElement( "script" ); + var i, val, + script = doc.createElement( "script" ); script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } doc.head.appendChild( script ).parentNode.removeChild( script ); } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} /* global Symbol */ // Defining this global in .eslintrc.json would create a danger of using the global // unguarded in another place, it seems safer to define global only for this module @@ -88,7 +147,7 @@ var support = {}; var - version = "3.2.1", + version = "3.5.1", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -96,19 +155,6 @@ var // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); }; jQuery.fn = jQuery.prototype = { @@ -175,6 +221,18 @@ jQuery.fn = jQuery.prototype = { return this.eq( -1 ); }, + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); @@ -209,7 +267,7 @@ jQuery.extend = jQuery.fn.extend = function() { } // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } @@ -226,25 +284,28 @@ jQuery.extend = jQuery.fn.extend = function() { // Extend the base object for ( name in options ) { - src = target[ name ]; copy = options[ name ]; + // Prevent Object.prototype pollution // Prevent never-ending loop - if ( target === copy ) { + if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; + clone = src; } + copyIsArray = false; // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); @@ -275,28 +336,6 @@ jQuery.extend( { noop: function() {}, - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - isPlainObject: function( obj ) { var proto, Ctor; @@ -319,9 +358,6 @@ jQuery.extend( { }, isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 var name; for ( name in obj ) { @@ -330,27 +366,10 @@ jQuery.extend( { return true; }, - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); }, each: function( obj, callback ) { @@ -374,13 +393,6 @@ jQuery.extend( { return obj; }, - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; @@ -467,43 +479,12 @@ jQuery.extend( { } // Flatten any nested arrays - return concat.apply( [], ret ); + return flat( ret ); }, // A global GUID counter for objects guid: 1, - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support @@ -515,7 +496,7 @@ if ( typeof Symbol === "function" ) { // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { +function( _i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); @@ -526,9 +507,9 @@ function isArrayLike( obj ) { // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); + type = toType( obj ); - if ( type === "function" || jQuery.isWindow( obj ) ) { + if ( isFunction( obj ) || isWindow( obj ) ) { return false; } @@ -537,17 +518,16 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.3.3 + * Sizzle CSS Selector Engine v2.3.5 * https://sizzlejs.com/ * - * Copyright jQuery Foundation and other contributors + * Copyright JS Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://js.foundation/ * - * Date: 2016-08-08 + * Date: 2020-03-14 */ -(function( window ) { - +( function( window ) { var i, support, Expr, @@ -578,6 +558,7 @@ var i, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), + nonnativeSelectorCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; @@ -586,61 +567,71 @@ var i, }, // Instance methods - hasOwn = ({}).hasOwnProperty, + hasOwn = ( {} ).hasOwnProperty, arr = [], pop = arr.pop, - push_native = arr.push, + pushNative = arr.push, push = arr.push, slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { - if ( list[i] === elem ) { + if ( list[ i ] === elem ) { return i; } } return -1; }, - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), @@ -651,16 +642,19 @@ var i, "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, + rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, @@ -673,18 +667,21 @@ var i, // CSS escapes // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair high < 0 ? - // BMP codepoint String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, @@ -700,7 +697,8 @@ var i, } // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped @@ -715,9 +713,9 @@ var i, setDocument(); }, - disabledAncestor = addCombinator( + inDisabledFieldset = addCombinator( function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; }, { dir: "parentNode", next: "legend" } ); @@ -725,18 +723,20 @@ var i, // Optimize for push.apply( _, NodeList ) try { push.apply( - (arr = slice.call( preferredDoc.childNodes )), + ( arr = slice.call( preferredDoc.childNodes ) ), preferredDoc.childNodes ); + // Support: Android<4.0 // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { - push_native.apply( target, slice.call(els) ); + pushNative.apply( target, slice.call( els ) ); } : // Support: IE<9 @@ -744,8 +744,9 @@ try { function( target, els ) { var j = target.length, i = 0; + // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} + while ( ( target[ j++ ] = els[ i++ ] ) ) {} target.length = j - 1; } }; @@ -769,24 +770,21 @@ function Sizzle( selector, context, results, seed ) { // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } + setDocument( context ); context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { // ID selector - if ( (m = match[1]) ) { + if ( ( m = match[ 1 ] ) ) { // Document context if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { + if ( ( elem = context.getElementById( m ) ) ) { // Support: IE, Opera, Webkit // TODO: identify versions @@ -805,7 +803,7 @@ function Sizzle( selector, context, results, seed ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && + if ( newContext && ( elem = newContext.getElementById( m ) ) && contains( context, elem ) && elem.id === m ) { @@ -815,12 +813,12 @@ function Sizzle( selector, context, results, seed ) { } // Type selector - } else if ( match[2] ) { + } else if ( match[ 2 ] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); @@ -830,50 +828,62 @@ function Sizzle( selector, context, results, seed ) { // Take advantage of querySelectorAll if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 + // Support: IE 8 only // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); } newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; } - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); } } } @@ -894,12 +904,14 @@ function createCache() { var keys = []; function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries delete cache[ keys.shift() ]; } - return (cache[ key + " " ] = value); + return ( cache[ key + " " ] = value ); } return cache; } @@ -918,17 +930,19 @@ function markFunction( fn ) { * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { - var el = document.createElement("fieldset"); + var el = document.createElement( "fieldset" ); try { return !!fn( el ); - } catch (e) { + } catch ( e ) { return false; } finally { + // Remove from its parent by default if ( el.parentNode ) { el.parentNode.removeChild( el ); } + // release memory in IE el = null; } @@ -940,11 +954,11 @@ function assert( fn ) { * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { - var arr = attrs.split("|"), + var arr = attrs.split( "|" ), i = arr.length; while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; + Expr.attrHandle[ arr[ i ] ] = handler; } } @@ -966,7 +980,7 @@ function siblingCheck( a, b ) { // Check if b follows a if ( cur ) { - while ( (cur = cur.nextSibling) ) { + while ( ( cur = cur.nextSibling ) ) { if ( cur === b ) { return -1; } @@ -994,7 +1008,7 @@ function createInputPseudo( type ) { function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; + return ( name === "input" || name === "button" ) && elem.type === type; }; } @@ -1037,7 +1051,7 @@ function createDisabledPseudo( disabled ) { // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; + inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; @@ -1059,21 +1073,21 @@ function createDisabledPseudo( disabled ) { * @param {Function} fn */ function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { + return markFunction( function( argument ) { argument = +argument; - return markFunction(function( seed, matches ) { + return markFunction( function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); } } - }); - }); + } ); + } ); } /** @@ -1094,10 +1108,13 @@ support = Sizzle.support = {}; * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); }; /** @@ -1110,7 +1127,11 @@ setDocument = Sizzle.setDocument = function( node ) { doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } @@ -1119,10 +1140,14 @@ setDocument = Sizzle.setDocument = function( node ) { docElem = document.documentElement; documentIsHTML = !isXML( document ); - // Support: IE 9-11, Edge + // Support: IE 9 - 11+, Edge 12 - 18+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { // Support: IE 11, Edge if ( subWindow.addEventListener ) { @@ -1134,25 +1159,36 @@ setDocument = Sizzle.setDocument = function( node ) { } } + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) - support.attributes = assert(function( el ) { + support.attributes = assert( function( el ) { el.className = "i"; - return !el.getAttribute("className"); - }); + return !el.getAttribute( "className" ); + } ); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); @@ -1161,38 +1197,38 @@ setDocument = Sizzle.setDocument = function( node ) { // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { + support.getById = assert( function( el ) { docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); + } ); // ID filter and find if ( support.getById ) { - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { - return elem.getAttribute("id") === attrId; + return elem.getAttribute( "id" ) === attrId; }; }; - Expr.find["ID"] = function( id, context ) { + Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); + elem.getAttributeNode( "id" ); return node && node.value === attrId; }; }; // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { + Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); @@ -1200,7 +1236,7 @@ setDocument = Sizzle.setDocument = function( node ) { if ( elem ) { // Verify the id attribute - node = elem.getAttributeNode("id"); + node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } @@ -1208,8 +1244,8 @@ setDocument = Sizzle.setDocument = function( node ) { // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } @@ -1222,7 +1258,7 @@ setDocument = Sizzle.setDocument = function( node ) { } // Tag - Expr.find["TAG"] = support.getElementsByTagName ? + Expr.find[ "TAG" ] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); @@ -1237,12 +1273,13 @@ setDocument = Sizzle.setDocument = function( node ) { var elem, tmp = [], i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } @@ -1254,7 +1291,7 @@ setDocument = Sizzle.setDocument = function( node ) { }; // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } @@ -1275,10 +1312,14 @@ setDocument = Sizzle.setDocument = function( node ) { // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + // Build QSA regex // Regex strategy adopted from Diego Perini - assert(function( el ) { + assert( function( el ) { + + var input; + // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, @@ -1292,78 +1333,98 @@ setDocument = Sizzle.setDocument = function( node ) { // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { + if ( !el.querySelectorAll( "[selected]" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibling-combinator selector` fails if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); + rbuggyQSA.push( ".#.+[+~]" ); } - }); - assert(function( el ) { + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { el.innerHTML = "" + ""; // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); + var input = document.createElement( "input" ); input.setAttribute( "type", "hidden" ); el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { + if ( el.querySelectorAll( "[name=d]" ).length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: IE9-11+ // IE's :disabled selector does not pick up the children of disabled fieldsets docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } + // Support: Opera 10 - 11 only // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); } - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { - assert(function( el ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( el, "*" ); @@ -1372,11 +1433,11 @@ setDocument = Sizzle.setDocument = function( node ) { // Gecko does not error, returns false instead matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); - }); + } ); } - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); /* Contains ---------------------------------------------------------------------- */ @@ -1393,11 +1454,11 @@ setDocument = Sizzle.setDocument = function( node ) { adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); + ) ); } : function( a, b ) { if ( b ) { - while ( (b = b.parentNode) ) { + while ( ( b = b.parentNode ) ) { if ( b === a ) { return true; } @@ -1426,7 +1487,11 @@ setDocument = Sizzle.setDocument = function( node ) { } // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected @@ -1434,13 +1499,24 @@ setDocument = Sizzle.setDocument = function( node ) { // Disconnected nodes if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { return -1; } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { return 1; } @@ -1453,6 +1529,7 @@ setDocument = Sizzle.setDocument = function( node ) { return compare & 4 ? -1 : 1; } : function( a, b ) { + // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; @@ -1468,8 +1545,14 @@ setDocument = Sizzle.setDocument = function( node ) { // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ aup ? -1 : bup ? 1 : sortInput ? @@ -1483,26 +1566,32 @@ setDocument = Sizzle.setDocument = function( node ) { // Otherwise we need full lists of their ancestors for comparison cur = a; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { ap.unshift( cur ); } cur = b; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { + while ( ap[ i ] === bp[ i ] ) { i++; } return i ? + // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : + siblingCheck( ap[ i ], bp[ i ] ) : // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ 0; }; @@ -1514,16 +1603,10 @@ Sizzle.matches = function( expr, elements ) { }; Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); + setDocument( elem ); if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && + !nonnativeSelectorCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { @@ -1532,32 +1615,46 @@ Sizzle.matchesSelector = function( elem, expr ) { // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { return ret; } - } catch (e) {} + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { + // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { + // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : @@ -1567,13 +1664,13 @@ Sizzle.attr = function( elem, name ) { val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; }; Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); + return ( sel + "" ).replace( rcssescape, fcssescape ); }; Sizzle.error = function( msg ) { @@ -1596,7 +1693,7 @@ Sizzle.uniqueSort = function( results ) { results.sort( sortOrder ); if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } @@ -1624,17 +1721,21 @@ getText = Sizzle.getText = function( elem ) { nodeType = elem.nodeType; if ( !nodeType ) { + // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { + while ( ( node = elem[ i++ ] ) ) { + // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { + // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); @@ -1643,6 +1744,7 @@ getText = Sizzle.getText = function( elem ) { } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } + // Do not include comment or processing instruction nodes return ret; @@ -1670,19 +1772,21 @@ Expr = Sizzle.selectors = { preFilter: { "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) @@ -1693,22 +1797,25 @@ Expr = Sizzle.selectors = { 7 sign of y-component 8 y of y-component */ - match[1] = match[1].toLowerCase(); + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } return match; @@ -1716,26 +1823,28 @@ Expr = Sizzle.selectors = { "PSEUDO": function( match ) { var excess, - unquoted = !match[6] && match[2]; + unquoted = !match[ 6 ] && match[ 2 ]; - if ( matchExpr["CHILD"].test( match[0] ) ) { + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { return null; } // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && + ( excess = tokenize( unquoted, true ) ) && + // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) @@ -1748,7 +1857,9 @@ Expr = Sizzle.selectors = { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? - function() { return true; } : + function() { + return true; + } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; @@ -1758,10 +1869,16 @@ Expr = Sizzle.selectors = { var pattern = classCache[ className + " " ]; return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); }, "ATTR": function( name, operator, check ) { @@ -1777,6 +1894,8 @@ Expr = Sizzle.selectors = { result += ""; + /* eslint-disable max-len */ + return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : @@ -1785,10 +1904,12 @@ Expr = Sizzle.selectors = { operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; + /* eslint-enable max-len */ + }; }, - "CHILD": function( type, what, argument, first, last ) { + "CHILD": function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; @@ -1800,7 +1921,7 @@ Expr = Sizzle.selectors = { return !!elem.parentNode; } : - function( elem, context, xml ) { + function( elem, _context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, @@ -1814,7 +1935,7 @@ Expr = Sizzle.selectors = { if ( simple ) { while ( dir ) { node = elem; - while ( (node = node[ dir ]) ) { + while ( ( node = node[ dir ] ) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { @@ -1822,6 +1943,7 @@ Expr = Sizzle.selectors = { return false; } } + // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } @@ -1837,22 +1959,22 @@ Expr = Sizzle.selectors = { // ...in a gzip-friendly way node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; - while ( (node = ++nodeIndex && node && node[ dir ] || + while ( ( node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { + ( diff = nodeIndex = 0 ) || start.pop() ) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { @@ -1862,16 +1984,18 @@ Expr = Sizzle.selectors = { } } else { + // Use previously-cached element index if available if ( useCache ) { + // ...in a gzip-friendly way node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; @@ -1881,9 +2005,10 @@ Expr = Sizzle.selectors = { // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : @@ -1892,12 +2017,13 @@ Expr = Sizzle.selectors = { // Cache the index of each encountered element if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || + ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); uniqueCache[ type ] = [ dirruns, diff ]; } @@ -1918,6 +2044,7 @@ Expr = Sizzle.selectors = { }, "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters @@ -1937,15 +2064,15 @@ Expr = Sizzle.selectors = { if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { + markFunction( function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); } - }) : + } ) : function( elem ) { return fn( elem, 0, args ); }; @@ -1956,8 +2083,10 @@ Expr = Sizzle.selectors = { }, pseudos: { + // Potentially complex pseudos - "not": markFunction(function( selector ) { + "not": markFunction( function( selector ) { + // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators @@ -1966,39 +2095,40 @@ Expr = Sizzle.selectors = { matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { + markFunction( function( seed, matches, _context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); } } - }) : - function( elem, context, xml ) { - input[0] = elem; + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; matcher( input, null, xml, results ); + // Don't keep the element (issue #299) - input[0] = null; + input[ 0 ] = null; return !results.pop(); }; - }), + } ), - "has": markFunction(function( selector ) { + "has": markFunction( function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; - }), + } ), - "contains": markFunction(function( text ) { + "contains": markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; - }), + } ), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value @@ -2008,25 +2138,26 @@ Expr = Sizzle.selectors = { // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { + // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { + if ( !ridentifier.test( lang || "" ) ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { - if ( (elemLang = documentIsHTML ? + if ( ( elemLang = documentIsHTML ? elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); return false; }; - }), + } ), // Miscellaneous "target": function( elem ) { @@ -2039,7 +2170,9 @@ Expr = Sizzle.selectors = { }, "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties @@ -2047,16 +2180,20 @@ Expr = Sizzle.selectors = { "disabled": createDisabledPseudo( true ), "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); }, "selected": function( elem ) { + // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions elem.parentNode.selectedIndex; } @@ -2065,6 +2202,7 @@ Expr = Sizzle.selectors = { // Contents "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) @@ -2078,7 +2216,7 @@ Expr = Sizzle.selectors = { }, "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); + return !Expr.pseudos[ "empty" ]( elem ); }, // Element/input types @@ -2102,57 +2240,62 @@ Expr = Sizzle.selectors = { // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); }, // Position-in-collection - "first": createPositionalPseudo(function() { + "first": createPositionalPseudo( function() { return [ 0 ]; - }), + } ), - "last": createPositionalPseudo(function( matchIndexes, length ) { + "last": createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; - }), + } ), - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; - }), + } ), - "even": createPositionalPseudo(function( matchIndexes, length ) { + "even": createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "odd": createPositionalPseudo(function( matchIndexes, length ) { + "odd": createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; - }) + } ) } }; -Expr.pseudos["nth"] = Expr.pseudos["eq"]; +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { @@ -2183,37 +2326,39 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { while ( soFar ) { // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { if ( match ) { + // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; + soFar = soFar.slice( match[ 0 ].length ) || soFar; } - groups.push( (tokens = []) ); + groups.push( ( tokens = [] ) ); } matched = false; // Combinators - if ( (match = rcombinators.exec( soFar )) ) { + if ( ( match = rcombinators.exec( soFar ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, + // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); + type: match[ 0 ].replace( rtrim, " " ) + } ); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, type: type, matches: match - }); + } ); soFar = soFar.slice( matched.length ); } } @@ -2230,6 +2375,7 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { soFar.length : soFar ? Sizzle.error( selector ) : + // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; @@ -2239,7 +2385,7 @@ function toSelector( tokens ) { len = tokens.length, selector = ""; for ( ; i < len; i++ ) { - selector += tokens[i].value; + selector += tokens[ i ].value; } return selector; } @@ -2252,9 +2398,10 @@ function addCombinator( matcher, combinator, base ) { doneName = done++; return combinator.first ? + // Check against closest ancestor/preceding element function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } @@ -2269,7 +2416,7 @@ function addCombinator( matcher, combinator, base ) { // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; @@ -2277,27 +2424,29 @@ function addCombinator( matcher, combinator, base ) { } } } else { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); if ( skip && skip === elem.nodeName.toLowerCase() ) { elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && + } else if ( ( oldCache = uniqueCache[ key ] ) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); + return ( newCache[ 2 ] = oldCache[ 2 ] ); } else { + // Reuse newcache so results back-propagate to previous elements uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { return true; } } @@ -2313,20 +2462,20 @@ function elementMatcher( matchers ) { function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { + if ( !matchers[ i ]( elem, context, xml ) ) { return false; } } return true; } : - matchers[0]; + matchers[ 0 ]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); + Sizzle( selector, contexts[ i ], results ); } return results; } @@ -2339,7 +2488,7 @@ function condense( unmatched, map, filter, context, xml ) { mapped = map != null; for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { + if ( ( elem = unmatched[ i ] ) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { @@ -2359,14 +2508,18 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } - return markFunction(function( seed, results, context, xml ) { + return markFunction( function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? @@ -2374,6 +2527,7 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS elems, matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? @@ -2397,8 +2551,8 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); } } } @@ -2406,25 +2560,27 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) ) { + if ( ( elem = matcherOut[ i ] ) ) { + // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); + temp.push( ( matcherIn[ i ] = elem ) ); } } - postFinder( null, (matcherOut = []), temp, xml ); + postFinder( null, ( matcherOut = [] ), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - seed[temp] = !(results[temp] = elem); + seed[ temp ] = !( results[ temp ] = elem ); } } } @@ -2442,14 +2598,14 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS push.apply( results, matcherOut ); } } - }); + } ); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) @@ -2461,38 +2617,43 @@ function matcherFromTokens( tokens ) { }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? + ( checkContext = context ).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { + if ( Expr.relative[ tokens[ j ].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), j < len && toSelector( tokens ) ); } @@ -2513,28 +2674,40 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { unmatched = seed && [], setMatched = [], contextBackup = outermostContext, + // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), len = elems.length; if ( outermost ) { - outermostContext = context === document || context || outermost; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { if ( byElement && elem ) { j = 0; - if ( !context && elem.ownerDocument !== document ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { setDocument( elem ); xml = !documentIsHTML; } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { results.push( elem ); break; } @@ -2546,8 +2719,9 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // Track unmatched elements for set filters if ( bySet ) { + // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { + if ( ( elem = !matcher && elem ) ) { matchedCount--; } @@ -2571,16 +2745,17 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; - while ( (matcher = setMatchers[j++]) ) { + while ( ( matcher = setMatchers[ j++ ] ) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); } } } @@ -2621,13 +2796,14 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { cached = compilerCache[ selector + " " ]; if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { - cached = matcherFromTokens( match[i] ); + cached = matcherFromTokens( match[ i ] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { @@ -2636,7 +2812,10 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { } // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); // Save selector and tokenization cached.selector = selector; @@ -2656,7 +2835,7 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); results = results || []; @@ -2665,11 +2844,12 @@ select = Sizzle.select = function( selector, context, results, seed ) { if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; if ( !context ) { return results; @@ -2682,20 +2862,22 @@ select = Sizzle.select = function( selector, context, results, seed ) { } // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; while ( i-- ) { - token = tokens[i]; + token = tokens[ i ]; // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { + if ( Expr.relative[ ( type = token.type ) ] ) { break; } - if ( (find = Expr.find[ type ]) ) { + if ( ( find = Expr.find[ type ] ) ) { + // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); @@ -2726,7 +2908,7 @@ select = Sizzle.select = function( selector, context, results, seed ) { // One-time assignments // Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function @@ -2737,58 +2919,59 @@ setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { +support.sortDetached = assert( function( el ) { + // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); // Support: IE<8 // Prevent attribute/property "interpolation" // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { +if ( !assert( function( el ) { el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } - }); + } ); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { +if ( !support.attributes || !assert( function( el ) { el.innerHTML = ""; el.firstChild.setAttribute( "value", "" ); return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } - }); + } ); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : - null; + null; } - }); + } ); } return Sizzle; -})( window ); +} )( window ); @@ -2848,11 +3031,9 @@ var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>| -var risSimple = /^.[^:#\[\.,]*$/; - // Implement the identical functionality for filter and not function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { + if ( isFunction( qualifier ) ) { return jQuery.grep( elements, function( elem, i ) { return !!qualifier.call( elem, i, elem ) !== not; } ); @@ -2872,16 +3053,8 @@ function winnow( elements, qualifier, not ) { } ); } - // Simple selector that can be filtered directly, removing non-Elements - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - // Complex selector, compare the two sets, removing non-Elements - qualifier = jQuery.filter( qualifier, elements ); - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; - } ); + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); } jQuery.filter = function( expr, elems, not ) { @@ -3002,7 +3175,7 @@ var rootjQuery, for ( match in context ) { // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { + if ( isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes @@ -3045,7 +3218,7 @@ var rootjQuery, // HANDLE: $(function) // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { + } else if ( isFunction( selector ) ) { return root.ready !== undefined ? root.ready( selector ) : @@ -3167,7 +3340,7 @@ jQuery.each( { parents: function( elem ) { return dir( elem, "parentNode" ); }, - parentsUntil: function( elem, i, until ) { + parentsUntil: function( elem, _i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { @@ -3182,10 +3355,10 @@ jQuery.each( { prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, - nextUntil: function( elem, i, until ) { + nextUntil: function( elem, _i, until ) { return dir( elem, "nextSibling", until ); }, - prevUntil: function( elem, i, until ) { + prevUntil: function( elem, _i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { @@ -3195,18 +3368,24 @@ jQuery.each( { return siblings( elem.firstChild ); }, contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } + if ( elem.contentDocument != null && - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { - return jQuery.merge( [], elem.childNodes ); + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { @@ -3360,11 +3539,11 @@ jQuery.Callbacks = function( options ) { ( function add( args ) { jQuery.each( args, function( _, arg ) { - if ( jQuery.isFunction( arg ) ) { + if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } - } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { + } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); @@ -3479,11 +3658,11 @@ function adoptValue( value, resolve, reject, noValue ) { try { // Check for promise aspect first to privilege synchronous behavior - if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { + if ( value && isFunction( ( method = value.promise ) ) ) { method.call( value ).done( resolve ).fail( reject ); // Other thenables - } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { + } else if ( value && isFunction( ( method = value.then ) ) ) { method.call( value, resolve, reject ); // Other non-thenables @@ -3538,17 +3717,17 @@ jQuery.extend( { var fns = arguments; return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { + jQuery.each( tuples, function( _i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; // deferred.progress(function() { bind to newDefer or newDefer.notify }) // deferred.done(function() { bind to newDefer or newDefer.resolve }) // deferred.fail(function() { bind to newDefer or newDefer.reject }) deferred[ tuple[ 1 ] ]( function() { var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { + if ( returned && isFunction( returned.promise ) ) { returned.promise() .progress( newDefer.notify ) .done( newDefer.resolve ) @@ -3602,7 +3781,7 @@ jQuery.extend( { returned.then; // Handle a returned thenable - if ( jQuery.isFunction( then ) ) { + if ( isFunction( then ) ) { // Special processors (notify) just wait for resolution if ( special ) { @@ -3698,7 +3877,7 @@ jQuery.extend( { resolve( 0, newDefer, - jQuery.isFunction( onProgress ) ? + isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith @@ -3710,7 +3889,7 @@ jQuery.extend( { resolve( 0, newDefer, - jQuery.isFunction( onFulfilled ) ? + isFunction( onFulfilled ) ? onFulfilled : Identity ) @@ -3721,7 +3900,7 @@ jQuery.extend( { resolve( 0, newDefer, - jQuery.isFunction( onRejected ) ? + isFunction( onRejected ) ? onRejected : Thrower ) @@ -3761,8 +3940,15 @@ jQuery.extend( { // fulfilled_callbacks.disable tuples[ 3 - i ][ 2 ].disable, + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock ); } @@ -3832,7 +4018,7 @@ jQuery.extend( { // Use .then() to unwrap secondary thenables (cf. gh-3000) if ( master.state() === "pending" || - jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { return master.then(); } @@ -3960,7 +4146,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { bulk = key == null; // Sets many values - if ( jQuery.type( key ) === "object" ) { + if ( toType( key ) === "object" ) { chainable = true; for ( i in key ) { access( elems, fn, i, key[ i ], true, emptyGet, raw ); @@ -3970,7 +4156,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { } else if ( value !== undefined ) { chainable = true; - if ( !jQuery.isFunction( value ) ) { + if ( !isFunction( value ) ) { raw = true; } @@ -3984,7 +4170,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { // ...except when executing function values } else { bulk = fn; - fn = function( elem, key, value ) { + fn = function( elem, _key, value ) { return bulk.call( jQuery( elem ), value ); }; } @@ -4012,6 +4198,23 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { return len ? fn( elems[ 0 ], key ) : emptyGet; }; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} var acceptData = function( owner ) { // Accepts only: @@ -4074,14 +4277,14 @@ Data.prototype = { // Handle: [ owner, key, value ] args // Always use camelCase key (gh-2257) if ( typeof data === "string" ) { - cache[ jQuery.camelCase( data ) ] = value; + cache[ camelCase( data ) ] = value; // Handle: [ owner, { properties } ] args } else { // Copy the properties one-by-one to the cache object for ( prop in data ) { - cache[ jQuery.camelCase( prop ) ] = data[ prop ]; + cache[ camelCase( prop ) ] = data[ prop ]; } } return cache; @@ -4091,7 +4294,7 @@ Data.prototype = { this.cache( owner ) : // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; }, access: function( owner, key, value ) { @@ -4139,9 +4342,9 @@ Data.prototype = { // If key is an array of keys... // We always set camelCase keys, so remove that. - key = key.map( jQuery.camelCase ); + key = key.map( camelCase ); } else { - key = jQuery.camelCase( key ); + key = camelCase( key ); // If a key with the spaces exists, use it. // Otherwise, create an array by matching non-whitespace @@ -4287,7 +4490,7 @@ jQuery.fn.extend( { if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice( 5 ) ); + name = camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } @@ -4491,6 +4694,26 @@ var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } var isHiddenWithinTree = function( elem, el ) { // isHiddenWithinTree might be called from jQuery#filter function; @@ -4505,37 +4728,15 @@ var isHiddenWithinTree = function( elem, el ) { // Support: Firefox <=43 - 45 // Disconnected elements can have computed display: none, so first confirm that elem is // in the document. - jQuery.contains( elem.ownerDocument, elem ) && + isAttached( elem ) && jQuery.css( elem, "display" ) === "none"; }; -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, - scale = 1, + var adjusted, scale, maxIterations = 20, currentValue = tween ? function() { @@ -4548,35 +4749,39 @@ function adjustCSS( elem, prop, valueParts, tween ) { unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + // Trust units reported by jQuery.css unit = unit || initialInUnit[ 3 ]; - // Make sure we update the tween properties later on - valueParts = valueParts || []; - // Iteratively approximate from a nonzero starting point initialInUnit = +initial || 1; - do { - - // If previous iteration zeroed out, double until we get *something*. - // Use string for doubling so we don't accidentally see scale as unchanged below - scale = scale || ".5"; + while ( maxIterations-- ) { - // Adjust and apply - initialInUnit = initialInUnit / scale; + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; - // Update scale, tolerating zero or NaN from tween.cur() - // Break the loop if scale is unchanged or perfect, or if we've just had enough. - } while ( - scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations - ); + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; } if ( valueParts ) { @@ -4692,17 +4897,46 @@ jQuery.fn.extend( { } ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); -var rscriptType = ( /^$|\/(?:java|ecma)script/i ); +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); -// We have to close these tags to support XHTML (#13200) -var wrapMap = { +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; // Support: IE <=9 only - option: [ 1, "" ], + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten @@ -4715,12 +4949,14 @@ var wrapMap = { _default: [ 0, "", "" ] }; -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + function getAll( context, tag ) { @@ -4764,7 +5000,7 @@ function setGlobalEval( elems, refElements ) { var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, + var elem, tmp, tag, wrap, attached, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, @@ -4776,7 +5012,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) { if ( elem || elem === 0 ) { // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { + if ( toType( elem ) === "object" ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit @@ -4828,13 +5064,13 @@ function buildFragment( elems, context, scripts, selection, ignored ) { continue; } - contains = jQuery.contains( elem.ownerDocument, elem ); + attached = isAttached( elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history - if ( contains ) { + if ( attached ) { setGlobalEval( tmp ); } @@ -4853,34 +5089,6 @@ function buildFragment( elems, context, scripts, selection, ignored ) { } -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, @@ -4894,8 +5102,19 @@ function returnFalse() { return false; } +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + // Support: IE <=9 only -// See #13393 for more info +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 function safeActiveElement() { try { return document.activeElement; @@ -4978,8 +5197,8 @@ jQuery.event = { special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { return; } @@ -5003,7 +5222,7 @@ jQuery.event = { // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { - events = elemData.events = {}; + events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { @@ -5161,12 +5380,15 @@ jQuery.event = { dispatch: function( nativeEvent ) { - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event @@ -5195,9 +5417,10 @@ jQuery.event = { while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; @@ -5286,7 +5509,7 @@ jQuery.event = { enumerable: true, configurable: true, - get: jQuery.isFunction( hook ) ? + get: isFunction( hook ) ? function() { if ( this.originalEvent ) { return hook( this.originalEvent ); @@ -5321,39 +5544,51 @@ jQuery.event = { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, - focus: { + click: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); } + + // Return false to allow normal processing in the caller + return false; }, - delegateType: "focusout" - }, - click: { + trigger: function( data ) { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); } + + // Return non-false to allow normal event-path propagation + return true; }, - // For cross-browser consistency, don't fire native .click() on links + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack _default: function( event ) { - return nodeName( event.target, "a" ); + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); } }, @@ -5367,8 +5602,95 @@ jQuery.event = { } } } - } -}; + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} jQuery.removeEvent = function( elem, type, handle ) { @@ -5421,7 +5743,7 @@ jQuery.Event = function( src, props ) { } // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); + this.timeStamp = src && src.timeStamp || Date.now(); // Mark it as fixed this[ jQuery.expando ] = true; @@ -5482,6 +5804,7 @@ jQuery.each( { shiftKey: true, view: true, "char": true, + code: true, charCode: true, key: true, keyCode: true, @@ -5528,6 +5851,33 @@ jQuery.each( { } }, jQuery.event.addProp ); +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout @@ -5613,21 +5963,13 @@ jQuery.fn.extend( { var - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 + // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ rnoInnerhtml = /\s*$/g; // Prefer a tbody over its parent table for containing new rows @@ -5635,7 +5977,7 @@ function manipulationTarget( elem, content ) { if ( nodeName( elem, "table" ) && nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - return jQuery( ">tbody", elem )[ 0 ] || elem; + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; } return elem; @@ -5647,10 +5989,8 @@ function disableScript( elem ) { return elem; } function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); } else { elem.removeAttribute( "type" ); } @@ -5659,7 +5999,7 @@ function restoreScript( elem ) { } function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + var i, l, type, pdataOld, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; @@ -5667,13 +6007,11 @@ function cloneCopyEvent( src, dest ) { // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); + pdataOld = dataPriv.get( src ); events = pdataOld.events; if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; + dataPriv.remove( dest, "handle events" ); for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { @@ -5709,22 +6047,22 @@ function fixInput( src, dest ) { function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays - args = concat.apply( [], args ); + args = flat( args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, l = collection.length, iNoClone = l - 1, value = args[ 0 ], - isFunction = jQuery.isFunction( value ); + valueIsFunction = isFunction( value ); // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || + if ( valueIsFunction || ( l > 1 && typeof value === "string" && !support.checkClone && rchecked.test( value ) ) ) { return collection.each( function( index ) { var self = collection.eq( index ); - if ( isFunction ) { + if ( valueIsFunction ) { args[ 0 ] = value.call( this, index, self.html() ); } domManip( self, args, callback, ignored ); @@ -5778,14 +6116,16 @@ function domManip( collection, args, callback, ignored ) { !dataPriv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - if ( node.src ) { + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); } } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } } @@ -5807,7 +6147,7 @@ function remove( elem, selector, keepData ) { } if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + if ( keepData && isAttached( node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); @@ -5819,13 +6159,13 @@ function remove( elem, selector, keepData ) { jQuery.extend( { htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); + return html; }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); + inPage = isAttached( elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && @@ -6065,8 +6405,6 @@ jQuery.each( { return this.pushStack( ret ); }; } ); -var rmargin = ( /^margin/ ); - var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var getStyles = function( elem ) { @@ -6083,6 +6421,29 @@ var getStyles = function( elem ) { return view.getComputedStyle( elem ); }; +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + ( function() { @@ -6096,25 +6457,35 @@ var getStyles = function( elem ) { return; } + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; div.style.cssText = - "box-sizing:border-box;" + - "position:relative;display:block;" + + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + "margin:auto;border:1px;padding:1px;" + - "top:1%;width:50%"; - div.innerHTML = ""; - documentElement.appendChild( container ); + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); var divStyle = window.getComputedStyle( div ); pixelPositionVal = divStyle.top !== "1%"; // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = divStyle.marginLeft === "2px"; - boxSizingReliableVal = divStyle.width === "4px"; + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - // Support: Android 4.0 - 4.3 only + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 // Some styles come back with percentage values, even though they shouldn't - div.style.marginRight = "50%"; - pixelMarginRightVal = divStyle.marginRight === "4px"; + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; documentElement.removeChild( container ); @@ -6123,7 +6494,12 @@ var getStyles = function( elem ) { div = null; } - var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, container = document.createElement( "div" ), div = document.createElement( "div" ); @@ -6138,26 +6514,55 @@ var getStyles = function( elem ) { div.cloneNode( true ).style.backgroundClip = ""; support.clearCloneStyle = div.style.backgroundClip === "content-box"; - container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + - "padding:0;margin-top:1px;position:absolute"; - container.appendChild( div ); - jQuery.extend( support, { - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, boxSizingReliable: function() { computeStyleTests(); return boxSizingReliableVal; }, - pixelMarginRight: function() { + pixelBoxStyles: function() { computeStyleTests(); - return pixelMarginRightVal; + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; }, reliableMarginLeft: function() { computeStyleTests(); return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; } } ); } )(); @@ -6180,7 +6585,7 @@ function curCSS( elem, name, computed ) { if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + if ( ret === "" && !isAttached( elem ) ) { ret = jQuery.style( elem, name ); } @@ -6189,7 +6594,7 @@ function curCSS( elem, name, computed ) { // but width seems to be reliably pixels. // This is against the CSSOM draft spec: // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { // Remember the original values width = style.width; @@ -6236,30 +6641,13 @@ function addGetHookIf( conditionFn, hookFn ) { } -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; -// Return a css property mapped to a potentially vendor prefixed property +// Return a vendor-prefixed property or undefined function vendorPropName( name ) { - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; @@ -6272,17 +6660,34 @@ function vendorPropName( name ) { } } -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; } - return ret; + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; } -function setPositiveNumber( elem, value, subtract ) { + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point @@ -6294,87 +6699,146 @@ function setPositiveNumber( elem, value, subtract ) { value; } -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i, - val = 0; - - // If we already have the right measurement, avoid augmentation - if ( extra === ( isBorderBox ? "border" : "content" ) ) { - i = 4; +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; - // Otherwise initialize for horizontal or vertical properties - } else { - i = name === "width" ? 1 : 0; + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; } for ( ; i < 4; i += 2 ) { - // Both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } - if ( isBorderBox ) { + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - // At this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" } else { - // At this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } - // At this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } - return val; + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; } -function getWidthOrHeight( elem, name, extra ) { +function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style - var valueIsBorderBox, - styles = getStyles( elem ), - val = curCSS( elem, name, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, - // Computed unit is not pixels. Stop here and return. + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. if ( rnumnonpx.test( val ) ) { - return val; + if ( !extra ) { + return val; + } + val = "auto"; } - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && - ( support.boxSizingReliable() || val === elem.style[ name ] ); - // Fall back to offsetWidth/Height when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - if ( val === "auto" ) { - val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ]; + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } } - // Normalize "", auto, and prepare for extra + // Normalize "" and auto val = parseFloat( val ) || 0; - // Use the active box-sizing model to add/subtract irrelevant styles + // Adjust for the element's box model return ( val + - augmentWidthOrHeight( + boxModelAdjustment( elem, - name, + dimension, extra || ( isBorderBox ? "border" : "content" ), valueIsBorderBox, - styles + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val ) ) + "px"; } @@ -6404,6 +6868,13 @@ jQuery.extend( { "flexGrow": true, "flexShrink": true, "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, "lineHeight": true, "opacity": true, "order": true, @@ -6415,9 +6886,7 @@ jQuery.extend( { // Add in properties whose names you wish to fix before // setting or getting the value - cssProps: { - "float": "cssFloat" - }, + cssProps: {}, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { @@ -6429,7 +6898,7 @@ jQuery.extend( { // Make sure that we're working with the right name var ret, type, hooks, - origName = jQuery.camelCase( name ), + origName = camelCase( name ), isCustomProp = rcustomProp.test( name ), style = elem.style; @@ -6461,7 +6930,9 @@ jQuery.extend( { } // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } @@ -6497,7 +6968,7 @@ jQuery.extend( { css: function( elem, name, extra, styles ) { var val, num, hooks, - origName = jQuery.camelCase( name ), + origName = camelCase( name ), isCustomProp = rcustomProp.test( name ); // Make sure that we're working with the right name. We don't @@ -6535,8 +7006,8 @@ jQuery.extend( { } } ); -jQuery.each( [ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { @@ -6552,29 +7023,52 @@ jQuery.each( [ "height", "width" ], function( i, name ) { // in IE throws an error. ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); + return getWidthOrHeight( elem, dimension, extra ); } ) : - getWidthOrHeight( elem, name, extra ); + getWidthOrHeight( elem, dimension, extra ); } }, set: function( elem, value, extra ) { var matches, - styles = extra && getStyles( elem ), - subtract = extra && augmentWidthOrHeight( - elem, - name, - extra, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 ); + } // Convert to pixels if value adjustment is needed if ( subtract && ( matches = rcssNum.exec( value ) ) && ( matches[ 3 ] || "px" ) !== "px" ) { - elem.style[ name ] = value; - value = jQuery.css( elem, name ); + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); } return setPositiveNumber( elem, value, subtract ); @@ -6618,7 +7112,7 @@ jQuery.each( { } }; - if ( !rmargin.test( prefix ) ) { + if ( prefix !== "margin" ) { jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; } } ); @@ -6728,9 +7222,9 @@ Tween.propHooks = { // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; @@ -6789,7 +7283,7 @@ function createFxNow() { window.setTimeout( function() { fxNow = undefined; } ); - return ( fxNow = jQuery.now() ); + return ( fxNow = Date.now() ); } // Generate parameters to create a standard animation @@ -6893,9 +7387,10 @@ function defaultPrefilter( elem, props, opts ) { // Restrict "overflow" and "display" styles during box animations if ( isBox && elem.nodeType === 1 ) { - // Support: IE <=9 - 11, Edge 12 - 13 + // Support: IE <=9 - 11, Edge 12 - 15 // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; // Identify a display type, preferring old show/hide data over the CSS cascade @@ -7003,7 +7498,7 @@ function propFilter( props, specialEasing ) { // camelCase, specialEasing and expand cssHook pass for ( index in props ) { - name = jQuery.camelCase( index ); + name = camelCase( index ); easing = specialEasing[ name ]; value = props[ index ]; if ( Array.isArray( value ) ) { @@ -7128,9 +7623,9 @@ function Animation( elem, properties, options ) { for ( ; index < length; index++ ) { result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); if ( result ) { - if ( jQuery.isFunction( result.stop ) ) { + if ( isFunction( result.stop ) ) { jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - jQuery.proxy( result.stop, result ); + result.stop.bind( result ); } return result; } @@ -7138,7 +7633,7 @@ function Animation( elem, properties, options ) { jQuery.map( props, createTween, animation ); - if ( jQuery.isFunction( animation.opts.start ) ) { + if ( isFunction( animation.opts.start ) ) { animation.opts.start.call( elem, animation ); } @@ -7171,7 +7666,7 @@ jQuery.Animation = jQuery.extend( Animation, { }, tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { + if ( isFunction( props ) ) { callback = props; props = [ "*" ]; } else { @@ -7203,9 +7698,9 @@ jQuery.Animation = jQuery.extend( Animation, { jQuery.speed = function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, + isFunction( speed ) && speed, duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + easing: fn && easing || easing && !isFunction( easing ) && easing }; // Go to the end state if fx are off @@ -7232,7 +7727,7 @@ jQuery.speed = function( speed, easing, fn ) { opt.old = opt.complete; opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { + if ( isFunction( opt.old ) ) { opt.old.call( this ); } @@ -7284,7 +7779,7 @@ jQuery.fn.extend( { clearQueue = type; type = undefined; } - if ( clearQueue && type !== false ) { + if ( clearQueue ) { this.queue( type || "fx", [] ); } @@ -7367,7 +7862,7 @@ jQuery.fn.extend( { } } ); -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? @@ -7396,7 +7891,7 @@ jQuery.fx.tick = function() { i = 0, timers = jQuery.timers; - fxNow = jQuery.now(); + fxNow = Date.now(); for ( ; i < timers.length; i++ ) { timer = timers[ i ]; @@ -7588,7 +8083,7 @@ boolHook = { } }; -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { @@ -7749,7 +8244,7 @@ jQuery.each( [ // Strip and collapse whitespace according to HTML spec - // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace function stripAndCollapse( value ) { var tokens = value.match( rnothtmlwhite ) || []; return tokens.join( " " ); @@ -7760,20 +8255,30 @@ function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; } +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + jQuery.fn.extend( { addClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; - if ( jQuery.isFunction( value ) ) { + if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); } ); } - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; + classes = classesToArray( value ); + if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); @@ -7802,7 +8307,7 @@ jQuery.fn.extend( { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; - if ( jQuery.isFunction( value ) ) { + if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); } ); @@ -7812,9 +8317,9 @@ jQuery.fn.extend( { return this.attr( "class", "" ); } - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; + classes = classesToArray( value ); + if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); @@ -7844,13 +8349,14 @@ jQuery.fn.extend( { }, toggleClass: function( value, stateVal ) { - var type = typeof value; + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); - if ( typeof stateVal === "boolean" && type === "string" ) { + if ( typeof stateVal === "boolean" && isValidValue ) { return stateVal ? this.addClass( value ) : this.removeClass( value ); } - if ( jQuery.isFunction( value ) ) { + if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( value.call( this, i, getClass( this ), stateVal ), @@ -7862,12 +8368,12 @@ jQuery.fn.extend( { return this.each( function() { var className, i, self, classNames; - if ( type === "string" ) { + if ( isValidValue ) { // Toggle individual class names i = 0; self = jQuery( this ); - classNames = value.match( rnothtmlwhite ) || []; + classNames = classesToArray( value ); while ( ( className = classNames[ i++ ] ) ) { @@ -7926,7 +8432,7 @@ var rreturn = /\r/g; jQuery.fn.extend( { val: function( value ) { - var hooks, ret, isFunction, + var hooks, ret, valueIsFunction, elem = this[ 0 ]; if ( !arguments.length ) { @@ -7955,7 +8461,7 @@ jQuery.fn.extend( { return; } - isFunction = jQuery.isFunction( value ); + valueIsFunction = isFunction( value ); return this.each( function( i ) { var val; @@ -7964,7 +8470,7 @@ jQuery.fn.extend( { return; } - if ( isFunction ) { + if ( valueIsFunction ) { val = value.call( this, i, jQuery( this ).val() ); } else { val = value; @@ -8106,18 +8612,24 @@ jQuery.each( [ "radio", "checkbox" ], function() { // Return jQuery for attributes-only inclusion -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; jQuery.extend( jQuery.event, { trigger: function( event, data, elem, onlyHandlers ) { - var i, cur, tmp, bubbleType, ontype, handle, special, + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, eventPath = [ elem || document ], type = hasOwn.call( event, "type" ) ? event.type : event, namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - cur = tmp = elem = elem || document; + cur = lastElement = tmp = elem = elem || document; // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { @@ -8169,7 +8681,7 @@ jQuery.extend( jQuery.event, { // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { @@ -8189,13 +8701,15 @@ jQuery.extend( jQuery.event, { // Fire handlers on the event path i = 0; while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - + lastElement = cur; event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); @@ -8221,7 +8735,7 @@ jQuery.extend( jQuery.event, { // Call a native DOM method on the target with the same name as the event. // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method tmp = elem[ ontype ]; @@ -8232,7 +8746,17 @@ jQuery.extend( jQuery.event, { // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + jQuery.event.triggered = undefined; if ( tmp ) { @@ -8278,31 +8802,6 @@ jQuery.fn.extend( { } ); -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - - - - -support.focusin = "onfocusin" in window; - - // Support: Firefox <=44 // Firefox doesn't have focus(in | out) events // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 @@ -8321,7 +8820,10 @@ if ( !support.focusin ) { jQuery.event.special[ fix ] = { setup: function() { - var doc = this.ownerDocument || this, + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { @@ -8330,7 +8832,7 @@ if ( !support.focusin ) { dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { - var doc = this.ownerDocument || this, + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { @@ -8346,7 +8848,7 @@ if ( !support.focusin ) { } var location = window.location; -var nonce = jQuery.now(); +var nonce = { guid: Date.now() }; var rquery = ( /\?/ ); @@ -8404,7 +8906,7 @@ function buildParams( prefix, obj, traditional, add ) { } } ); - } else if ( !traditional && jQuery.type( obj ) === "object" ) { + } else if ( !traditional && toType( obj ) === "object" ) { // Serialize object item. for ( name in obj ) { @@ -8426,7 +8928,7 @@ jQuery.param = function( a, traditional ) { add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value - var value = jQuery.isFunction( valueOrFunction ) ? + var value = isFunction( valueOrFunction ) ? valueOrFunction() : valueOrFunction; @@ -8434,6 +8936,10 @@ jQuery.param = function( a, traditional ) { encodeURIComponent( value == null ? "" : value ); }; + if ( a == null ) { + return ""; + } + // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { @@ -8474,7 +8980,7 @@ jQuery.fn.extend( { rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && ( this.checked || !rcheckableType.test( type ) ); } ) - .map( function( i, elem ) { + .map( function( _i, elem ) { var val = jQuery( this ).val(); if ( val == null ) { @@ -8544,7 +9050,7 @@ function addToPrefiltersOrTransports( structure ) { i = 0, dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - if ( jQuery.isFunction( func ) ) { + if ( isFunction( func ) ) { // For each dataType in the dataTypeExpression while ( ( dataType = dataTypes[ i++ ] ) ) { @@ -8936,12 +9442,14 @@ jQuery.extend( { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); } } - match = responseHeaders[ key.toLowerCase() ]; + match = responseHeaders[ key.toLowerCase() + " " ]; } - return match == null ? null : match; + return match == null ? null : match.join( ", " ); }, // Raw string @@ -9016,7 +9524,7 @@ jQuery.extend( { if ( s.crossDomain == null ) { urlAnchor = document.createElement( "a" ); - // Support: IE <=8 - 11, Edge 12 - 13 + // Support: IE <=8 - 11, Edge 12 - 15 // IE throws exception on accessing the href property if url is malformed, // e.g. http://example.com:80x/ try { @@ -9074,8 +9582,8 @@ jQuery.extend( { // Remember the hash so we can put it back uncached = s.url.slice( cacheURL.length ); - // If data is available, append data to url - if ( s.data ) { + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry @@ -9085,7 +9593,8 @@ jQuery.extend( { // Add or update anti-cache param if needed if ( s.cache === false ) { cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; } // Put hash and anti-cache on the URL that will be requested (gh-1732) @@ -9218,6 +9727,11 @@ jQuery.extend( { response = ajaxHandleResponses( s, jqXHR, responses ); } + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); @@ -9308,11 +9822,11 @@ jQuery.extend( { } } ); -jQuery.each( [ "get", "post" ], function( i, method ) { +jQuery.each( [ "get", "post" ], function( _i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { + if ( isFunction( data ) ) { type = type || callback; callback = data; data = undefined; @@ -9329,8 +9843,17 @@ jQuery.each( [ "get", "post" ], function( i, method ) { }; } ); +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + -jQuery._evalUrl = function( url ) { +jQuery._evalUrl = function( url, options, doc ) { return jQuery.ajax( { url: url, @@ -9340,7 +9863,16 @@ jQuery._evalUrl = function( url ) { cache: true, async: false, global: false, - "throws": true + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } } ); }; @@ -9350,7 +9882,7 @@ jQuery.fn.extend( { var wrap; if ( this[ 0 ] ) { - if ( jQuery.isFunction( html ) ) { + if ( isFunction( html ) ) { html = html.call( this[ 0 ] ); } @@ -9376,7 +9908,7 @@ jQuery.fn.extend( { }, wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { + if ( isFunction( html ) ) { return this.each( function( i ) { jQuery( this ).wrapInner( html.call( this, i ) ); } ); @@ -9396,10 +9928,10 @@ jQuery.fn.extend( { }, wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); + var htmlIsFunction = isFunction( html ); return this.each( function( i ) { - jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); } ); }, @@ -9491,7 +10023,8 @@ jQuery.ajaxTransport( function( options ) { return function() { if ( callback ) { callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; if ( type === "abort" ) { xhr.abort(); @@ -9531,7 +10064,7 @@ jQuery.ajaxTransport( function( options ) { // Listen to events xhr.onload = callback(); - errorCallback = xhr.onerror = callback( "error" ); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); // Support: IE 9 only // Use onreadystatechange to replace onabort @@ -9622,24 +10155,21 @@ jQuery.ajaxPrefilter( "script", function( s ) { // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { - // This transport only deals with cross domain requests - if ( s.crossDomain ) { + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { var script, callback; return { send: function( _, complete ) { - script = jQuery( "'; + $repetitions = 300; + $text = $text . str_repeat($comment, $repetitions); + + $onlyTextReadingTimeInfo = $this->refinery->string()->estimatedReadingTime(); + $this->assertEquals( + 1, + $onlyTextReadingTimeInfo->transform($text) + ); + } } diff --git a/tests/UI/Client/Counter/CounterTest.html b/tests/UI/Client/Counter/CounterTest.html index 5bd3572d60fd..c6d9ce6e433a 100644 --- a/tests/UI/Client/Counter/CounterTest.html +++ b/tests/UI/Client/Counter/CounterTest.html @@ -1,3 +1,7 @@ +
      + +
      +
      diff --git a/tests/UI/Client/Counter/CounterTest.js b/tests/UI/Client/Counter/CounterTest.js index 8a4a9ab407a7..426b7a95e70f 100644 --- a/tests/UI/Client/Counter/CounterTest.js +++ b/tests/UI/Client/Counter/CounterTest.js @@ -1,6 +1,12 @@ var CounterTests = { html: "Counter/CounterTest.html", + testGetCounterOrNullOnEmpty(){ + return il.UI.counter.getCounterObjectOrNull($("#testEmpty")) === null; + }, + testGetCounterOrNullOnTest1(){ + return il.UI.counter.getCounterObjectOrNull($("#test1")).getStatusCount()==1; + }, testInvalidThrowsExceptionA: function(){ try{ var counter = il.UI.counter.getCounterObject("invalid"); @@ -8,7 +14,7 @@ var CounterTests = { return true; } }, - testInvalidThrowsExceptionA: function(){ + testInvalidThrowsExceptionB: function(){ try{ var counter = il.UI.counter.getCounterObject("#test1"); }catch(e){ diff --git a/tests/UI/Component/Card/CardTest.php b/tests/UI/Component/Card/CardTest.php index 64b224d2674c..09f45093a0cc 100644 --- a/tests/UI/Component/Card/CardTest.php +++ b/tests/UI/Component/Card/CardTest.php @@ -125,7 +125,7 @@ public function test_render_content_full() " \"alt\"" . "
      " . "
      " . - "
      Card Title
      " . + "
      Card Title
      " . "
      " . "
      Random Content
      " . "
      "; @@ -146,7 +146,7 @@ public function test_render_content_with_highlight() " \"alt\"" . "
      " . "
      " . - "
      Card Title
      " . + "
      Card Title
      " . "
      " . ""; diff --git a/tests/UI/Component/Card/RepositoryObjectTest.php b/tests/UI/Component/Card/RepositoryObjectTest.php index e856e4ceb5af..1a987d13784c 100644 --- a/tests/UI/Component/Card/RepositoryObjectTest.php +++ b/tests/UI/Component/Card/RepositoryObjectTest.php @@ -148,7 +148,7 @@ public function test_render_with_object_icon() alt
      -
      Card Title
      +
      Card Title
      @@ -188,7 +188,7 @@ public function test_render_with_certificate_icon() alt
      -
      Card Title
      +
      Card Title
      @@ -234,7 +234,7 @@ public function test_render_with_progressmeter() alt
      -
      Card Title
      +
      Card Title
      @@ -278,7 +278,7 @@ public function test_render_with_actions() alt
      -
      Card Title
      +
      Card Title
      diff --git a/tests/UI/Component/Deck/DeckTest.php b/tests/UI/Component/Deck/DeckTest.php index 0315963b58fe..1c6a366dc8c6 100644 --- a/tests/UI/Component/Deck/DeckTest.php +++ b/tests/UI/Component/Deck/DeckTest.php @@ -109,17 +109,17 @@ public function test_render_content() $expected_html = '
      -
      Card Title
      -
      Card Title
      -
      Card Title
      +
      Card Title
      +
      Card Title
      +
      Card Title
      -
      Card Title
      -
      Card Title
      -
      Card Title
      +
      Card Title
      +
      Card Title
      +
      Card Title
      -
      Card Title
      +
      Card Title
      '; diff --git a/tests/UI/Component/Divider/DividerTest.php b/tests/UI/Component/Divider/DividerTest.php index bb1727d8807b..be1aedb4896e 100644 --- a/tests/UI/Component/Divider/DividerTest.php +++ b/tests/UI/Component/Divider/DividerTest.php @@ -55,7 +55,7 @@ public function test_render_horizontal_with_label() $c = $f->horizontal()->withLabel("label"); $html = trim($r->render($c)); - $expected_html = '
      label
      '; + $expected_html = '

      label

      '; $this->assertHTMLEquals("
      " . $expected_html . "
      ", "
      " . $html . "
      "); } diff --git a/tests/UI/Component/Input/Field/NumericInputTest.php b/tests/UI/Component/Input/Field/NumericInputTest.php index b3d020d635e5..f85be36563e0 100644 --- a/tests/UI/Component/Input/Field/NumericInputTest.php +++ b/tests/UI/Component/Input/Field/NumericInputTest.php @@ -22,7 +22,7 @@ public function setUp() : void protected function buildFactory() { $df = new Data\Factory(); - $language = $this->createMock(\ilLanguage::class); + $language = $this->getLanguage(); return new ILIAS\UI\Implementation\Component\Input\Field\Factory( new SignalGenerator(), $df, @@ -132,4 +132,54 @@ public function test_render_disabled() . " " . " " . " " . ""; $this->assertEquals($expected, $html); } + + public function testNullValue() + { + $f = $this->buildFactory(); + $post_data = new DefInputData(['name_0' => null]); + $field = $f->numeric('')->withNameFrom($this->name_source); + $field_required = $field->withRequired(true); + + $value = $field->withInput($post_data)->getContent(); + $this->assertTrue($value->isOk()); + $this->assertNull($value->value()); + + $value = $field_required->withInput($post_data)->getContent(); + $this->assertTrue($value->isError()); + return $field; + } + + /** + * @depends testNullValue + */ + public function testEmptyValue($field) + { + $post_data = new DefInputData(['name_0' => '']); + $field_required = $field->withRequired(true); + + $value = $field->withInput($post_data)->getContent(); + $this->assertTrue($value->isOk()); + $this->assertNull($value->value()); + + $field_required = $field_required->withInput($post_data); + $value = $field_required->getContent(); + $this->assertTrue($value->isError()); + } + + /** + * @depends testNullValue + */ + public function testZeroIsValidValue($field) + { + $post_data = new DefInputData(['name_0' => 0]); + $field_required = $field->withRequired(true); + + $value = $field->withInput($post_data)->getContent(); + $this->assertTrue($value->isOk()); + $this->assertEquals(0, $value->value()); + + $value = $field_required->withInput($post_data)->getContent(); + $this->assertTrue($value->isOK()); + $this->assertEquals(0, $value->value()); + } } diff --git a/tests/UI/Component/Input/Field/RadioInputTest.php b/tests/UI/Component/Input/Field/RadioInputTest.php index bce6288814d6..49b9e3b7dda2 100644 --- a/tests/UI/Component/Input/Field/RadioInputTest.php +++ b/tests/UI/Component/Input/Field/RadioInputTest.php @@ -37,7 +37,7 @@ protected function buildRadio() : Radio $byline = "byline"; $radio = $f->radio($label, $byline) ->withOption('value0', 'label0', 'byline0') - ->withOption('value1', 'label1', 'byline1') + ->withOption('1', 'label1', 'byline1') ->withNameFrom($this->name_source); return $radio; } @@ -93,7 +93,7 @@ public function test_render_value() $label = $radio->getLabel(); $byline = $radio->getByline(); $options = $radio->getOptions(); - $value = array_keys($options)[0]; + $value = '1'; $radio = $radio->withValue($value); $expected = "" . "
      " @@ -103,7 +103,7 @@ public function test_render_value() foreach ($options as $opt_value => $opt_label) { $expected .= "
      "; - if ($opt_value === $value) { + if ($opt_value == $value) { $expected .= ""; } else { $expected .= ""; @@ -124,6 +124,7 @@ public function test_render_value() } + public function test_render_disabled() { $r = $this->getDefaultRenderer(); diff --git a/tests/UI/Component/Input/Field/SwitchableGroupInputTest.php b/tests/UI/Component/Input/Field/SwitchableGroupInputTest.php index 5d0d6c6c8524..84aa9df583a5 100644 --- a/tests/UI/Component/Input/Field/SwitchableGroupInputTest.php +++ b/tests/UI/Component/Input/Field/SwitchableGroupInputTest.php @@ -472,7 +472,7 @@ public function testRenderWithValueByIndex() $sg = $f->switchableGroup([$group1,$group2], $label, $byline); $r = $this->getDefaultRenderer(); - $html = $r->render($sg->withValue(1)); + $html = $r->render($sg->withValue('1')); $expected = << diff --git a/tests/UI/Component/Item/ItemGroupTest.php b/tests/UI/Component/Item/ItemGroupTest.php index d00a6af91021..d7b712fe94b2 100644 --- a/tests/UI/Component/Item/ItemGroupTest.php +++ b/tests/UI/Component/Item/ItemGroupTest.php @@ -93,17 +93,20 @@ public function test_render_base() $expected = << -

      group

      +

      group

      -
      title1
      + title1
      -
      title2
      + title2
      EOT; - $this->assertHTMLEquals($expected, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($html) + ); } public function test_render_with_actions() @@ -126,7 +129,7 @@ public function test_render_with_actions() $expected = << -

      group

      EOT; - $this->assertHTMLEquals($expected, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($html) + ); } } diff --git a/tests/UI/Component/Item/ItemNotificationTest.php b/tests/UI/Component/Item/ItemNotificationTest.php index 52b5233fc79c..68afbb59b721 100644 --- a/tests/UI/Component/Item/ItemNotificationTest.php +++ b/tests/UI/Component/Item/ItemNotificationTest.php @@ -206,9 +206,9 @@ public function getOnLoadCodeAsync()
      -
      +

      TestLink -

      +
      -
      title_aggregate
      +

      title_aggregate

      diff --git a/tests/UI/Component/Item/ItemTest.php b/tests/UI/Component/Item/ItemTest.php index e6e6c3f3ccee..da6f57c07784 100644 --- a/tests/UI/Component/Item/ItemTest.php +++ b/tests/UI/Component/Item/ItemTest.php @@ -142,7 +142,7 @@ public function test_render_base() $expected = << -
      Item Title
      + Item Title EOT; - $this->assertHTMLEquals($expected, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($html) + ); } public function test_render_lead_image() @@ -204,13 +207,16 @@ public function test_render_lead_image() str
      -
      title
      + title
      EOT; - $this->assertHTMLEquals($expected, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($html) + ); } public function test_render_lead_icon() @@ -229,13 +235,16 @@ public function test_render_lead_icon()
      -
      title
      + title
      EOT; - $this->assertHTMLEquals($expected, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($html) + ); } public function test_render_lead_text_and_color() @@ -254,16 +263,19 @@ public function test_render_lead_text_and_color()
      -
      lead
      + lead
      -
      title
      + title
      EOT; - $this->assertHTMLEquals($expected, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($html) + ); } public function test_shy_title_and_property() @@ -280,8 +292,8 @@ public function test_shy_title_and_property() $html = $r->render($c); $expected = << -
      -
      + +
      @@ -312,7 +324,7 @@ public function test_link_title() $html = $r->render($c); $expected = <<
      ILIAS
      + EOT; $this->assertHTMLEquals($expected, $html); diff --git a/tests/UI/Component/MainControls/Slate/CombinedSlateTest.php b/tests/UI/Component/MainControls/Slate/CombinedSlateTest.php index 7b25868ac226..f114c3536394 100644 --- a/tests/UI/Component/MainControls/Slate/CombinedSlateTest.php +++ b/tests/UI/Component/MainControls/Slate/CombinedSlateTest.php @@ -108,7 +108,7 @@ public function testRenderingWithSubDivider()

      -
      Title
      +

      Title


      EOT; $this->assertEquals( diff --git a/tests/UI/Component/MainControls/Slate/NotificationSlateTest.php b/tests/UI/Component/MainControls/Slate/NotificationSlateTest.php index 0adf7af38f5c..3f84d1aaa3e8 100644 --- a/tests/UI/Component/MainControls/Slate/NotificationSlateTest.php +++ b/tests/UI/Component/MainControls/Slate/NotificationSlateTest.php @@ -112,7 +112,7 @@ public function testRenderingWithSubslateAndButton()
      -
      item title
      +

      item title

      diff --git a/tests/UI/Component/Panel/PanelListingTest.php b/tests/UI/Component/Panel/PanelListingTest.php index e881d88af8da..4814e840b4db 100644 --- a/tests/UI/Component/Panel/PanelListingTest.php +++ b/tests/UI/Component/Panel/PanelListingTest.php @@ -97,27 +97,30 @@ public function test_render_base() $expected = << -

      title

      +

      title

      -

      Subtitle 1

      +

      Subtitle 1

      -
      title1
      + title1
      -
      title2
      + title2
      -

      Subtitle 2

      +

      Subtitle 2

      -
      title3
      + title3
      EOT; - $this->assertHTMLEquals($expected, $html); + $this->assertHTMLEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($html) + ); } public function test_render_with_actions() @@ -139,7 +142,7 @@ public function test_render_with_actions() $expected = << -

      title

-
{TXT_TERM}
+

{TXT_TERM}

{TXT_DEFINITION}
diff --git a/Modules/Glossary/templates/default/tpl.glossary_definition_list.html b/Modules/Glossary/templates/default/tpl.glossary_definition_list.html index 160018e60d1a..8181d7d58db6 100755 --- a/Modules/Glossary/templates/default/tpl.glossary_definition_list.html +++ b/Modules/Glossary/templates/default/tpl.glossary_definition_list.html @@ -1,5 +1,5 @@
-

{TXT_TERM}

+

{TXT_TERM}

diff --git a/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDataSet.php b/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDataSet.php index d1b3aa13c1e7..9592b42f5c1f 100644 --- a/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDataSet.php +++ b/Modules/IndividualAssessment/classes/class.ilIndividualAssessmentDataSet.php @@ -111,10 +111,10 @@ protected function _readData($entity, $ids) 'id' => $iass_id, 'title' => $obj->getTitle(), 'description' => $obj->getDescription(), - 'content' => $settings->content(), - 'recordTemplate' => $settings->recordTemplate(), - 'eventTimePlaceRequired' => (int) $settings->eventTimePlaceRequired(), - 'file_required' => (int) $settings->fileRequired(), + 'content' => $settings->getContent(), + 'recordTemplate' => $settings->getRecordTemplate(), + 'eventTimePlaceRequired' => (int) $settings->isEventTimePlaceRequired(), + 'file_required' => (int) $settings->isFileRequired(), "contact" => $info->getContact(), "responsibility" => $info->getResponsibility(), "phone" => $info->getPhone(), @@ -170,7 +170,7 @@ public function importRecord($entity, $types, array $rec, ilImportMapping $mappi $rec['phone'], $rec['mails'], $rec['consultation_hours'] - ); + ); $newObj->setTitle($rec["title"]); $newObj->setDescription($rec["description"]); diff --git a/Modules/LearningModule/Presentation/classes/class.ilLMPresentationGUI.php b/Modules/LearningModule/Presentation/classes/class.ilLMPresentationGUI.php index dc0565c116b3..d8efc2c807d2 100755 --- a/Modules/LearningModule/Presentation/classes/class.ilLMPresentationGUI.php +++ b/Modules/LearningModule/Presentation/classes/class.ilLMPresentationGUI.php @@ -292,6 +292,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 diff --git a/Modules/LearningModule/classes/class.ilEditClipboard.php b/Modules/LearningModule/classes/class.ilEditClipboard.php index 7646afc757cb..defaa67aa204 100755 --- a/Modules/LearningModule/classes/class.ilEditClipboard.php +++ b/Modules/LearningModule/classes/class.ilEditClipboard.php @@ -37,8 +37,11 @@ class ilEditClipboard { public static function getContentObjectType() { - if (isset($_SESSION["ilEditClipboard"])) { - return $_SESSION["ilEditClipboard"]["type"]; + global $DIC; + $user = $DIC->user(); + $lm_type = $user->getPref("lm_clipboard_type"); + if ($lm_type != "") { + return $lm_type; } else { return false; } @@ -46,13 +49,18 @@ public static function getContentObjectType() public static function setAction($a_action) { - $_SESSION["ilEditClipboard"] = array("action" => $a_action); + global $DIC; + $user = $DIC->user(); + $user->writePref("lm_clipboard_action", $a_action); } public static function getAction() { - if (isset($_SESSION["ilEditClipboard"])) { - return $_SESSION["ilEditClipboard"]["action"]; + global $DIC; + $user = $DIC->user(); + $lm_action = $user->getPref("lm_clipboard_action"); + if ($lm_action != "") { + return $lm_action; } else { return false; } @@ -60,19 +68,32 @@ public static function getAction() public static function getContentObjectId() { - if (isset($_SESSION["ilEditClipboard"])) { - return $_SESSION["ilEditClipboard"]["id"]; + global $DIC; + $user = $DIC->user(); + $lm_id = $user->getPref("lm_clipboard_id"); + if ($lm_id != "") { + return $lm_id; } + return ""; } public static function storeContentObject($a_type, $a_id, $a_action = "cut") { - $_SESSION["ilEditClipboard"] = array("type" => $a_type, - "id" => $a_id, "action" => $a_action); + global $DIC; + $user = $DIC->user(); + $user->writePref("lm_clipboard_id", $a_id); + $user->writePref("lm_clipboard_type", $a_type); + self::setAction($a_action); } public static function clear() { - unset($_SESSION["ilEditClipboard"]); + global $DIC; + $user = $DIC->user(); + $user->clipboardDeleteObjectsOfType("pg"); + $user->clipboardDeleteObjectsOfType("st"); + $user->writePref("lm_clipboard_id", ""); + $user->writePref("lm_clipboard_type", ""); + $user->writePref("lm_clipboard_action", ""); } } diff --git a/Modules/LearningModule/classes/class.ilLMTracker.php b/Modules/LearningModule/classes/class.ilLMTracker.php index e8c5caa39f34..f197973b35b7 100644 --- a/Modules/LearningModule/classes/class.ilLMTracker.php +++ b/Modules/LearningModule/classes/class.ilLMTracker.php @@ -179,25 +179,17 @@ 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.ilLearningModuleNotification.php b/Modules/LearningModule/classes/class.ilLearningModuleNotification.php index f9d19b7d0014..40f3c87bc7a7 100644 --- a/Modules/LearningModule/classes/class.ilLearningModuleNotification.php +++ b/Modules/LearningModule/classes/class.ilLearningModuleNotification.php @@ -140,14 +140,13 @@ public function send() $mail_obj = new ilMail(ANONYMOUS_USER_ID); $mail_obj->appendInstallationSignature(true); - $mail_obj->sendMail( + $mail_obj->enqueue( ilObjUser::_lookupLogin($user_id), "", "", $subject, $message, - [], - false + [] ); } } diff --git a/Modules/LearningModule/templates/default/tpl.glossary_term_output.html b/Modules/LearningModule/templates/default/tpl.glossary_term_output.html index b8283d9b85a7..9317a3796487 100755 --- a/Modules/LearningModule/templates/default/tpl.glossary_term_output.html +++ b/Modules/LearningModule/templates/default/tpl.glossary_term_output.html @@ -23,7 +23,7 @@
-

{TXT_TERM}

+

{TXT_TERM}

{TXT_DEFINITION}
diff --git a/Modules/LearningSequence/classes/GlobalSettings/LSGlobalSettings.php b/Modules/LearningSequence/classes/GlobalSettings/LSGlobalSettings.php new file mode 100644 index 000000000000..02ead10a6b33 --- /dev/null +++ b/Modules/LearningSequence/classes/GlobalSettings/LSGlobalSettings.php @@ -0,0 +1,35 @@ +polling_interval_seconds = $polling_interval_seconds; + } + + public function getPollingIntervalSeconds() : float + { + return $this->polling_interval_seconds; + } + + public function getPollingIntervalMilliseconds() : int + { + $interval = $this->getPollingIntervalSeconds() * 1000; + return (int) $interval; + } + + public function withPollingIntervalSeconds(float $seconds) : LSGlobalSettings + { + $clone = clone $this; + $clone->polling_interval_seconds = $seconds; + return $clone; + } +} diff --git a/Modules/LearningSequence/classes/GlobalSettings/LSGlobalSettingsDB.php b/Modules/LearningSequence/classes/GlobalSettings/LSGlobalSettingsDB.php new file mode 100644 index 000000000000..bf75005d36aa --- /dev/null +++ b/Modules/LearningSequence/classes/GlobalSettings/LSGlobalSettingsDB.php @@ -0,0 +1,11 @@ +il_settings = $il_settings; + } + + public function getSettings() : LSGlobalSettings + { + $interval_seconds = (float) $this->il_settings->get( + self::SETTING_POLL_INTERVAL, + self::POLL_INTERVAL_DEFAULT + ); + + return new LSGlobalSettings($interval_seconds); + } + + public function storeSettings(LSGlobalSettings $settings) + { + $this->il_settings->set( + self::SETTING_POLL_INTERVAL, + $settings->getPollingIntervalSeconds() + ); + } +} diff --git a/Modules/LearningSequence/classes/Player/LSControlBuilder.php b/Modules/LearningSequence/classes/Player/LSControlBuilder.php index d593efde8858..5a830bbb5cb3 100644 --- a/Modules/LearningSequence/classes/Player/LSControlBuilder.php +++ b/Modules/LearningSequence/classes/Player/LSControlBuilder.php @@ -16,8 +16,6 @@ class LSControlBuilder implements ControlBuilder { const CMD_START_OBJECT = 'start_legacy_obj'; const CMD_CHECK_CURRENT_ITEM_LP = 'ccilp'; - const UPDATE_LEGACY_OBJECT_LP_INTERVAL = 2000; - /** * @var Component|null @@ -84,14 +82,21 @@ class LSControlBuilder implements ControlBuilder */ protected $additional_js; + /** + * @var LSGlobalSettings + */ + protected $global_settings; + public function __construct( Factory $ui_factory, LSURLBuilder $url_builder, - ilLanguage $language + ilLanguage $language, + LSGlobalSettings $global_settings ) { $this->ui_factory = $ui_factory; $this->url_builder = $url_builder; $this->lng = $language; + $this->global_settings = $global_settings; } public function getExitControl() @@ -302,7 +307,7 @@ public function start(string $label, string $url, int $parameter = null) : Contr $this->start = $this->ui_factory->button() ->primary($label, '') ->withOnLoadCode(function ($id) use ($url) { - $interval = self::UPDATE_LEGACY_OBJECT_LP_INTERVAL; + $interval = $this->global_settings->getPollingIntervalMilliseconds(); return "$('#{$id}').on('click', function(ev) { var il_ls_win = window.open('$url'); window._lso_current_item_lp = -1; @@ -329,7 +334,12 @@ protected function setListenerJS( ) { $this->additional_js = <<keys() as $key) { - $this[$key] = $dic[$key]; - } - $this["db.filesystem"] = function ($c) : ilLearningSequenceFilesystem { return new ilLearningSequenceFilesystem(); }; diff --git a/Modules/LearningSequence/classes/class.ilLSLocalDI.php b/Modules/LearningSequence/classes/class.ilLSLocalDI.php index 2fb7640be1b0..5ca00f049e4f 100644 --- a/Modules/LearningSequence/classes/class.ilLSLocalDI.php +++ b/Modules/LearningSequence/classes/class.ilLSLocalDI.php @@ -9,14 +9,11 @@ class ilLSLocalDI extends Container { public function init( - ilLSDI $dic, + ArrayAccess $dic, + ilLSDI $lsdic, DataFactory $data_factory, ilObjLearningSequence $object ) { - foreach ($dic->keys() as $key) { - $this[$key] = $dic[$key]; - } - $ref_id = (int) $object->getRefId(); $obj_id = (int) $object->getId(); $obj_title = $object->getTitle(); @@ -36,12 +33,12 @@ public function init( return ilContainerSorting::_getInstance($c["obj.obj_id"]); }; - $this["db.lsitems"] = function ($c) use ($dic) : ilLSItemsDB { + $this["db.lsitems"] = function ($c) use ($dic, $lsdic) : ilLSItemsDB { $online_status = new LSItemOnlineStatus(); return new ilLSItemsDB( $dic["tree"], $c["obj.sorting"], - $dic["db.postconditions"], + $lsdic["db.postconditions"], $online_status ); }; @@ -53,16 +50,16 @@ public function init( ); }; - $this["learneritems"] = function ($c) : ilLSLearnerItemsQueries { + $this["learneritems"] = function ($c) use ($dic, $lsdic) : ilLSLearnerItemsQueries { return new ilLSLearnerItemsQueries( $c["db.progress"], - $c["db.states"], + $lsdic["db.states"], $c["obj.ref_id"], $c["usr.id"] ); }; - $this["gui.learner"] = function ($c) use ($dic, $object) : ilObjLearningSequenceLearnerGUI { + $this["gui.learner"] = function ($c) use ($dic, $lsdic, $object) : ilObjLearningSequenceLearnerGUI { $has_items = count($c["learneritems"]->getItems()) > 0; $first_access = $c["learneritems"]->getFirstAccess(); @@ -79,7 +76,7 @@ public function init( $dic["ui.factory"], $dic["ui.renderer"], $c["roles"], - $dic["db.settings"]->getSettingsFor($c["obj.obj_id"]), + $lsdic["db.settings"]->getSettingsFor($c["obj.obj_id"]), $c["player.curriculumbuilder"], $c["player"] ); @@ -120,11 +117,17 @@ public function init( return new LSUrlBuilder($player_base_url); }; + $this["globalsetttings"] = function ($c) use ($dic) { + $db = new ilLSGlobalSettingsDB($dic['ilSetting']); + return $db->getSettings(); + }; + $this["player.controlbuilder"] = function ($c) use ($dic) : LSControlBuilder { return new LSControlBuilder( $dic["ui.factory"], $c["player.urlbuilder"], - $dic["lng"] + $dic["lng"], + $c["globalsetttings"] ); }; @@ -158,7 +161,7 @@ public function init( ); }; - $this["player"] = function ($c) use ($dic) : ilLSPlayer { + $this["player"] = function ($c) use ($dic, $lsdic) : ilLSPlayer { return new ilLSPlayer( $c["obj.title"], $c["learneritems"], @@ -168,7 +171,7 @@ public function init( $c["player.viewfactory"], $c["player.kioskrenderer"], $dic["ui.factory"], - $dic["gs.current_context"] + $lsdic["gs.current_context"] ); }; diff --git a/Modules/LearningSequence/classes/class.ilObjLearningSequence.php b/Modules/LearningSequence/classes/class.ilObjLearningSequence.php index ef8bde988ff6..810b40f41881 100644 --- a/Modules/LearningSequence/classes/class.ilObjLearningSequence.php +++ b/Modules/LearningSequence/classes/class.ilObjLearningSequence.php @@ -240,6 +240,7 @@ public function getLocalDI() : \ArrayAccess if (is_null($this->local_di)) { $di = new ilLSLocalDI(); $di->init( + $this->getDIC(), $this->getDI(), new \ILIAS\Data\Factory(), $this diff --git a/Modules/LearningSequence/classes/Settings/ilObjLearningSequenceAdmin.php b/Modules/LearningSequence/classes/class.ilObjLearningSequenceAdmin.php similarity index 62% rename from Modules/LearningSequence/classes/Settings/ilObjLearningSequenceAdmin.php rename to Modules/LearningSequence/classes/class.ilObjLearningSequenceAdmin.php index 3d62e80e30bb..1259a9aac6e3 100644 --- a/Modules/LearningSequence/classes/Settings/ilObjLearningSequenceAdmin.php +++ b/Modules/LearningSequence/classes/class.ilObjLearningSequenceAdmin.php @@ -1,11 +1,11 @@ -type = 'lsos'; + + global $DIC; + $this->ctrl = $DIC['ilCtrl']; + $this->rbacsystem = $DIC['rbacsystem']; + parent::__construct($a_data, $a_id, $a_call_by_reference, $a_prepare_output); + + $this->settings_db = new ilLSGlobalSettingsDB($DIC['ilSetting']); + $this->ui_factory = $DIC['ui.factory']; + $this->ui_renderer = $DIC['ui.renderer']; + $this->refinery = $DIC['refinery']; + $this->request = $DIC->http()->request(); + } + + public function getAdminTabs() + { + $this->tabs_gui->addTarget('settings', $this->ctrl->getLinkTargetByClass(self::class, self::CMD_EDIT)); + if ($this->rbacsystem->checkAccess('edit_permission', $this->object->getRefId())) { + $this->tabs_gui->addTarget('perm_settings', $this->ctrl->getLinkTargetByClass('ilpermissiongui', 'perm'), array(), 'ilpermissiongui'); + } + } + + public function executeCommand() + { + $this->checkPermission('read'); + $next_class = $this->ctrl->getNextClass($this); + $cmd = $this->ctrl->getCmd(); + $this->prepareOutput(); + + switch ($next_class) { + case 'ilpermissiongui': + $this->tabs_gui->setTabActive('perm_settings'); + $perm_gui = new ilPermissionGUI($this); + $this->ctrl->forwardCommand($perm_gui); + break; + + default: + switch ($cmd) { + case self::CMD_VIEW: + case self::CMD_EDIT: + $this->edit(); + break; + case self::CMD_SAVE: + $this->save(); + break; + default: + throw new Exception(__METHOD__ . " :: Unknown command " . $cmd); + } + } + } + + protected function getForm(array $values = []) : Input\Container\Form\Form + { + $target = $this->ctrl->getLinkTargetByClass(self::class, self::CMD_SAVE); + $poll_interval = $this->ui_factory->input()->field()->numeric( + $this->lng->txt("lso_admin_interval_label"), + $this->lng->txt("lso_admin_interval_byline") + ) + ->withAdditionalTransformation( + $this->refinery->int()->isGreaterThan(0) + ) + ->withAdditionalTransformation( + $this->refinery->custom()->transformation( + function ($v) { + return (float) $v; + } + ) + ); + + if (isset($values[self::F_POLL_INTERVAL])) { + $poll_interval = $poll_interval->withValue($values[self::F_POLL_INTERVAL]); + } + + $section = $this->ui_factory->input()->field()->section( + [self::F_POLL_INTERVAL => $poll_interval], + $this->lng->txt("lso_admin_form_title"), + $this->lng->txt("lso_admin_form_byline") + ); + $form = $this->ui_factory->input()->container()->form() + ->standard($target, [$section]) + ->withAdditionalTransformation( + $this->refinery->custom()->transformation( + function ($data) { + return array_shift($data); + } + ) + ); + + return $form; + } + + protected function show(Input\Container\Form\Form $form) : void + { + $this->tpl->setContent( + $this->ui_renderer->render($form) + ); + } + + protected function edit() : void + { + $values = [ + self::F_POLL_INTERVAL => $this->settings_db->getSettings()->getPollingIntervalSeconds() + ]; + $form = $this->getForm($values); + $this->show($form); + } + + protected function save() : void + { + $form = $this->getForm()->withRequest($this->request); + $data = $form->getData(); + if ($data) { + $settings = $this->settings_db->getSettings() + ->withPollingIntervalSeconds($data[self::F_POLL_INTERVAL]); + $this->settings_db->storeSettings($settings); + } + $this->show($form); + } +} diff --git a/Modules/LearningSequence/test/GlobalSettings/GlobalSettingsTest.php b/Modules/LearningSequence/test/GlobalSettings/GlobalSettingsTest.php new file mode 100644 index 000000000000..021172dac823 --- /dev/null +++ b/Modules/LearningSequence/test/GlobalSettings/GlobalSettingsTest.php @@ -0,0 +1,35 @@ +assertEquals( + $interval, + $settings->getPollingIntervalSeconds() + ); + + return $settings; + } + + /** + * @depends testConstruction + */ + public function testIntervalAttribute(LSGlobalSettings $settings) + { + $interval = 2.0; + $settings = $settings->withPollingIntervalSeconds($interval); + $this->assertEquals( + $interval, + $settings->getPollingIntervalSeconds() + ); + $this->assertEquals( + $interval * 1000, + $settings->getPollingIntervalMilliseconds() + ); + } +} diff --git a/Modules/LearningSequence/test/LSControlBuilderTest.php b/Modules/LearningSequence/test/LSControlBuilderTest.php index 138f725ed8b4..5e878b596154 100644 --- a/Modules/LearningSequence/test/LSControlBuilderTest.php +++ b/Modules/LearningSequence/test/LSControlBuilderTest.php @@ -22,8 +22,9 @@ public function setUp() : void $data_factory = new DataFactory(); $uri = $data_factory->uri('http://ilias.de/somepath'); $url_builder = new LSUrlBuilder($uri); + $settings = new LSGlobalSettings(12); - $this->control_builder = new LSControlBuilder($ui_factory, $url_builder, $lang); + $this->control_builder = new LSControlBuilder($ui_factory, $url_builder, $lang, $settings); } public function testConstruction() diff --git a/Modules/LearningSequence/test/ilModulesLearningSequenceSuite.php b/Modules/LearningSequence/test/ilModulesLearningSequenceSuite.php index 4ab199aabfbe..d2aca5a61f11 100644 --- a/Modules/LearningSequence/test/ilModulesLearningSequenceSuite.php +++ b/Modules/LearningSequence/test/ilModulesLearningSequenceSuite.php @@ -16,6 +16,7 @@ public static function suite() require_once("./Modules/LearningSequence/test/Settings/LSSettingsTest.php"); require_once("./Modules/LearningSequence/test/LSItems/LSItemTest.php"); require_once("./Modules/LearningSequence/test/LearnerProgress/LSLearnerItemTest.php"); + require_once("./Modules/LearningSequence/test/GlobalSettings/GlobalSettingsTest.php"); $suite->addTestSuite("LSControlBuilderTest"); $suite->addTestSuite("LSLocatorBuilderTest"); @@ -24,6 +25,7 @@ public static function suite() $suite->addTestSuite("LSSettingsTest"); $suite->addTestSuite("LSItemTest"); $suite->addTestSuite("LSLearnerItemTest"); + $suite->addTestSuite("LSGlobalSettingsTest"); return $suite; } diff --git a/Modules/OrgUnit/classes/Provider/OrgUnitToolProvider.php b/Modules/OrgUnit/classes/Provider/OrgUnitToolProvider.php index 34d0ac65209d..7497eab63ce0 100644 --- a/Modules/OrgUnit/classes/Provider/OrgUnitToolProvider.php +++ b/Modules/OrgUnit/classes/Provider/OrgUnitToolProvider.php @@ -57,11 +57,13 @@ public function getToolsForContextStack(CalledContexts $called_contexts) : array private function getTree() : Tree { global $DIC; + $lng = $DIC->language(); $tree = $this->getTreeRecursion(); $parent_node_id = $DIC->repositoryTree()->getParentId(ilObjOrgUnit::getRootOrgRefId()); - return $this->dic->ui()->factory()->tree()->expandable($tree)->withData($tree->getChildsOfNode($parent_node_id)); + return $this->dic->ui()->factory()->tree()->expandable($lng->txt("org_units"), $tree) + ->withData($tree->getChildsOfNode($parent_node_id)); } private function getTreeRecursion() : TreeRecursion diff --git a/Modules/Portfolio/classes/class.ilObjPortfolioAdministrationGUI.php b/Modules/Portfolio/classes/class.ilObjPortfolioAdministrationGUI.php index 4907be5e8567..41096d3a12a7 100644 --- a/Modules/Portfolio/classes/class.ilObjPortfolioAdministrationGUI.php +++ b/Modules/Portfolio/classes/class.ilObjPortfolioAdministrationGUI.php @@ -161,7 +161,6 @@ public function saveSettings() $banner = (bool) $form->getInput("banner"); $prfa_set = new ilSetting("prfa"); - $prfa_set->set("pd_block", (bool) $form->getInput("pd_block")); $prfa_set->set("banner", $banner); $prfa_set->set("banner_width", (int) $form->getInput("width")); $prfa_set->set("banner_height", (int) $form->getInput("height")); @@ -217,11 +216,6 @@ protected function initFormSettings() $prfa_set = new ilSetting("prfa"); - $pdblock = new ilCheckboxInputGUI($lng->txt("prtf_pd_block"), "pd_block"); - $pdblock->setInfo($lng->txt("prtf_pd_block_info")); - $pdblock->setChecked($prfa_set->get("pd_block", false)); - $form->addItem($pdblock); - $banner = new ilCheckboxInputGUI($lng->txt("prtf_preview_banner"), "banner"); $banner->setInfo($lng->txt("prtf_preview_banner_info")); $form->addItem($banner); diff --git a/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModuleGUI.php b/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModuleGUI.php index 11f466153ba7..f522c1cf8890 100755 --- a/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModuleGUI.php +++ b/Modules/Scorm2004/classes/class.ilObjSCORM2004LearningModuleGUI.php @@ -2010,7 +2010,7 @@ public function insertChapter($a_redirect = true) $chap_ids[] = $chap->getId(); } $chap_ids = array_reverse($chap_ids); - $chap_ids = implode($chap_ids, ":"); + $chap_ids = implode(":", $chap_ids); if ($a_redirect) { $ilCtrl->setParameter($this, "highlight", $chap_ids); @@ -2057,7 +2057,7 @@ public function insertSco($a_redirect = true) $sco_ids[] = $sco->getId(); } $sco_ids = array_reverse($sco_ids); - $sco_ids = implode($sco_ids, ":"); + $sco_ids = implode(":", $sco_ids); if ($a_redirect) { $ilCtrl->setParameter($this, "highlight", $sco_ids); @@ -2104,7 +2104,7 @@ public function insertAsset($a_redirect = true) $ass_ids[] = $ass->getId(); } $ass_ids = array_reverse($ass_ids); - $ass_ids = implode($ass_ids, ":"); + $ass_ids = implode(":", $ass_ids); if ($a_redirect) { $ilCtrl->setParameter($this, "highlight", $ass_ids); @@ -2150,7 +2150,7 @@ public function insertPage($a_redirect = true) $page_ids[] = $page->getId(); } $page_ids = array_reverse($page_ids); - $page_ids = implode($page_ids, ":"); + $page_ids = implode(":", $page_ids); if ($a_redirect) { $ilCtrl->setParameter($this, "highlight", $page_ids); @@ -2403,7 +2403,7 @@ public function insertTemplate($a_redirect = true) $page_ids[] = $page->getId(); } $page_ids = array_reverse($page_ids); - $page_ids = implode($page_ids, ":"); + $page_ids = implode(":", $page_ids); if ($a_redirect) { if ($_GET["obj_id"] != "") { diff --git a/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php b/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php index 5a34e28481b9..1993a75a6088 100755 --- a/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php +++ b/Modules/ScormAicc/classes/SCORM/class.ilObjSCORMTracking.php @@ -277,8 +277,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 +297,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(); diff --git a/Modules/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php b/Modules/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php index 0a96c94695ff..4ec323e9ba36 100755 --- a/Modules/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php +++ b/Modules/ScormAicc/classes/class.ilObjSAHSLearningModuleGUI.php @@ -640,10 +640,10 @@ public function certificate() public function getTabs() { global $DIC; - $rbacsystem = $DIC['rbacsystem']; $ilCtrl = $DIC['ilCtrl']; $ilHelp = $DIC['ilHelp']; - + $ilAccess = $DIC->access(); + if ($this->ctrl->getCmd() == "delete") { return; } @@ -692,7 +692,7 @@ public function getTabs() // learning progress and offline mode include_once './Services/Tracking/classes/class.ilLearningProgressAccess.php'; - if (ilLearningProgressAccess::checkAccess($this->object->getRefId()) || $rbacsystem->checkAccess("edit_permission", "", $this->object->getRefId())) { + if (ilLearningProgressAccess::checkAccess($this->object->getRefId()) || $ilAccess->checkAccess("write", "", $this->object->getRefId())) { //if scorm && offline_mode activated if ($this->object->getSubType() == "scorm2004" || $this->object->getSubType() == "scorm") { if ($this->object->getOfflineMode() == true) { @@ -715,7 +715,7 @@ public function getTabs() } // tracking data - if ($rbacsystem->checkAccess("read_learning_progress", $this->object->getRefId()) || $rbacsystem->checkAccess("edit_learning_progress", $this->object->getRefId())) { + if ($ilAccess->checkAccess("read_learning_progress", '', $this->object->getRefId()) || $ilAccess->checkAccess("edit_learning_progress", '', $this->object->getRefId())) { if ($this->object->getSubType() == "scorm2004" || $this->object->getSubType() == "scorm") { include_once('./Services/PrivacySecurity/classes/class.ilPrivacySettings.php'); $privacy = ilPrivacySettings::_getInstance(); @@ -744,7 +744,7 @@ public function getTabs() } // export - if ($rbacsystem->checkAccess("edit_permission", "", $this->object->getRefId())) { + if ($ilAccess->checkAccess("write", "", $this->object->getRefId())) { $this->tabs_gui->addTarget( "export", $this->ctrl->getLinkTarget($this, "export"), @@ -754,7 +754,7 @@ public function getTabs() } // perm - if ($rbacsystem->checkAccess('edit_permission', $this->object->getRefId())) { + if ($ilAccess->checkAccess('edit_permission', '', $this->object->getRefId())) { $this->tabs_gui->addTarget( "perm_settings", $this->ctrl->getLinkTargetByClass(array(get_class($this),'ilpermissiongui'), "perm"), diff --git a/Modules/ScormAicc/scripts/basisAPI.js b/Modules/ScormAicc/scripts/basisAPI.js index e3b0928bd0d1..62a4122de377 100644 --- a/Modules/ScormAicc/scripts/basisAPI.js +++ b/Modules/ScormAicc/scripts/basisAPI.js @@ -77,7 +77,7 @@ function sendRequest (url, data, callback, user, password, headers) { } function useSendBeacon() { - if (navigator.userAgent.indexOf("Chrome") > -1) { + if (navigator.userAgent.indexOf("Chrom") > -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; @@ -316,6 +316,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 +326,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; iapplyToSubTreeNodes( + function (ilObjStudyProgramme $prg) use (&$ret) { + if ($prg->getId() == $this->getId()) { + return; + } + $ret[] = $prg; + }, + false + ); + return $ret; + } + /** * Get all ilObjStudyProgrammes that are direct children of this * object. diff --git a/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php b/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php index d17a2d86cd8d..725da4d622bf 100644 --- a/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php +++ b/Modules/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php @@ -950,18 +950,14 @@ public function mayManageMembers() : bool ); } - public function getLocalMembers(): array + public function getLocalMembers() : array { return $this->object->getMembers(); } - public function isOperationAllowedForUser(int $usr_id, string $operation): bool + public function isOperationAllowedForUser(int $usr_id, string $operation) : bool { - $ret = $this->position_based_access->isUserAccessibleForOperationAtPrg( - $usr_id, - $this->object, - $operation - ); - return $ret; + return $this->mayManageMembers() + || $this->position_based_access->isUserAccessibleForOperationAtPrg($usr_id, $this->object, $operation); } } diff --git a/Modules/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php b/Modules/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php index 0c10af9b5a71..738acf530125 100644 --- a/Modules/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php +++ b/Modules/StudyProgramme/classes/class.ilStudyProgrammeDashboardViewGUI.php @@ -88,8 +88,10 @@ public function getHTML() : string continue; } + $current_prg = $current->getStudyProgramme(); + /** @var ilStudyProgrammeSettings $current_prg_settings */ - $current_prg_settings = $current->getStudyProgramme()->getRawSettings(); + $current_prg_settings = $current_prg->getRawSettings(); /** @var ilStudyProgrammeUserProgress $current_progress */ $current_progress = $current->getRootProgress(); @@ -97,9 +99,8 @@ public function getHTML() : string list($valid, $validation_date) = $this->findValidationValues($assignments); list($minimum_percents, $current_percents) = $this->calculatePercent( - $current_prg_settings->getAssessmentSettings()->getPoints(), - $current_progress->getCurrentAmountOfPoints(), - (int) $current->getStudyProgramme()->getRefId() + $current_prg, + $current_progress->getCurrentAmountOfPoints() ); $current_status = $current_progress->getStatus(); @@ -128,7 +129,6 @@ public function getHTML() : string } $items[] = $this->buildItem($current->getStudyProgramme(), $properties); - ; } if (count($items) == 0) { @@ -288,25 +288,32 @@ protected function txt(string $code) : string return $this->lng->txt($code); } - protected function calculatePercent(int $points, int $current_points, int $prg_ref_id) : array + protected function calculatePercent(ilObjStudyProgramme $prg, int $current_points) : array { - $children = ilObjStudyProgramme::getAllChildren($prg_ref_id); - $max_points = 0; - /** @var ilObjStudyProgramme $child */ - foreach ($children as $child) { - $max_points += $child->getRawSettings()->getAssessmentSettings()->getPoints(); - } - $minimum_percents = 0; $current_percents = 0; - if ($max_points > 0) { - $minimum_percents = round((100 * $points / $max_points), 2); - $current_percents = round((100 * $current_points / $max_points), 2); - } - if ($max_points == 0 && $points == 0) { + if ($prg->hasLPChildren()) { $minimum_percents = 100; - $current_percents = 100; + if ($current_points > 0) { + $current_percents = 100; + } + } + + $children = $prg->getAllPrgChildren(); + if (count($children) > 0) { + $max_points = 0; + foreach ($children as $child) { + $max_points += $child->getPoints(); + } + + if ($max_points > 0) { + $prg_points = $prg->getPoints(); + $minimum_percents = round((100 * $prg_points / $max_points), 2); + } + if ($current_points > 0) { + $current_percents = round((100 * $current_points / $max_points), 2); + } } return [ @@ -315,6 +322,7 @@ protected function calculatePercent(int $points, int $current_points, int $prg_r ]; } + /** * @throws ilException */ diff --git a/Modules/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php b/Modules/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php index 885283877a8d..eed296b0664f 100644 --- a/Modules/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php +++ b/Modules/StudyProgramme/classes/class.ilStudyProgrammeMembersTableGUI.php @@ -293,22 +293,20 @@ protected function buildActionDropDown( $access_by_position = $this->isPermissionControlledByOrguPosition(); $parent = $this->getParentObject(); - $view_individual_plan = - $access_by_position == false || - $parent->isOperationAllowedForUser($usr_id, ilOrgUnitOperation::OP_VIEW_INDIVIDUAL_PLAN) - ; + $view_individual_plan = $parent->isOperationAllowedForUser( + $usr_id, + ilOrgUnitOperation::OP_VIEW_INDIVIDUAL_PLAN + ); - $edit_individual_plan = - $access_by_position == false || - $parent->isOperationAllowedForUser($usr_id, ilOrgUnitOperation::OP_VIEW_INDIVIDUAL_PLAN) - ; + $edit_individual_plan = $parent->isOperationAllowedForUser( + $usr_id, + ilOrgUnitOperation::OP_VIEW_INDIVIDUAL_PLAN + ); - $manage_members = - ( $access_by_position == false || - $parent->isOperationAllowedForUser($usr_id, ilOrgUnitOperation::OP_MANAGE_MEMBERS) - ) && - in_array($usr_id, $this->getParentObject()->getLocalMembers()) - ; + $manage_members = $parent->isOperationAllowedForUser( + $usr_id, + ilOrgUnitOperation::OP_MANAGE_MEMBERS + ) && in_array($usr_id, $this->getParentObject()->getLocalMembers()); foreach ($actions as $action) { switch ($action) { diff --git a/Modules/StudyProgramme/test/ilModulesStudyProgrammeSuite.php b/Modules/StudyProgramme/test/ilModulesStudyProgrammeSuite.php index 3e66bab2a40b..06f35e638ce7 100644 --- a/Modules/StudyProgramme/test/ilModulesStudyProgrammeSuite.php +++ b/Modules/StudyProgramme/test/ilModulesStudyProgrammeSuite.php @@ -59,6 +59,7 @@ public static function suite() require_once("./Modules/StudyProgramme/test/ilStudyProgrammeDeadlineSettingsTest.php"); require_once("./Modules/StudyProgramme/test/ilStudyProgrammeTypeSettingsTest.php"); require_once("./Modules/StudyProgramme/test/types/ilStudyProgrammeTypeInfoTest.php"); + require_once("./Modules/StudyProgramme/test/ilStudyProgrammeDashGUITest.php"); $suite->addTestSuite("ilObjStudyProgrammeTest"); $suite->addTestSuite("ilStudyProgrammeEventsTest"); $suite->addTestSuite("ilStudyProgrammeLPTest"); @@ -83,6 +84,7 @@ public static function suite() $suite->addTestSuite("ilStudyProgrammeDeadlineSettingsTest"); $suite->addTestSuite("ilStudyProgrammeTypeSettingsTest"); $suite->addTestSuite("ilStudyProgrammeTypeInfoTest"); + $suite->addTestSuite("ilStudyProgrammeDashGUITest"); return $suite; } } diff --git a/Modules/StudyProgramme/test/ilObjStudyProgrammeTest.php b/Modules/StudyProgramme/test/ilObjStudyProgrammeTest.php index fd1335978c73..829e5ae269e2 100644 --- a/Modules/StudyProgramme/test/ilObjStudyProgrammeTest.php +++ b/Modules/StudyProgramme/test/ilObjStudyProgrammeTest.php @@ -307,6 +307,15 @@ public function testTreeGetChildren() $this->assertEquals(0, count($children[0]->getChildren())); } + public function testTreeGetAllPrgChildren() + { + $this->createSmallTree(); + $this->assertEquals( + 4, + $this->root_object->getAllPrgChildren() + ); + } + /** * Test getParent on ilObjStudyProgramme * diff --git a/Modules/StudyProgramme/test/ilStudyProgrammeDashGUITest.php b/Modules/StudyProgramme/test/ilStudyProgrammeDashGUITest.php new file mode 100644 index 000000000000..363e46ba8688 --- /dev/null +++ b/Modules/StudyProgramme/test/ilStudyProgrammeDashGUITest.php @@ -0,0 +1,122 @@ + Extended GPL, see docs/LICENSE */ + +use PHPUnit\Framework\TestCase; + +/** + * Encapsulation of GUI + */ +class mockSPRGDashGUI extends ilStudyProgrammeDashboardViewGUI +{ + public function __construct() + { + } + + public function mockCalculatePercent($prg, int $current_points) + { + return $this->calculatePercent($prg, $current_points); + } +} + +/** + * TestCase for SPRG-Section of dashboard + */ +class ilStudyProgrammeDashGUITest extends TestCase +{ + protected function setUp() : void + { + $this->gui = new mockSPRGDashGUI(); + $this->prg = $this->getMockBuilder(ilObjStudyProgramme::class) + ->disableOriginalConstructor() + ->setMethods([ + 'hasLPChildren', + 'getAllPrgChildren', + 'getPoints' + ]) + ->getMock(); + } + + public function userPointsDataProvider() + { + return [ + 'zero' => [0, 0], + 'one' => [1, 0.63], + 'ten' => [10, 6.25], + 'fiftyfive' => [55, 34.38], + 'hundred' => [100, 62.5], + 'oneOone' => [101, 63.13], + 'onesixty' => [160, 100] + ]; + } + + /** + * @dataProvider userPointsDataProvider + */ + public function testPercentageWithoutChildren(int $current_user_points) + { + $this->prg->method('hasLPChildren') + ->willReturn(false); + $this->prg->method('getAllPrgChildren') + ->willReturn([]); + + list($minimum_percents, $current_percents) + = $this->gui->mockCalculatePercent($this->prg, $current_user_points); + + $this->assertEquals(0, $minimum_percents); + $this->assertEquals(0, $current_percents); + } + + /** + * @dataProvider userPointsDataProvider + */ + public function testPercentageWithCoursesAtTopLevel(int $current_user_points) + { + $this->prg->method('hasLPChildren') + ->willReturn(true); + + list($minimum_percents, $current_percents) + = $this->gui->mockCalculatePercent($this->prg, $current_user_points); + + $this->assertEquals(100, $minimum_percents); + if ($current_user_points == 0) { + $this->assertEquals(0, $current_percents); + } + if ($current_user_points > 0) { + $this->assertEquals(100, $current_percents); + } + } + + /** + * @dataProvider userPointsDataProvider + */ + public function testPercentageWithPrograms(int $current_user_points, float $expected) + { + $node = $this->getMockBuilder(ilObjStudyProgramme::class) + ->disableOriginalConstructor() + ->setMethods(['getPoints']) + ->getMock(); + + $node1 = clone $node; + $node1->method('getPoints')->willReturn(100); + $node2 = clone $node; + $node2->method('getPoints')->willReturn(50); + $node3 = clone $node; + $node3->method('getPoints')->willReturn(5); + $node4 = clone $node; + $node4->method('getPoints')->willReturn(5); + + $this->prg->method('hasLPChildren') + ->willReturn(false); + $this->prg->method('getAllPrgChildren') + ->willReturn([$node1, $node2, $node3, $node4]); + + $this->prg->method('getPoints')->willReturn(60); + + list($minimum_percents, $current_percents) + = $this->gui->mockCalculatePercent($this->prg, $current_user_points); + + $this->assertEquals(37.5, $minimum_percents); //37.5 = (160 max points / 60 root-prg points) * 100 + $this->assertEquals($expected, $current_percents); + } +} diff --git a/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php b/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php index 124995ba5648..e3bf08ed0c4e 100644 --- a/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php +++ b/Modules/Survey/Evaluation/class.ilSurveyEvaluationGUI.php @@ -447,7 +447,7 @@ public function exportCumulatedResults($details = 0) $separator = ";"; foreach ($csvfile as $csvrow) { $csvrow = $this->processCSVRow($csvrow, true, $separator); - $csv .= join($csvrow, $separator) . "\n"; + $csv .= join($separator, $csvrow) . "\n"; } ilUtil::deliverData($csv, $surveyname . ".csv"); exit(); @@ -724,7 +724,7 @@ protected function buildExportModal($a_id, $a_cmd) return $modal->getHTML(); } - public function evaluation($details = 0) + public function evaluation($details = 0, $pdf = false, $return_pdf = false) { $rbacsystem = $this->rbacsystem; $ilToolbar = $this->toolbar; @@ -947,6 +947,15 @@ public function evaluation($details = 0) $this->log->debug("end"); + if ($pdf) { + $html = $this->tpl->printToString(); + if ($return_pdf) { + return $html; + } else { + $this->generateAndSendPDF($html); + } + } + // $this->tpl->addCss("./Modules/Survey/templates/default/survey_print.css", "print"); } @@ -1370,7 +1379,7 @@ public function exportEvaluationUser() $separator = ";"; foreach ($rows as $csvrow) { $csvrow = str_replace("\n", " ", $this->processCSVRow($csvrow, true, $separator)); - $csv .= join($csvrow, $separator) . "\n"; + $csv .= join($separator, $csvrow) . "\n"; } ilUtil::deliverData($csv, "$surveyname.csv"); exit(); @@ -1665,32 +1674,14 @@ protected function hasResultsAccess() return $this->access->checkRbacOrPositionPermissionAccess('read_results', 'access_results', $this->object->getRefId()); } - // - // PATCH BGHW - // - public function evaluationpdf() { - $this->ctrl->setParameter($this, "pdf", 1); - $this->ctrl->setParameter($this, "cp", $_GET["cp"]); - $this->ctrl->setParameter($this, "vw", $_GET["vw"]); - $this->callPhantom( - $this->ctrl->getLinkTarget($this, "evaluation", "", false, false), - "pdf", - $this->object->getTitle() . ".pdf" - ); + $this->evaluation(0, true); } public function evaluationdetailspdf() { - $this->ctrl->setParameter($this, "pdf", 1); - $this->ctrl->setParameter($this, "cp", $_GET["cp"]); - $this->ctrl->setParameter($this, "vw", $_GET["vw"]); - $this->callPhantom( - $this->ctrl->getLinkTarget($this, "evaluationdetails", "", false, false), - "pdf", - $this->object->getTitle() . ".pdf" - ); + $this->evaluation(1, true); } public function downloadChart() @@ -1700,14 +1691,7 @@ public function downloadChart() return; } - $this->ctrl->setParameter($this, "qid", $qid); - $url = $this->ctrl->getLinkTarget($this, "renderChartOnly", "", false, false); - $this->ctrl->setParameter($this, "qid", ""); - - include_once "./Modules/SurveyQuestionPool/classes/class.SurveyQuestion.php"; - $file = $this->object->getTitle() . " - " . SurveyQuestion::_getTitle($qid); - - $this->callPhantom($url, "png", $file . ".png"); + $this->renderChartOnly(); } public function renderChartOnly() @@ -1729,7 +1713,6 @@ public function renderChartOnly() } // parse answer data in evaluation results - include_once "./Modules/SurveyQuestionPool/classes/class.SurveyQuestion.php"; foreach ($this->object->getSurveyQuestions() as $qdata) { if ($qid == $qdata["question_id"]) { $q_eval = SurveyQuestion::_instanciateQuestionEvaluation($qdata["question_id"], $finished_ids); @@ -1758,26 +1741,37 @@ public function renderChartOnly() } // "print view" - $ptpl = new ilTemplate("tpl.main.html", true, true); - foreach ($tpl->css_files as $css) { - $ptpl->setCurrentBlock("css_file"); - $ptpl->setVariable("CSS_FILE", $css["file"]); - $ptpl->setVariable("CSS_MEDIA", $css["media"]); - $ptpl->parseCurrentBlock(); - } - foreach ($tpl->js_files as $js) { - $ptpl->setCurrentBlock("js_file"); - $ptpl->setVariable("JS_FILE", $js); - $ptpl->parseCurrentBlock(); - } - $ptpl->setVariable("LOCATION_STYLESHEET", ilUtil::getStyleSheetLocation()); - $ptpl->setVariable("LOCATION_CONTENT_STYLESHEET", ilUtil::getNewContentStyleSheetLocation()); - $ptpl->setVariable("CONTENT", $dtmpl->get()); - echo $ptpl->get(); - exit(); + $this->tpl->setContent($dtmpl->get()); + + $html = $this->tpl->printToString(); + $this->generateAndSendPDF($html, $this->object->getTitle() . " - " . SurveyQuestion::_getTitle($qid).".pdf"); + } + + /** + * + * @param $html + * @param $filename + * @throws Exception + */ + public function generateAndSendPDF($html, $filename = "") + { + // :TODO: fixing css dummy parameters + $html = preg_replace("/\?dummy\=[0-9]+/", "", $html); + $html = preg_replace("/\?vers\=[0-9A-Za-z\-]+/", "", $html); + $html = str_replace('.css$Id$', ".css", $html); + $html = preg_replace("/src=\"\\.\\//ims", "src=\"" . ILIAS_HTTP_PATH . "/", $html); + $html = preg_replace("/href=\"\\.\\//ims", "href=\"" . ILIAS_HTTP_PATH . "/", $html); + + //echo $html; exit; + + if ($filename == "") { + $filename = $this->object->getTitle() . ".pdf"; + } + $pdf_factory = new ilHtmlToPdfTransformerFactory(); + $pdf_factory->deliverPDFFromHTMLString($html, $filename, ilHtmlToPdfTransformerFactory::PDF_OUTPUT_DOWNLOAD, "Survey", "Results"); } - public function callPhantom($a_url, $a_suffix, $a_filename, $a_return = false) + public function callPdfGeneration($a_url, $a_suffix, $a_filename, $a_return = false) { $script = ILIAS_ABSOLUTE_PATH . "/Modules/Survey/js/phantom.js"; diff --git a/Modules/Survey/Mail/FormMailCodesGUI.php b/Modules/Survey/Mail/FormMailCodesGUI.php index bfb8c8d9cbcc..199e9712bc9b 100644 --- a/Modules/Survey/Mail/FormMailCodesGUI.php +++ b/Modules/Survey/Mail/FormMailCodesGUI.php @@ -74,7 +74,7 @@ public function __construct($guiclass) $this->mailmessage->setRequired(true); $this->mailmessage->setCols(80); $this->mailmessage->setRows(10); - $this->mailmessage->setInfo(sprintf($this->lng->txt('message_content_info'), join($existingcolumns, ', '))); + $this->mailmessage->setInfo(sprintf($this->lng->txt('message_content_info'), join(', ', $existingcolumns))); $this->addItem($this->mailmessage); // save message diff --git a/Modules/Survey/Participants/class.ilSurveyParticipantsGUI.php b/Modules/Survey/Participants/class.ilSurveyParticipantsGUI.php index 458f21640333..91494f720123 100644 --- a/Modules/Survey/Participants/class.ilSurveyParticipantsGUI.php +++ b/Modules/Survey/Participants/class.ilSurveyParticipantsGUI.php @@ -1470,7 +1470,7 @@ public function initMailRatersForm($appr_id, array $rec_ids) $mailmessage_a->setRequired(true); $mailmessage_a->setCols(80); $mailmessage_a->setRows(10); - $mailmessage_a->setInfo(sprintf($this->lng->txt('message_content_info'), join($existingcolumns, ', '))); + $mailmessage_a->setInfo(sprintf($this->lng->txt('message_content_info'), join(', ', $existingcolumns))); $form->addItem($mailmessage_a); $recf = new ilHiddenInputGUI("rtr_id"); diff --git a/Modules/Survey/README.md b/Modules/Survey/README.md index cc6185e607df..4038c4f08e19 100644 --- a/Modules/Survey/README.md +++ b/Modules/Survey/README.md @@ -7,6 +7,11 @@ This component does currently not offer any public services. +# PDF Generation + +- If wkhtmltopdf is being used you must set the "Use print media type instead of screen" flag. +- Both PhantomJS and wkhtmltopdf struggle with canvas rendering, see e.g. https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1964 + # Internal Documentation This section documents the general concepts and structures of the Survey Module. These are internal implementations which SHOULD not be used outside of this module unless mentioned in the API section of this README. diff --git a/Modules/Survey/classes/class.SurveySearch.php b/Modules/Survey/classes/class.SurveySearch.php index 77ca07491778..1b9ce2dc3470 100755 --- a/Modules/Survey/classes/class.SurveySearch.php +++ b/Modules/Survey/classes/class.SurveySearch.php @@ -156,13 +156,13 @@ public function search() } $cumulated_fields = array(); foreach ($fields as $params) { - array_push($cumulated_fields, "(" . join($params, " OR ") . ")"); + array_push($cumulated_fields, "(" . join(" OR ", $params) . ")"); } $str_where = ""; if ($this->concatenation == self::CONCAT_AND) { - $str_where = "(" . join($cumulated_fields, " AND ") . ")"; + $str_where = "(" . join(" AND ", $cumulated_fields) . ")"; } else { - $str_where = "(" . join($cumulated_fields, " OR ") . ")"; + $str_where = "(" . join(" OR ", $cumulated_fields) . ")"; } if ($str_where) { $str_where = " AND $str_where"; diff --git a/Modules/Survey/classes/class.ilObjSurvey.php b/Modules/Survey/classes/class.ilObjSurvey.php index 350503ed7fb9..7e0c52bccdb7 100755 --- a/Modules/Survey/classes/class.ilObjSurvey.php +++ b/Modules/Survey/classes/class.ilObjSurvey.php @@ -1298,7 +1298,7 @@ public function getAuthor() } } } - return join($author, ","); + return join(",", $author); } /** @@ -3199,7 +3199,7 @@ public function getQuestionblocksTable($arrFilter) "questionblock_id" => $row["questionblock_id"], "title" => $row["title"], "svy" => $surveytitles[$row["obj_fi"]], - "contains" => join($questions_array, ", "), + "contains" => join(", ", $questions_array), "owner" => $row["owner_fi"] ); } @@ -6127,19 +6127,24 @@ public function sendTutorResults() $ilCtrl->setParameterByClass("ilSurveyEvaluationGUI", "ref_id", $this->getRefId()); - include_once "./Modules/Survey/classes/class.ilSurveyEvaluationGUI.php"; $gui = new ilSurveyEvaluationGUI($this); + $url = $ilCtrl->getLinkTargetByClass(array("ilObjSurveyGUI", "ilSurveyEvaluationGUI"), "evaluationdetails", "", false, false); + $html = $gui->evaluation(1, true, true); + $_GET["ref_id"] = $old_ref_id; $_GET["baseClass"] = $old_base_class; + $pdf_factory = new ilHtmlToPdfTransformerFactory(); + $pdf = $pdf_factory->deliverPDFFromHTMLString($html, "survey.pdf", ilHtmlToPdfTransformerFactory::PDF_OUTPUT_FILE, "Survey", "Results"); + /* $log->debug("calling phantom for ref_id: " . $this->getRefId()); - $pdf = $gui->callPhantom($url, "pdf", true, true); + $pdf = $gui->callPdfGeneration($url, "pdf", true, true); - $log->debug("phantom called : " . $pdf); + $log->debug("phantom called : " . $pdf);*/ if (!$pdf || !file_exists($pdf)) { @@ -6167,14 +6172,13 @@ public function sendTutorResults() $mail_obj = new ilMail(ANONYMOUS_USER_ID); $mail_obj->appendInstallationSignature(true); $log->debug("send mail to user id: " . $user_id . ",login: " . ilObjUser::_lookupLogin($user_id)); - $mail_obj->sendMail( + $mail_obj->enqueue( ilObjUser::_lookupLogin($user_id), "", "", $subject, $message, - array($att), - array("system") + array($att) ); } diff --git a/Modules/Survey/classes/class.ilObjSurveyGUI.php b/Modules/Survey/classes/class.ilObjSurveyGUI.php index 0378a01d27c3..fdf70b01d5ce 100755 --- a/Modules/Survey/classes/class.ilObjSurveyGUI.php +++ b/Modules/Survey/classes/class.ilObjSurveyGUI.php @@ -1769,12 +1769,8 @@ public function infoScreen() // :TODO: really save in session? $_SESSION["anonymous_id"][$this->object->getId()] = $anonymous_code; - if (ilObjSurvey::RESULTS_SELF_EVAL_ALL) { - $survey_started = $this->object->isSurveyStarted($ilUser->getId(), $anonymous_code, $ilUser->getId()); - } else { - $survey_started = $this->object->isSurveyStarted($ilUser->getId(), $anonymous_code); - } - + $survey_started = $this->object->isSurveyStarted($ilUser->getId(), $anonymous_code); + $showButtons = $big_button = false; // already finished? diff --git a/Modules/Survey/module.xml b/Modules/Survey/module.xml index 8a15eee2d9ad..02fd603c5c80 100644 --- a/Modules/Survey/module.xml +++ b/Modules/Survey/module.xml @@ -28,4 +28,7 @@ + + + diff --git a/Modules/SurveyQuestionPool/Phrases/class.ilSurveyPhrasesGUI.php b/Modules/SurveyQuestionPool/Phrases/class.ilSurveyPhrasesGUI.php index a4c4e0a9079a..2cd1ed6a470f 100755 --- a/Modules/SurveyQuestionPool/Phrases/class.ilSurveyPhrasesGUI.php +++ b/Modules/SurveyQuestionPool/Phrases/class.ilSurveyPhrasesGUI.php @@ -150,7 +150,7 @@ public function phrases() $data = array(); foreach ($phrases as $phrase_id => $phrase_array) { $categories = &ilSurveyPhrases::_getCategoriesForPhrase($phrase_id); - array_push($data, array('phrase_id' => $phrase_id, 'phrase' => $phrase_array["title"], 'answers' => join($categories, ", "))); + array_push($data, array('phrase_id' => $phrase_id, 'phrase' => $phrase_array["title"], 'answers' => join(", ", $categories))); } $table_gui->setData($data); $this->tpl->setContent($table_gui->getHTML()); @@ -225,7 +225,7 @@ public function deletePhrasesForm($checked_phrases) foreach ($checked_phrases as $phrase_id) { $phrase_array = $phrases[$phrase_id]; $categories = &ilSurveyPhrases::_getCategoriesForPhrase($phrase_id); - array_push($data, array('phrase_id' => $phrase_id, 'phrase' => $phrase_array["title"], 'answers' => join($categories, ", "))); + array_push($data, array('phrase_id' => $phrase_id, 'phrase' => $phrase_array["title"], 'answers' => join(", ", $categories))); } $table_gui->setData($data); $this->tpl->setVariable('ADM_CONTENT', $table_gui->getHTML()); diff --git a/Modules/SurveyQuestionPool/Questions/class.SurveyQuestionGUI.php b/Modules/SurveyQuestionPool/Questions/class.SurveyQuestionGUI.php index bd369e4347bf..b4d4f0d38b35 100755 --- a/Modules/SurveyQuestionPool/Questions/class.SurveyQuestionGUI.php +++ b/Modules/SurveyQuestionPool/Questions/class.SurveyQuestionGUI.php @@ -902,7 +902,7 @@ protected function initPhrasesForm() $categories = ilSurveyPhrases::_getCategoriesForPhrase($phrase_id); $opt = new ilRadioOption($phrase_array["title"], $phrase_id); - $opt->setInfo(join($categories, ",")); + $opt->setInfo(join(",", $categories)); $group->addOption($opt); if ($phrase_array["org_title"] == "dp_standard_numbers") { diff --git a/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php b/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php index e9bb1922d069..4c2ffc34ef63 100644 --- a/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php +++ b/Modules/SystemFolder/classes/class.ilAccessibilitySupportContactsGUI.php @@ -119,6 +119,7 @@ public static function getFooterLink() $ctrl = $DIC->ctrl(); $user = $DIC->user(); $http = $DIC->http(); + $lng = $DIC->language(); $users = ilAccessibilitySupportContacts::getValidSupportContactIds(); @@ -132,7 +133,7 @@ public static function getFooterLink() $url = $request_scheme . '://' . $http->request()->getServerParams()['HTTP_HOST'] . $http->request()->getServerParams()['REQUEST_URI']; - return "mailto:" . $mails . "?body=%0D%0A%0D%0AGemeldeter%20Link:%0D%0A" . rawurlencode($url); + return "mailto:" . $mails . "?body=%0D%0A%0D%0A" . $lng->txt("report_accessibility_link") . "%0D%0A" . rawurlencode($url); } else { return $ctrl->getLinkTargetByClass("ilaccessibilitysupportcontactsgui", ""); } diff --git a/Modules/Test/classes/class.assMarkSchema.php b/Modules/Test/classes/class.assMarkSchema.php index b292cc7b5e8a..e7ffd8cdd542 100755 --- a/Modules/Test/classes/class.assMarkSchema.php +++ b/Modules/Test/classes/class.assMarkSchema.php @@ -164,7 +164,7 @@ public function saveToDb($test_id) } } if (count($difffields)) { - $this->logAction($test_id, $lng->txtlng("assessment", "log_mark_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . join($difffields, ", ")); + $this->logAction($test_id, $lng->txtlng("assessment", "log_mark_changed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . join(", ", $difffields)); } } else { $this->logAction($test_id, $lng->txtlng("assessment", "log_mark_removed", ilObjAssessmentFolder::_getLogLanguage()) . ": " . diff --git a/Modules/Test/classes/class.ilObjAssessmentFolder.php b/Modules/Test/classes/class.ilObjAssessmentFolder.php index 05bab1de7650..f32c08aff276 100644 --- a/Modules/Test/classes/class.ilObjAssessmentFolder.php +++ b/Modules/Test/classes/class.ilObjAssessmentFolder.php @@ -229,7 +229,7 @@ public function _setManualScoring($type_ids) if ((!is_array($type_ids)) || (count($type_ids) == 0)) { $setting->delete("assessment_manual_scoring"); } else { - $setting->set("assessment_manual_scoring", implode($type_ids, ",")); + $setting->set("assessment_manual_scoring", implode(",", $type_ids)); } } @@ -247,7 +247,7 @@ public static function setScoringAdjustableQuestions($type_ids) if ((!is_array($type_ids)) || (count($type_ids) == 0)) { $setting->delete("assessment_scoring_adjustment"); } else { - $setting->set("assessment_scoring_adjustment", implode($type_ids, ",")); + $setting->set("assessment_scoring_adjustment", implode(",", $type_ids)); } } diff --git a/Modules/Test/classes/class.ilObjAssessmentFolderGUI.php b/Modules/Test/classes/class.ilObjAssessmentFolderGUI.php index f782a62f66b1..668a589ca057 100644 --- a/Modules/Test/classes/class.ilObjAssessmentFolderGUI.php +++ b/Modules/Test/classes/class.ilObjAssessmentFolderGUI.php @@ -384,7 +384,7 @@ public function exportLogObject() } $csvoutput = ""; foreach ($csv as $row) { - $csvoutput .= join($row, $separator) . "\n"; + $csvoutput .= join($separator, $row) . "\n"; } ilUtil::deliverData($csvoutput, str_replace(" ", "_", "log_" . $from . "_" . $until . "_" . $available_tests[$test]) . ".csv"); } diff --git a/Modules/Test/classes/class.ilObjTest.php b/Modules/Test/classes/class.ilObjTest.php index 33a6551b9291..477d310196ff 100755 --- a/Modules/Test/classes/class.ilObjTest.php +++ b/Modules/Test/classes/class.ilObjTest.php @@ -1487,7 +1487,7 @@ public function saveToDb($properties_only = false) array_push($changed_fields, "$key: " . $oldrow[$key] . " => " . $newrow[$key]); } } - $changes = join($changed_fields, ", "); + $changes = join(", ", $changed_fields); if (count($changed_fields) > 0) { $this->logAction($this->lng->txtlng("assessment", "log_modified_test", ilObjAssessmentFolder::_getLogLanguage()) . " [" . $changes . "]"); } @@ -7041,7 +7041,7 @@ public function getAuthor() } } } - return join($author, ","); + return join(",", $author); } /** @@ -7070,7 +7070,7 @@ public static function _lookupAuthor($obj_id) } } } - return join($author, ","); + return join(",", $author); } /** @@ -7086,7 +7086,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) { @@ -10598,7 +10605,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 ea86ff5947d5..a9a24d883073 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.ilParticipantsTestResultsGUI.php b/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php index 04bcd4392898..d7d812cc093c 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.ilTestEvaluationData.php b/Modules/Test/classes/class.ilTestEvaluationData.php index 8a9cff6de9ae..979b00da4763 100755 --- a/Modules/Test/classes/class.ilTestEvaluationData.php +++ b/Modules/Test/classes/class.ilTestEvaluationData.php @@ -204,7 +204,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 3948db49b3bd..7f974ef8f28c 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)) { diff --git a/Modules/Test/classes/class.ilTestEvaluationPassData.php b/Modules/Test/classes/class.ilTestEvaluationPassData.php index ef0570722434..e285e91fa7c8 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) : void + { + $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 7f2110c24fad..623a00e3062a 100755 --- a/Modules/Test/classes/class.ilTestExport.php +++ b/Modules/Test/classes/class.ilTestExport.php @@ -307,7 +307,7 @@ protected function aggregatedResultsToCSV($deliver = true) $separator = ";"; foreach ($rows as $evalrow) { $csvrow = &$this->test_obj->processCSVRow($evalrow, true, $separator); - $csv .= join($csvrow, $separator) . "\n"; + $csv .= join($separator, $csvrow) . "\n"; } if ($deliver) { ilUtil::deliverData($csv, ilUtil::getASCIIFilename($this->test_obj->getTitle() . "_aggregated.csv")); @@ -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]); } @@ -923,7 +956,7 @@ public function exportToCSV($deliver = true, $filterby = "", $filtertext = "", $ $separator = ";"; foreach ($rows as $evalrow) { $csvrow = &$this->test_obj->processCSVRow($evalrow, true, $separator); - $csv .= join($csvrow, $separator) . "\n"; + $csv .= join($separator, $csvrow) . "\n"; } if ($deliver) { ilUtil::deliverData($csv, ilUtil::getASCIIFilename($this->test_obj->getTitle() . "_results.csv")); 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 c4ab7405c74f..aa71399f625f 100755 --- a/Modules/Test/classes/class.ilTestOutputGUI.php +++ b/Modules/Test/classes/class.ilTestOutputGUI.php @@ -655,7 +655,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.ilTestPasswordChecker.php b/Modules/Test/classes/class.ilTestPasswordChecker.php index e714f865bd2d..7a7df12b8f81 100644 --- a/Modules/Test/classes/class.ilTestPasswordChecker.php +++ b/Modules/Test/classes/class.ilTestPasswordChecker.php @@ -123,12 +123,10 @@ public function logWrongEnteredPassword() protected function getWrongEnteredPasswordLogMsg() { - $msg = $this->lng->txtlng( + return $this->lng->txtlng( 'assessment', 'log_wrong_test_password_entered', ilObjAssessmentFolder::_getLogLanguage() ); - - return sprintf($msg, $this->getUserEnteredPassword()); } } diff --git a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php index 24a997355184..d79856e30e5c 100755 --- a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -1483,6 +1483,9 @@ public function outProcessingTime($active_id) $this->tpl->setVariable("USER_REMAINING_TIME", sprintf($this->lng->txt("tst_time_already_spent_left"), $str_time_left)); $this->tpl->parseCurrentBlock(); + // jQuery is required by tpl.workingtime.js + require_once "./Services/jQuery/classes/class.iljQueryUtil.php"; + iljQueryUtil::initjQuery(); $template = new ilTemplate("tpl.workingtime.js", true, true, 'Modules/Test'); $template->setVariable("STRING_MINUTE", $this->lng->txt("minute")); $template->setVariable("STRING_MINUTES", $this->lng->txt("minutes")); @@ -1557,13 +1560,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')); } @@ -1588,7 +1597,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()); @@ -1602,6 +1611,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(); + } } } diff --git a/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php b/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php index e07532f16b57..1286501d36fa 100644 --- a/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php +++ b/Modules/Test/classes/class.ilTestSubmissionReviewGUI.php @@ -185,8 +185,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/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_correct_solution_output.html b/Modules/Test/templates/default/tpl.il_as_tst_correct_solution_output.html index a25ae3e2da55..a2a44fefac1d 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 @@ -5,7 +5,7 @@ + diff --git a/Modules/Test/templates/default/tpl.workingtime.js b/Modules/Test/templates/default/tpl.workingtime.js index 5c1a714191ad..bbde116a5e41 100644 --- a/Modules/Test/templates/default/tpl.workingtime.js +++ b/Modules/Test/templates/default/tpl.workingtime.js @@ -1,45 +1,104 @@ - var serverdate = -1; - var unsaved = true; - - function setWorkingTime() - { - if (serverdate == -1) - { - var n = new Date({YEAR}, {MONTHNOW}, {DAYNOW}, {HOURNOW}, {MINUTENOW}, {SECONDNOW}); - serverdate = n.getTime() / 1000; +/*global il:false, jQuery:false*/ +(function(w, $) { + + var test_end = -1, + local_timer_start = -1, + unsaved = true, + interval = 0; + + if (w.performance) { + local_timer_start = performance.now(); + } + + var server_date = (new Date({YEAR}, {MONTHNOW}, {DAYNOW}, {HOURNOW}, {MINUTENOW}, {SECONDNOW})).getTime() / 1000, + // first tick happens immediately, and older browser use tick-based + // counter which increments as soon as jQuery fires $(document).ready + now = server_date - 1, + test_start = (new Date({YEAR}, {MONTH}, {DAY}, {HOUR}, {MINUTE}, {SECOND})).getTime() / 1000, + test_time_min = {PTIME_M}, + test_time_sec = {PTIME_S}, + minute = "{STRING_MINUTE}", + minutes = "{STRING_MINUTES}", + second = "{STRING_SECOND}", + seconds = "{STRING_SECONDS}", + timeleft = "{STRING_TIMELEFT}", + redirectUrl = "{REDIRECT_URL}", + and = "{AND}", + time_left_span; + + + test_end = (new Date({ENDYEAR}, {ENDMONTH}, {ENDDAY}, {ENDHOUR}, {ENDMINUTE}, {ENDSECOND})).getTime() / 1000; + + + /** + * invoke test player's auto-save if available + */ + function autoSave() { + unsaved = false; + if (typeof il.TestPlayerQuestionEditControl !== 'undefined') { + il.TestPlayerQuestionEditControl.saveOnTimeReached(); } - else - { - serverdate++; + } + + /** + * submit form to redirectUrl + */ + function redirect() { + $("#listofquestions").attr('action', redirectUrl).submit(); + } + + /** + * Format a "time left" string from parameters provided. + * @param {Number} avail Time available in full seconds. + * @param {Number} avail_m Full minutes available. + * @param {Number} avail_s Seconds available subtracted by full minutes (i.e. avail mod 60). + * @return {String} Text telling how much time is left to finish the test in user's language + */ + function formatString(avail, avail_m, avail_s) { + var output = avail_m + " "; + if (avail_m === 1) { + output += minute; + } else { + output += minutes; } - var startd = new Date({YEAR}, {MONTH}, {DAY}, {HOUR}, {MINUTE}, {SECOND}); - var enddtime = -1; - - var endd = new Date({ENDYEAR}, {ENDMONTH}, {ENDDAY}, {ENDHOUR}, {ENDMINUTE}, {ENDSECOND}); - enddtime = endd.getTime() / 1000; - - var ptime_m = {PTIME_M}; - var ptime_s = {PTIME_S}; - var minute = "{STRING_MINUTE}"; - var minutes = "{STRING_MINUTES}"; - var second = "{STRING_SECOND}"; - var seconds = "{STRING_SECONDS}"; - var timeleft = "{STRING_TIMELEFT}"; - var redirectUrl = "{REDIRECT_URL}"; - var and = "{AND}"; - var now = serverdate; - var then = startd.getTime() / 1000; + // show seconds if less than 5min (300s) left + if (avail < 300) { + if (avail_s < 10) { + output += " " + and + " 0" + avail_s + " "; + } else { + output += " " + and + " " + avail_s + " "; + } + if (avail_m == 0) { + if (avail_s < 10) { + output = "0" + avail_s + " "; + } else { + output = avail_s + " "; + } + } + if (avail_s == 1) { + output += second; + } else { + output += seconds; + } + } + return output; + } + + /** + * Calculate remaining working time and dispatch actions based on that + */ + function setWorkingTime() { // time since start in seconds - var diff = Math.floor(now - then); - // available time - var avail = ptime_m * 60 + ptime_s - diff; - if (avail < 0) - { + var diff = Math.floor(now - test_start), + // available time + avail = test_time_min * 60 + test_time_sec - diff, + avail_m, avail_s, output; + + if (avail < 0) { avail = 0; } - if (enddtime > -1) - { - var diffToEnd = Math.floor(enddtime - now); + if (test_end > -1) { + var diffToEnd = Math.floor(test_end - now); if ((diffToEnd > 0) && (diffToEnd < avail)) { avail = diffToEnd; @@ -49,64 +108,49 @@ avail = 0; } } - if ((avail <= 0) && unsaved) - { - unsaved = false; -// fau: testNav - call saveOnTimeReached in the new control script - if (typeof il.TestPlayerQuestionEditControl != 'undefined') - { - il.TestPlayerQuestionEditControl.saveOnTimeReached(); - } -// fau. + if ((avail <= 0) && unsaved) { + autoSave(); } - if((avail <= 0) && redirectUrl != "") { - $("#listofquestions").attr('action', redirectUrl).submit(); + if((avail <= 0) && redirectUrl !== "") { + redirect(); } - var avail_m = Math.floor(avail / 60); - var avail_s = avail - (avail_m * 60); - var output = avail_m + " "; - if (avail_m == 1) - { - output += minute; - } - else - { - output += minutes; - } - if (avail < 300) - { - if (avail_s < 10) - { - output += " " + and + " 0" + avail_s + " "; - } - else - { - output += " " + and + " " + avail_s + " "; - } - if (avail_m == 0) - { - if (avail_s < 10) - { - output = "0" + avail_s + " "; - } - else - { - output = avail_s + " "; - } - } - if (avail_s == 1) - { - output += second; - } - else - { - output += seconds; + avail_m = Math.floor(avail / 60); + avail_s = avail - (avail_m * 60); + output = formatString(avail, avail_m, avail_s); + + time_left_span.html( timeleft.replace(/%s/, output) ); + } + + /** + * MUST be invoked every 1000ms in older browsers + * (SHOULD for those that support window.performance) + */ + function tick() { + if (local_timer_start >= 0) { + // use performance API + var local_timer_now = performance.now(); + if (local_timer_now >= local_timer_start) { + // floor in order to ensure the test is not submitted before end + // in which case ILIAS would display "autosave [failed|succeeded]" + // and not "the test ended ..." + now = Math.floor(server_date + (local_timer_now - local_timer_start) / 1000); + } else { + // result by performance API does not make sense, maybe it's broken + // in this browser/version or blocked by a privacy plugin + now++; } + } else { + // performance API unsupported by client + now++; } - var span = document.getElementById("timeleft"); - span.innerHTML = timeleft.replace(/%s/, output); + setWorkingTime(); } - window.setWorkingTime = setWorkingTime; - - window.setInterval('setWorkingTime()',1000); \ No newline at end of file + + $(function() { + time_left_span = $('#timeleft'); + tick(); + interval = w.setInterval(tick, 1000); + }); + +}(window, jQuery)); diff --git a/Modules/TestQuestionPool/classes/class.assClozeTest.php b/Modules/TestQuestionPool/classes/class.assClozeTest.php index 5e53392ba3cc..ffd3c7afa8df 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTest.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTest.php @@ -1257,7 +1257,7 @@ protected function isValidNumericSubmitValue($submittedValue) public function validateSolutionSubmit() { - foreach ($this->getSolutionSubmit() as $gapIndex => $value) { + foreach ($this->getSolutionSubmitValidation() as $gapIndex => $value) { $gap = $this->getGap($gapIndex); if ($gap->getType() != CLOZE_NUMERIC) { @@ -1284,7 +1284,9 @@ public function fetchSolutionSubmit($submit) $gap = $this->getGap($matches[1]); if (is_object($gap)) { if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) { - if ($gap->getType() == CLOZE_NUMERIC) { + 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; @@ -1296,7 +1298,32 @@ public function fetchSolutionSubmit($submit) 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); + if (strlen($value)) { + $gap = $this->getGap($matches[1]); + if (is_object($gap)) { + if (!(($gap->getType() == CLOZE_SELECT) && ($value == -1))) { + if ($gap->getType() == CLOZE_NUMERIC) { + $value = str_replace(",", ".", $value); + } + $solutionSubmit[trim($matches[1])] = $value; + } + } + } + } + } + + return $solutionSubmit; + } + public function getSolutionSubmit() { return $this->fetchSolutionSubmit($_POST); @@ -1951,4 +1978,9 @@ public function addAnswerOptionValue($qIndex, $answerOptionValue, $points) $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..e1c1fd869d5f 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php @@ -1588,7 +1588,7 @@ protected function completeAddAnswerAction($answers, $questionIndex) $found = false; foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $item) { - if ($ans['answer'] != $item->getAnswerText()) { + if ($ans['answer'] !== $item->getAnswerText()) { continue; } diff --git a/Modules/TestQuestionPool/classes/class.assErrorText.php b/Modules/TestQuestionPool/classes/class.assErrorText.php index 2a161f13c422..fe6efee397fa 100644 --- a/Modules/TestQuestionPool/classes/class.assErrorText.php +++ b/Modules/TestQuestionPool/classes/class.assErrorText.php @@ -814,9 +814,9 @@ public function createErrorTextExport($selections = null) $items[$idx] = $word; $counter++; } - $textarray[$textidx] = join($items, " "); + $textarray[$textidx] = join(" ", $items); } - return join($textarray, "\n"); + return join("\n", $textarray); } public function getBestSelection($withPositivePointsOnly = true) diff --git a/Modules/TestQuestionPool/classes/class.assFlashQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assFlashQuestionGUI.php index c0e95aa47c8f..f024179a302a 100644 --- a/Modules/TestQuestionPool/classes/class.assFlashQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assFlashQuestionGUI.php @@ -272,10 +272,10 @@ public function getSolutionOutput( if (count($params)) { $template->setCurrentBlock("flash_vars"); - $template->setVariable("FLASH_VARS", join($params, "&")); + $template->setVariable("FLASH_VARS", join("&", $params)); $template->parseCurrentBlock(); $template->setCurrentBlock("applet_parameters"); - $template->setVariable("PARAM_VALUE", join($params, "&")); + $template->setVariable("PARAM_VALUE", join("&", $params)); $template->parseCurrentBlock(); } if ($show_question_text == true) { @@ -309,10 +309,10 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa } if (count($params)) { $template->setCurrentBlock("flash_vars"); - $template->setVariable("FLASH_VARS", join($params, "&")); + $template->setVariable("FLASH_VARS", join("&", $params)); $template->parseCurrentBlock(); $template->setCurrentBlock("applet_parameters"); - $template->setVariable("PARAM_VALUE", join($params, "&")); + $template->setVariable("PARAM_VALUE", join("&", $params)); $template->parseCurrentBlock(); } $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); @@ -359,10 +359,10 @@ public function getTestOutput($active_id, $pass, $is_postponed = false, $use_pos if (count($params)) { $template->setCurrentBlock("flash_vars"); - $template->setVariable("FLASH_VARS", join($params, "&")); + $template->setVariable("FLASH_VARS", join("&", $params)); $template->parseCurrentBlock(); $template->setCurrentBlock("applet_parameters"); - $template->setVariable("PARAM_VALUE", join($params, "&")); + $template->setVariable("PARAM_VALUE", join("&", $params)); $template->parseCurrentBlock(); } $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); diff --git a/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php b/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php index 026843630547..c9b7590b70d5 100755 --- a/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assFormulaQuestion.php @@ -1268,7 +1268,7 @@ public function getBestSolution($solutions) $unit_factor = assFormulaQuestionUnit::lookupUnitFactor($user_solution[$result_name]['unit']); } - $user_solution[$result->getResult()]["value"] = round(ilMath::_div($resVal, $unit_factor), 55); + $user_solution[$result->getResult()]["value"] = ilMath::_div($resVal, $unit_factor, 55); } if ($result->getResultType() == assFormulaQuestionResult::RESULT_CO_FRAC || $result->getResultType() == assFormulaQuestionResult::RESULT_FRAC) { @@ -1280,15 +1280,12 @@ public function getBestSolution($solutions) $user_solution[$result->getResult()]["value"] = $value; $user_solution[$result->getResult()]["frac_helper"] = null; } - } elseif ($result->getPrecision() > 0) { - $user_solution[$result->getResult()]["value"] = round( + } else { + $user_solution[$result->getResult()]["value"] = ilMath::_div( $user_solution[$result->getResult()]["value"], + 1, $result->getPrecision() ); - } else { - $user_solution[$result->getResult()]["value"] = round( - $user_solution[$result->getResult()]["value"] - ); } } return $user_solution; diff --git a/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php index 5da9c530cf55..9ffa10e8457e 100755 --- a/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assFormulaQuestionGUI.php @@ -907,7 +907,7 @@ public function checkInput() * @param boolean $show_feedback Show the question feedback * @param boolean $show_correct_solution Show the correct solution instead of the user solution * @param boolean $show_manual_scoring Show specific information for the manual scoring output - * @return The solution output of the question as HTML code + * @return string The solution output of the question as HTML code */ public function getSolutionOutput( $active_id, @@ -932,7 +932,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); 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.assImagemapQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php index 289c9870c223..9d6dfa8613b8 100755 --- a/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php @@ -240,7 +240,7 @@ public function saveShape() $coords = ""; switch ($_POST["shape"]) { case "rect": - $coords = join($_POST['image']['mapcoords'], ","); + $coords = join(",", $_POST['image']['mapcoords']); ilUtil::sendSuccess($this->lng->txt('msg_rect_added'), true); break; case "circle": @@ -250,7 +250,7 @@ public function saveShape() ilUtil::sendSuccess($this->lng->txt('msg_circle_added'), true); break; case "poly": - $coords = join($_POST['image']['mapcoords'], ","); + $coords = join(",", $_POST['image']['mapcoords']); ilUtil::sendSuccess($this->lng->txt('msg_poly_added'), true); break; } @@ -301,9 +301,9 @@ public function areaEditor($shape = '') ilUtil::sendInfo($this->lng->txt("rectangle_click_tl_corner")); } elseif (count($coords) == 1) { ilUtil::sendInfo($this->lng->txt("rectangle_click_br_corner")); - $preview->addPoint($preview->getAreaCount(), join($coords, ","), true, "blue"); + $preview->addPoint($preview->getAreaCount(), join(",", $coords), true, "blue"); } elseif (count($coords) == 2) { - $c = join($coords, ","); + $c = join(",", $coords); $hidearea = true; $disabled_save = ""; } @@ -313,7 +313,7 @@ public function areaEditor($shape = '') ilUtil::sendInfo($this->lng->txt("circle_click_center")); } elseif (count($coords) == 1) { ilUtil::sendInfo($this->lng->txt("circle_click_circle")); - $preview->addPoint($preview->getAreaCount(), join($coords, ","), true, "blue"); + $preview->addPoint($preview->getAreaCount(), join(",", $coords), true, "blue"); } elseif (count($coords) == 2) { if (preg_match("/(\d+)\s*,\s*(\d+)\s+(\d+)\s*,\s*(\d+)/", $coords[0] . " " . $coords[1], $matches)) { $c = "$matches[1],$matches[2]," . (int) sqrt((($matches[3] - $matches[1]) * ($matches[3] - $matches[1])) + (($matches[4] - $matches[2]) * ($matches[4] - $matches[2]))); @@ -327,11 +327,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(), join(",", $coords), true, "blue"); } elseif (count($coords) > 1) { ilUtil::sendInfo($this->lng->txt("polygon_click_next_or_save")); $disabled_save = ""; - $c = join($coords, ","); + $c = join(",". $coords); } break; } diff --git a/Modules/TestQuestionPool/classes/class.assJavaApplet.php b/Modules/TestQuestionPool/classes/class.assJavaApplet.php index 161938d20451..dc40d9a50210 100755 --- a/Modules/TestQuestionPool/classes/class.assJavaApplet.php +++ b/Modules/TestQuestionPool/classes/class.assJavaApplet.php @@ -181,7 +181,7 @@ public function buildParams() array_push($params_array, "param_value_$key=" . $value["value"]); } - return join($params_array, ""); + return join("", $params_array); } /** @@ -201,7 +201,7 @@ public function buildParamsOnly() array_push($params_array, "param_name_$key=" . $value["name"]); array_push($params_array, "param_value_$key=" . $value["value"]); } - return join($params_array, ""); + return join("", $params_array); } /** diff --git a/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php b/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php index 22be37a51eec..999539678d1e 100644 --- a/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php @@ -785,8 +785,8 @@ public function getAvailableAnswerOptions($index = null) protected function calculateReachedPointsForSolution($value) { $value = $this->splitAndTrimOrderElementText($value, $this->answer_separator); - $value = join($value, $this->answer_separator); - if (strcmp($value, join($this->getOrderingElements(), $this->answer_separator)) == 0) { + $value = join($this->answer_separator, $value); + if (strcmp($value, join($this->answer_separator, $this->getOrderingElements())) == 0) { $points = $this->getPoints(); return $points; } diff --git a/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php b/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php index 611d24a4fd10..94016bfb2389 100644 --- a/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php @@ -133,7 +133,6 @@ public function getSolutionOutput( // get the solution of the user for the active pass or from the last pass if allowed $template = new ilTemplate("tpl.il_as_qpl_orderinghorizontal_output_solution.html", true, true, "Modules/TestQuestionPool"); - //$solutionvalue = ""; if (($active_id > 0) && (!$show_correct_solution)) { $elements = []; $solutions = &$this->object->getSolutionValues($active_id, $pass); @@ -151,8 +150,6 @@ public function getSolutionOutput( $template->setVariable("ELEMENT_VALUE", ilUtil::prepareFormOutput($element)); $template->parseCurrentBlock(); } - - //$solutionvalue = str_replace("{::}", " ", $solutions[0]["value1"]); } else { $elements = $this->object->getOrderingElements(); foreach ($elements as $id => $element) { @@ -161,7 +158,6 @@ public function getSolutionOutput( $template->setVariable("ELEMENT_VALUE", ilUtil::prepareFormOutput($element)); $template->parseCurrentBlock(); } - //$solutionvalue = join($this->object->getOrderingElements(), " "); } if (($active_id > 0) && (!$show_correct_solution)) { @@ -255,7 +251,7 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa $template->parseCurrentBlock(); } $template->setVariable("QUESTION_ID", $this->object->getId()); - $template->setVariable("VALUE_ORDERRESULT", ' value="' . join($elements, '{::}') . '"'); + $template->setVariable("VALUE_ORDERRESULT", ' value="' . join('{::}', $elements) . '"'); if ($this->object->textsize >= 10) { $template->setVariable("STYLE", " style=\"font-size: " . $this->object->textsize . "%;\""); } @@ -314,7 +310,7 @@ public function getTestOutput($active_id, $pass, $is_postponed = false, $use_pos if ($this->object->textsize >= 10) { $template->setVariable("STYLE", " style=\"font-size: " . $this->object->textsize . "%;\""); } - $template->setVariable("VALUE_ORDERRESULT", ' value="' . join($elements, '{::}') . '"'); + $template->setVariable("VALUE_ORDERRESULT", ' value="' . join('{::}', $elements) . '"'); $template->setVariable("QUESTIONTEXT", $this->object->prepareTextareaOutput($this->object->getQuestion(), true)); $questionoutput = $template->get(); if (!$show_question_only) { diff --git a/Modules/TestQuestionPool/classes/class.assQuestion.php b/Modules/TestQuestionPool/classes/class.assQuestion.php index 0a2a2e86de3a..1161f38bbeb5 100755 --- a/Modules/TestQuestionPool/classes/class.assQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assQuestion.php @@ -1065,7 +1065,7 @@ public function getSuggestedSolutionOutput() break; } } - return join($output, "
"); + return join("
", $output); } /** @@ -1320,7 +1320,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; } @@ -5479,4 +5479,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 d7b10aef65d9..0c043abc65d1 100755 --- a/Modules/TestQuestionPool/classes/class.assQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assQuestionGUI.php @@ -1309,6 +1309,10 @@ protected function saveTaxonomyAssignments() protected function populateTaxonomyFormSection(ilPropertyFormGUI $form) { if (count($this->getTaxonomyIds())) { + // this is needed by ilTaxSelectInputGUI in some cases + require_once 'Services/UIComponent/Overlay/classes/class.ilOverlayGUI.php'; + ilOverlayGUI::initJavaScript(); + $sectHeader = new ilFormSectionHeaderGUI(); $sectHeader->setTitle($this->lng->txt('qpl_qst_edit_form_taxonomy_section')); $form->addItem($sectHeader); diff --git a/Modules/TestQuestionPool/classes/class.ilMatchingPairWizardInputGUI.php b/Modules/TestQuestionPool/classes/class.ilMatchingPairWizardInputGUI.php index ac13674820f0..a3084f7b9850 100644 --- a/Modules/TestQuestionPool/classes/class.ilMatchingPairWizardInputGUI.php +++ b/Modules/TestQuestionPool/classes/class.ilMatchingPairWizardInputGUI.php @@ -248,7 +248,7 @@ public function insert($a_tpl) array_push($ids, $term->identifier); } $tpl->setVariable("POST_VAR", $this->getPostVar()); - $tpl->setVariable("TERM_IDS", join($ids, ",")); + $tpl->setVariable("TERM_IDS", join(",", $ids)); $tpl->parseCurrentBlock(); $tpl->setCurrentBlock('definition_ids'); @@ -257,7 +257,7 @@ public function insert($a_tpl) array_push($ids, $definition->identifier); } $tpl->setVariable("POST_VAR", $this->getPostVar()); - $tpl->setVariable("DEFINITION_IDS", join($ids, ",")); + $tpl->setVariable("DEFINITION_IDS", join(",", $ids)); $tpl->parseCurrentBlock(); $tpl->setVariable("ELEMENT_ID", $this->getPostVar()); diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php index adb15e2c3a39..cb46cc70b3ca 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assMatchingQuestionExport.php @@ -137,7 +137,7 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $attrs = array( "ident" => $definition->identifier, "match_max" => "1", - "match_group" => join($termids, ",") + "match_group" => join(",", $termids) ); $a_xml_writer->xmlStartTag("response_label", $attrs); $a_xml_writer->xmlStartTag("material"); diff --git a/Modules/TestQuestionPool/classes/forms/class.ilAssMatchingPairCorrectionsInputGUI.php b/Modules/TestQuestionPool/classes/forms/class.ilAssMatchingPairCorrectionsInputGUI.php index 57898eb9b216..a270dc08e3bb 100644 --- a/Modules/TestQuestionPool/classes/forms/class.ilAssMatchingPairCorrectionsInputGUI.php +++ b/Modules/TestQuestionPool/classes/forms/class.ilAssMatchingPairCorrectionsInputGUI.php @@ -101,7 +101,7 @@ public function insert($a_tpl) array_push($ids, $term->identifier); } $tpl->setVariable("POST_VAR", $this->getPostVar()); - $tpl->setVariable("TERM_IDS", join($ids, ",")); + $tpl->setVariable("TERM_IDS", join(",", $ids)); $tpl->parseCurrentBlock(); $tpl->setCurrentBlock('definition_ids'); @@ -110,7 +110,7 @@ public function insert($a_tpl) array_push($ids, $definition->identifier); } $tpl->setVariable("POST_VAR", $this->getPostVar()); - $tpl->setVariable("DEFINITION_IDS", join($ids, ",")); + $tpl->setVariable("DEFINITION_IDS", join(",", $ids)); $tpl->parseCurrentBlock(); $tpl->setVariable("ELEMENT_ID", $this->getPostVar()); diff --git a/Modules/TestQuestionPool/classes/tables/class.assFileUploadFileTableGUI.php b/Modules/TestQuestionPool/classes/tables/class.assFileUploadFileTableGUI.php index 3afbfd74761f..13d5067ab2f2 100644 --- a/Modules/TestQuestionPool/classes/tables/class.assFileUploadFileTableGUI.php +++ b/Modules/TestQuestionPool/classes/tables/class.assFileUploadFileTableGUI.php @@ -135,7 +135,7 @@ protected function buildFileItemContent($a_set) return ilUtil::prepareFormOutput($a_set['value2']); } - $link = ""; + $link = ""; $link .= ilUtil::prepareFormOutput($a_set['value2']) . ''; return $link; diff --git a/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js b/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js index 48fed5e51119..0d54aa201e21 100644 --- a/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js +++ b/Modules/TestQuestionPool/templates/default/cloze_gap_builder.js @@ -1132,6 +1132,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 +1226,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_content.html b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_content.html index c8daa46f9b0c..d0070414620a 100755 --- a/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_content.html +++ b/Modules/TestQuestionPool/templates/default/tpl.il_as_qpl_content.html @@ -1,7 +1,7 @@ {STATUSLINE}
-

{QUESTION_TITLE}

+

{QUESTION_TITLE}

{OBJECTIVES}
diff --git a/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_short.html b/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_short.html index 110a1ff5e0b8..e47daa832f28 100644 --- a/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_short.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_short.html @@ -2,7 +2,7 @@
  • - {ITEM} +

    {ITEM}

    {ICON_TXT} diff --git a/Modules/Test/templates/default/tpl.il_as_tst_output.html b/Modules/Test/templates/default/tpl.il_as_tst_output.html index 831f219e3752..a627bdbdd0ac 100755 --- a/Modules/Test/templates/default/tpl.il_as_tst_output.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_output.html @@ -30,14 +30,14 @@
- {QUESTION_OUTPUT} +

{QUESTION_OUTPUT}

{DISCARD_SOLUTION_MODAL} {NAV_WHILE_EDIT_MODAL} {NEXT_LOCKS_CHANGED_MODAL} {NEXT_LOCKS_UNCHANGED_MODAL}
- id="{INSTANT_RESPONSE_FOCUS_ID}">

{INSTANT_RESPONSE_HEADER}

+ id="{INSTANT_RESPONSE_FOCUS_ID}">

{INSTANT_RESPONSE_HEADER}

{RECEIVED_POINTS_INFORMATION}

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.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}
-

{HEADER}

+

{HEADER}


{BUTTONS} diff --git a/Modules/TestQuestionPool/templates/default/tpl.qpl_question_preview.html b/Modules/TestQuestionPool/templates/default/tpl.qpl_question_preview.html index 2afa3b2d7d54..c5bf9423e672 100644 --- a/Modules/TestQuestionPool/templates/default/tpl.qpl_question_preview.html +++ b/Modules/TestQuestionPool/templates/default/tpl.qpl_question_preview.html @@ -3,7 +3,7 @@
{QUESTION_OUTPUT} - id="{INSTANT_RESPONSE_FOCUS_ID}">

{INSTANT_RESPONSE_HEADER}

+ id="{INSTANT_RESPONSE_FOCUS_ID}">

{INSTANT_RESPONSE_HEADER}

{GENERIC_FEEDBACK}
diff --git a/Modules/Wiki/classes/class.ilObjWikiGUI.php b/Modules/Wiki/classes/class.ilObjWikiGUI.php index e5cc75be328d..6486dc1cc624 100755 --- a/Modules/Wiki/classes/class.ilObjWikiGUI.php +++ b/Modules/Wiki/classes/class.ilObjWikiGUI.php @@ -1648,6 +1648,7 @@ public function pdfExportObject() // :TODO: fixing css dummy parameters $html = preg_replace("/\?dummy\=[0-9]+/", "", $html); $html = preg_replace("/\?vers\=[0-9A-Za-z\-]+/", "", $html); + $html = str_replace('.css$Id$', ".css", $html); if (false) { include_once "Services/PDFGeneration/classes/class.ilPDFGeneration.php"; diff --git a/Modules/Wiki/classes/class.ilWikiDataSet.php b/Modules/Wiki/classes/class.ilWikiDataSet.php index a400a667dff5..9176443dc92b 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,32 @@ 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 +363,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 +387,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/Services/AccessControl/classes/class.ilRoleXmlImporter.php b/Services/AccessControl/classes/class.ilRoleXmlImporter.php index 206030e17adf..6e8c7cd9e8e7 100644 --- a/Services/AccessControl/classes/class.ilRoleXmlImporter.php +++ b/Services/AccessControl/classes/class.ilRoleXmlImporter.php @@ -220,22 +220,24 @@ protected function initRole($import_id) } $this->logger->debug('Searching already imported role by import_id: ' . $import_id); - $obj_id = ilObject::_lookupObjIdByImportId($import_id); + $obj_id = 0; + if ($import_id) { + $obj_id = ilObject::_lookupObjIdByImportId($import_id); + } $this->logger->debug('Found already imported obj_id: ' . $obj_id); if ($obj_id) { $this->role = ilObjectFactory::getInstanceByObjId($obj_id, false); - $this->role->setImportId((string) $import_id); } if ( (!$this->getRole() instanceof ilObjRole) && (!$this->getRole() instanceof ilObjRoleTemplate) - ){ + ) { $this->logger->debug('Creating new role template'); $this->role = new ilObjRoleTemplate(); - $this->role->setImportId((string) $import_id); } + $this->role->setImportId((string) $import_id); return true; } diff --git a/Services/Accessibility/classes/class.ilAccessibilityControlConceptGUI.php b/Services/Accessibility/classes/class.ilAccessibilityControlConceptGUI.php index 7a63c495f70a..4ee8a1c82e61 100644 --- a/Services/Accessibility/classes/class.ilAccessibilityControlConceptGUI.php +++ b/Services/Accessibility/classes/class.ilAccessibilityControlConceptGUI.php @@ -45,6 +45,7 @@ public function __construct() $lng = $DIC->language(); $http = $DIC->http(); $user = $DIC->user(); + $settings = $DIC->settings(); $accessibilityEvaluation = $DIC['acc.document.evaluator']; $this->ctrl = $ilCtrl; @@ -52,6 +53,7 @@ public function __construct() $this->lng = $lng; $this->http = $http; $this->user = $user; + $this->settings = $settings; $this->accessibilityEvaluation = $accessibilityEvaluation; $this->user->setLanguage($this->lng->getLangKey()); @@ -102,6 +104,9 @@ protected function showControlConcept() $this->user->setId(ANONYMOUS_USER_ID); } + $this->tpl->loadStandardTemplate(); + $this->tpl->setTitle($this->lng->txt("accessibility_control_concept")); + $tpl = $this->initTemplate('tpl.view_accessibility_control_concept.html'); $handleDocument = $this->accessibilityEvaluation->hasDocument(); @@ -109,11 +114,14 @@ protected function showControlConcept() $document = $this->accessibilityEvaluation->document(); $tpl->setVariable('ACCESSIBILITY_CONTROL_CONCEPT_CONTENT', $document->content()); } else { + $mails = (ilAccessibilitySupportContacts::getMailsToAddress() != "") + ? ilUtil::prepareFormOutput(ilAccessibilitySupportContacts::getMailsToAddress()) + : $this->settings->get("admin_email"); $tpl->setVariable( 'ACCESSIBILITY_CONTROL_CONCEPT_CONTENT', sprintf( $this->lng->txt('no_accessibility_control_concept_description'), - 'mailto:' . ilUtil::prepareFormOutput(ilAccessibilitySupportContacts::getMailsToAddress()) + 'mailto:' . $mails ) ); } @@ -131,6 +139,10 @@ public static function getFooterLink() global $DIC; $ilCtrl = $DIC->ctrl(); + if (!ilObjAccessibilitySettings::getControlConceptStatus()) { + return ""; + } + return $ilCtrl->getLinkTargetByClass("ilaccessibilitycontrolconceptgui"); } diff --git a/Services/Accessibility/classes/class.ilObjAccessibilitySettings.php b/Services/Accessibility/classes/class.ilObjAccessibilitySettings.php index fd65dbc9c7b0..1ec1077e5969 100755 --- a/Services/Accessibility/classes/class.ilObjAccessibilitySettings.php +++ b/Services/Accessibility/classes/class.ilObjAccessibilitySettings.php @@ -82,4 +82,28 @@ public function delete() return true; } + + /** + * @return bool + */ + public static function getControlConceptStatus() : bool + { + global $DIC; + + $settings = $DIC->settings(); + + return (bool) $settings->get('acc_ctrl_cpt_status', true); + } + + /** + * @param bool $status + */ + public static function saveControlConceptStatus(bool $status) + { + global $DIC; + + $settings = $DIC->settings(); + + $settings->set('acc_ctrl_cpt_status', (int) $status); + } } diff --git a/Services/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php b/Services/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php index f8870bb131e7..f450d35a06de 100755 --- a/Services/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php +++ b/Services/Accessibility/classes/class.ilObjAccessibilitySettingsGUI.php @@ -144,6 +144,12 @@ protected function getSettingsForm() $this->form = new ilPropertyFormGUI(); $this->form->setTitle($this->lng->txt('settings')); + $cb = new ilCheckboxInputGUI($this->lng->txt('adm_acc_ctrl_cpt_enable'), 'acc_ctrl_cpt_status'); + $cb->setValue(1); + $cb->setChecked(ilObjAccessibilitySettings::getControlConceptStatus()); + $cb->setInfo($this->lng->txt('adm_acc_ctrl_cpt_desc')); + $this->form->addItem($cb); + $ti = new ilTextInputGUI($this->lng->txt("adm_accessibility_contacts"), "accessibility_support_contacts"); $ti->setMaxLength(500); $ti->setValue(ilAccessibilitySupportContacts::getList()); @@ -184,6 +190,8 @@ public function saveAccessibilitySettings() $this->getSettingsForm(); if ($this->form->checkInput()) { + // Accessibility Control Concept status + ilObjAccessibilitySettings::saveControlConceptStatus((bool) $this->form->getInput('acc_ctrl_cpt_status')); // Accessibility support contacts ilAccessibilitySupportContacts::setList($_POST["accessibility_support_contacts"]); diff --git a/Services/Accordion/js/accordion.js b/Services/Accordion/js/accordion.js index 74597f97416b..020198dfa11f 100644 --- a/Services/Accordion/js/accordion.js +++ b/Services/Accordion/js/accordion.js @@ -171,8 +171,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 +181,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 +201,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 +233,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 +397,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..faffc707d184 100644 --- a/Services/Accordion/templates/default/tpl.accordion.html +++ b/Services/Accordion/templates/default/tpl.accordion.html @@ -1,8 +1,8 @@
-
{HEADER}
-
{CONTENT}
+
+
{CONTENT}
diff --git a/Services/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php b/Services/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php index c8f28ba510cb..9c4ed71173a9 100644 --- a/Services/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php +++ b/Services/Administration/GlobalScreen/classes/AdministrationMainBarProvider.php @@ -212,7 +212,7 @@ private function getGroups() : array array('ecss', "ltis", "cmis", "cmps", "extt"), "repository_and_objects" => array("reps", "crss", "grps", "prgs", "bibs", "blga", "chta", "facs", "frma", "lrss", - "mcts", "mobs", "svyf", "assf", "wbrs", "wiks"), + "mcts", "mobs", "svyf", "assf", "wbrs", "wiks", 'lsos'), ); $groups = []; // now get all items and groups that are accessible diff --git a/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php b/Services/AdvancedMetaData/classes/Types/class.ilAdvancedMDFieldDefinitionSelect.php index ac972bcd49e8..881dc5a0d3e7 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/Authentication/classes/Frontend/class.ilAuthFrontend.php b/Services/Authentication/classes/Frontend/class.ilAuthFrontend.php index 5cc31d3e3566..1a126bf3cd11 100644 --- a/Services/Authentication/classes/Frontend/class.ilAuthFrontend.php +++ b/Services/Authentication/classes/Frontend/class.ilAuthFrontend.php @@ -238,7 +238,7 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider $this->getLogger()->info('Authentication failed for inactive user with id and too may login attempts: ' . $this->getStatus()->getAuthenticatedUserId()); $this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID); - $this->getStatus()->setReason('err_inactive_login_attempts'); + $this->getStatus()->setReason('auth_err_login_attempts_deactivation'); return false; } 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.ilObjAuthSettingsGUI.php b/Services/Authentication/classes/class.ilObjAuthSettingsGUI.php index 20c3a7c9eb37..09d9b2a1ba85 100755 --- a/Services/Authentication/classes/class.ilObjAuthSettingsGUI.php +++ b/Services/Authentication/classes/class.ilObjAuthSettingsGUI.php @@ -714,13 +714,6 @@ protected function initAuthModeDetermination() if (is_object($this->form)) { return true; } - // Are there any authentication methods that support automatic determination ? - - include_once('Services/Authentication/classes/class.ilAuthModeDetermination.php'); - $det = ilAuthModeDetermination::_getInstance(); - if ($det->getCountActiveAuthModes() <= 1) { - return false; - } include_once('./Services/Form/classes/class.ilPropertyFormGUI.php'); $this->form = new ilPropertyFormGUI(); @@ -739,6 +732,13 @@ protected function initAuthModeDetermination() $cap->setChecked(ilCaptchaUtil::isActiveForLogin()); $this->form->addItem($cap); + // Are there any authentication methods that support automatic determination ? + include_once('Services/Authentication/classes/class.ilAuthModeDetermination.php'); + $det = ilAuthModeDetermination::_getInstance(); + if ($det->getCountActiveAuthModes() <= 1) { + return true; + } + $header = new ilFormSectionHeaderGUI(); $header->setTitle($this->lng->txt('auth_auth_mode_determination')); $this->form->addItem($header); diff --git a/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php b/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php index 4cbf484bece2..e56719c4f60c 100644 --- a/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php +++ b/Services/Awareness/GlobalScreen/classes/class.ilAwarenessMetaBarProvider.php @@ -103,7 +103,7 @@ public function getMetaBarItems() : array ->withCounter($f->counter()->status((int) $cnt)) ->withCounter($f->counter()->novelty((int) $hcnt)) ) - ->withTitle("Who is online") + ->withTitle($this->dic->language()->txt("awra")) ->withPosition(2) ->withAvailableCallable( function () use ($DIC, $online) { diff --git a/Services/COPage/classes/class.ilCOPageGlobalTemplate.php b/Services/COPage/classes/class.ilCOPageGlobalTemplate.php index 0d333282a55b..baec02d7f063 100755 --- a/Services/COPage/classes/class.ilCOPageGlobalTemplate.php +++ b/Services/COPage/classes/class.ilCOPageGlobalTemplate.php @@ -753,7 +753,7 @@ public function loadStandardTemplate() * * Will override the header_page_title. */ - public function setTitle($a_title) + public function setTitle($a_title, $hidden = false) { $this->title = $a_title; $this->header_page_title = $a_title; diff --git a/Services/COPage/classes/class.ilPCIIMTriggerEditorGUI.php b/Services/COPage/classes/class.ilPCIIMTriggerEditorGUI.php index 7af291410b64..b46dac369a9d 100755 --- a/Services/COPage/classes/class.ilPCIIMTriggerEditorGUI.php +++ b/Services/COPage/classes/class.ilPCIIMTriggerEditorGUI.php @@ -267,7 +267,7 @@ public function confirmDeleteTrigger() $ilCtrl = $this->ctrl; $tpl = $this->tpl; $lng = $this->lng; - + if (!is_array($_POST["tr"]) || count($_POST["tr"]) == 0) { ilUtil::sendFailure($lng->txt("no_checkbox"), true); $ilCtrl->redirect($this, "editMapAreas"); @@ -282,7 +282,6 @@ public function confirmDeleteTrigger() foreach ($_POST["tr"] as $i) { $cgui->addItem("tr[]", $i, $_POST["title"][$i]); } - $tpl->setContent($cgui->getHTML()); } } diff --git a/Services/COPage/classes/class.ilPCInteractiveImageGUI.php b/Services/COPage/classes/class.ilPCInteractiveImageGUI.php index 17ceda573196..fc848c8129b5 100755 --- a/Services/COPage/classes/class.ilPCInteractiveImageGUI.php +++ b/Services/COPage/classes/class.ilPCInteractiveImageGUI.php @@ -85,7 +85,9 @@ public function executeCommand() $this->pg_obj ); $ret = $this->ctrl->forwardCommand($image_map_edit); - $tpl->setContent($ret); + if ($ret != "") { + $tpl->setContent($ret); + } break; default: diff --git a/Services/COPage/classes/class.ilPCMap.php b/Services/COPage/classes/class.ilPCMap.php index d27dcec156d6..dc90ebb21e8b 100755 --- a/Services/COPage/classes/class.ilPCMap.php +++ b/Services/COPage/classes/class.ilPCMap.php @@ -147,7 +147,7 @@ public function getZoom() public function setLayout($a_width, $a_height, $a_horizontal_align) { if (is_object($this->map_node)) { - ilDomUtil::setFirstOptionalElement( + ilDOMUtil::setFirstOptionalElement( $this->dom, $this->map_node, "Layout", @@ -218,7 +218,7 @@ public function getHorizontalAlign() public function setCaption($a_caption) { if (is_object($this->map_node)) { - ilDomUtil::setFirstOptionalElement( + ilDOMUtil::setFirstOptionalElement( $this->dom, $this->map_node, "MapCaption", diff --git a/Services/COPage/classes/class.ilPCMediaObject.php b/Services/COPage/classes/class.ilPCMediaObject.php index 5115212a3b4d..638b998a421d 100755 --- a/Services/COPage/classes/class.ilPCMediaObject.php +++ b/Services/COPage/classes/class.ilPCMediaObject.php @@ -26,7 +26,12 @@ class ilPCMediaObject extends ilPageContent * @var \ILIAS\DI\UIServices */ protected $ui; - + + /** + * @var \ilLanguage + */ + protected $lng; + /** * Init page content component. */ @@ -37,6 +42,7 @@ public function init() $this->user = $DIC->user(); $this->setType("media"); $this->ui = $DIC->ui(); + $this->lng = $DIC->language(); } /** @@ -454,13 +460,16 @@ public function modifyPageContentPostXsl($a_html, $a_mode, $a_abstract_only = fa } // add fullscreen modals + $page = $this->getPage(); + $suffix = "-".$page->getParentType()."-".$page->getId(); $modal = $this->ui->factory()->modal()->roundtrip( - "Fullscreen", - $this->ui->factory()->legacy("") + $this->lng->txt("cont_fullscreen"), + $this->ui->factory()->legacy("") ); $show_signal = $modal->getShowSignal(); - return $a_html . $this->ui->renderer()->render($modal) . ""; + + return $a_html . "
" . $this->ui->renderer()->render($modal) . "
"; } /** diff --git a/Services/COPage/classes/class.ilPageObjectGUI.php b/Services/COPage/classes/class.ilPageObjectGUI.php index a4b1ad081266..c649f3a2ac68 100755 --- a/Services/COPage/classes/class.ilPageObjectGUI.php +++ b/Services/COPage/classes/class.ilPageObjectGUI.php @@ -1834,6 +1834,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() && @@ -1845,12 +1846,18 @@ public function showPage() $output = $this->obj->getRenderedContent(); } else { $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) { @@ -1859,61 +1866,63 @@ 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); + } + $output = str_replace("&", "&", $output); - // insert page snippets - //$output = $this->insertContentIncludes($output); + include_once './Services/MathJax/classes/class.ilMathJax.php'; + $output = ilMathJax::getInstance()->insertLatexImages($output); - // insert resource blocks - $output = $this->insertResources($output); + // insert page snippets + //$output = $this->insertContentIncludes($output); - // insert page toc - if ($this->getPageConfig()->getEnablePageToc()) { - $output = $this->insertPageToc($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(), $this->getAbstractOnly()); + //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()); + + // post xsl page content modification by pc elements + $output = $pc_obj->modifyPageContentPostXsl($output, $this->getOutputMode(), $this->getAbstractOnly()); + } } $this->addResourcesToTemplate($main_tpl); diff --git a/Services/COPage/css/content.css b/Services/COPage/css/content.css index ad6df04f1d1f..026f1887c111 100755 --- a/Services/COPage/css/content.css +++ b/Services/COPage/css/content.css @@ -772,7 +772,7 @@ div.ilc_va_icont_VAccordICont { padding-left: 30px; padding-top: 3px; padding-bottom: 3px; - padding-right: 03px; + padding-right: 30px; background-color: #FFFFFF; } div.ilc_va_ihead_VAccordIHead { diff --git a/Services/COPage/css/content.less b/Services/COPage/css/content.less index bb8208e02562..ffbeb9cbfff9 100644 --- a/Services/COPage/css/content.less +++ b/Services/COPage/css/content.less @@ -953,7 +953,7 @@ div.ilc_va_icont_VAccordICont padding-left: 30px; padding-top: 3px; padding-bottom: 3px; - padding-right: 03px; + padding-right: 30px; background-color: #FFFFFF; } diff --git a/Services/COPage/js/ilCOPagePres.js b/Services/COPage/js/ilCOPagePres.js index 3bf52b802df5..9b2f424669aa 100644 --- a/Services/COPage/js/ilCOPagePres.js +++ b/Services/COPage/js/ilCOPagePres.js @@ -778,17 +778,19 @@ il.COPagePres = il.COPagePres.initAudioVideo(acc_el); }, - setFullscreenModalShowSignal: function (signal) { + setFullscreenModalShowSignal: function (signal, suffix) { il.COPagePres.fullscreen_signal = signal; - $('#il-copg-mob-fullscreen').closest(".modal").on('shown.bs.modal', function () { - il.COPagePres.resizeFullScreenModal(); + il.COPagePres.fullscreen_suffix = suffix + $('#il-copg-mob-fullscreen' + suffix).closest(".modal").on('shown.bs.modal', function () { + il.COPagePres.resizeFullScreenModal(suffix); }).on('hidden.bs.modal', function () { - $("#il-copg-mob-fullscreen").attr("src", ""); + $("#il-copg-mob-fullscreen" + suffix).attr("src", ""); }); }, openFullScreenModal: function (target) { - $("#il-copg-mob-fullscreen").attr("src", target); + console.log("openFullScreenModal: " + target); + $("#il-copg-mob-fullscreen" + il.COPagePres.fullscreen_suffix).attr("src", target); $(document).trigger(il.COPagePres.fullscreen_signal, { id: il.COPagePres.fullscreen_signal, event: 'click', @@ -797,10 +799,10 @@ il.COPagePres = }); }, - resizeFullScreenModal: function () { + resizeFullScreenModal: function (suffix) { var vp = il.Util.getViewportRegion(); - var ifr = il.Util.getRegion('#il-copg-mob-fullscreen'); - $('#il-copg-mob-fullscreen').css("height", (vp.height - ifr.top + vp.top - 120) + "px"); + var ifr = il.Util.getRegion('#il-copg-mob-fullscreen' + suffix); + $('.il-copg-mob-fullscreen').css("height", (vp.height - ifr.top + vp.top - 120) + "px"); } diff --git a/Services/COPage/js/page_editing.js b/Services/COPage/js/page_editing.js index bb0e88997135..a054be5c5fe5 100644 --- a/Services/COPage/js/page_editing.js +++ b/Services/COPage/js/page_editing.js @@ -2460,6 +2460,7 @@ function editParagraph(div_id, mode, switched) $('#tinytarget_ifr').contents().find("html").attr('lang', $('html').attr('lang')); $('#tinytarget_ifr').contents().find("html").attr('dir', $('html').attr('dir')); + $('#tinytarget_ifr').contents().find("html").css("overflow", "auto"); }); } diff --git a/Services/COPage/templates/default/tpl.glossary_term_output.html b/Services/COPage/templates/default/tpl.glossary_term_output.html index b8283d9b85a7..9317a3796487 100755 --- a/Services/COPage/templates/default/tpl.glossary_term_output.html +++ b/Services/COPage/templates/default/tpl.glossary_term_output.html @@ -23,7 +23,7 @@
-

{TXT_TERM}

+

{TXT_TERM}

{TXT_DEFINITION}
diff --git a/Services/Calendar/classes/ConsultationHours/class.ilConsultationHoursGUI.php b/Services/Calendar/classes/ConsultationHours/class.ilConsultationHoursGUI.php index 9d6a84c5356d..45a36b405f18 100644 --- a/Services/Calendar/classes/ConsultationHours/class.ilConsultationHoursGUI.php +++ b/Services/Calendar/classes/ConsultationHours/class.ilConsultationHoursGUI.php @@ -322,6 +322,9 @@ protected function groupList() $ilToolbar = $DIC['ilToolbar']; $ilTabs = $DIC['ilTabs']; $tpl = $DIC['tpl']; + $ilHelp = $DIC['ilHelp']; + + $ilHelp->setScreenId("consultation_hours"); $ilToolbar->setFormAction($this->ctrl->getFormAction($this)); $ilToolbar->addButton($this->lng->txt('cal_ch_add_grp'), $this->ctrl->getLinkTarget($this, 'addGroup')); @@ -541,6 +544,9 @@ protected function bookingList() $ilToolbar = $DIC['ilToolbar']; $ilTabs = $DIC['ilTabs']; $tpl = $DIC['tpl']; + $ilHelp = $DIC['ilHelp']; + + $ilHelp->setScreenId("consultation_hours"); $this->setSubTabs(); $ilTabs->activateSubTab('cal_ch_app_bookings'); diff --git a/Services/Calendar/classes/class.ilCalendarPresentationGUI.php b/Services/Calendar/classes/class.ilCalendarPresentationGUI.php index 0b0adaf50b3a..0c68e17d827f 100644 --- a/Services/Calendar/classes/class.ilCalendarPresentationGUI.php +++ b/Services/Calendar/classes/class.ilCalendarPresentationGUI.php @@ -190,6 +190,8 @@ public function executeCommand() $this->initSeed(); $this->prepareOutput(); + + $this->help->setScreenIdComponent("cal"); switch ($cmd) { case 'selectCHCalendarOfUser': @@ -707,9 +709,6 @@ protected function prepareOutput() global $DIC; $tpl = $DIC->ui()->mainTemplate(); - $ilHelp = $this->help; - - $ilHelp->setScreenIdComponent("cal"); if ($this->category_id) { $this->addCategoryTabs(); diff --git a/Services/Calendar/templates/default/tpl.cal_selection_block_content.html b/Services/Calendar/templates/default/tpl.cal_selection_block_content.html index 68e3247cd10a..627f6bc74256 100644 --- a/Services/Calendar/templates/default/tpl.cal_selection_block_content.html +++ b/Services/Calendar/templates/default/tpl.cal_selection_block_content.html @@ -9,7 +9,7 @@
{GRP_HEAD}
- +
{IMG_ALT} @@ -17,7 +17,9 @@
{GRP_HEAD}
- {VAL_TITLE} + {PLAIN_TITLE} diff --git a/Services/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php b/Services/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php index 8bec27c54a49..7aa1b3cfc8df 100644 --- a/Services/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php +++ b/Services/Certificate/classes/Placeholder/Values/class.ilDefaultPlaceholderValues.php @@ -142,7 +142,14 @@ public function getPlaceholderValues(int $userId, int $objId) : array $placeholder['USER_FIRSTNAME'] = $this->utilHelper->prepareFormOutput((trim($user->getFirstname()))); $placeholder['USER_LASTNAME'] = $this->utilHelper->prepareFormOutput((trim($user->getLastname()))); $placeholder['USER_TITLE'] = $this->utilHelper->prepareFormOutput((trim($user->getUTitle()))); - $placeholder['USER_SALUTATION'] = $this->utilHelper->prepareFormOutput($this->language->txt("salutation_" . trim($user->getGender()))); + + $salutation = ''; + $gender = $user->getGender(); + if (is_string($gender) && strlen(trim($gender)) > 0 && strtolower($gender) !== 'n') { + $salutation = $this->utilHelper->prepareFormOutput($this->language->txt("salutation_" . trim($gender))); + } + + $placeholder['USER_SALUTATION'] = $salutation; $birthday = ''; $dateObject = $user->getBirthday(); diff --git a/Services/Certificate/classes/User/class.ilUserCertificate.php b/Services/Certificate/classes/User/class.ilUserCertificate.php index b406b18e1e47..d353fd1fb907 100644 --- a/Services/Certificate/classes/User/class.ilUserCertificate.php +++ b/Services/Certificate/classes/User/class.ilUserCertificate.php @@ -132,6 +132,30 @@ public function __construct( $this->id = $id; } + /** + * @param int $id + * @return $this + */ + public function withId(int $id) : self + { + $clone = clone $this; + $clone->id = $id; + + return $clone; + } + + /** + * @param int $version + * @return $this + */ + public function withVersion(int $version) : self + { + $clone = clone $this; + $clone->version = $version; + + return $clone; + } + /** * @return int */ diff --git a/Services/Certificate/classes/User/class.ilUserCertificateRepository.php b/Services/Certificate/classes/User/class.ilUserCertificateRepository.php index 04c4633fce29..a989fe4d68bf 100644 --- a/Services/Certificate/classes/User/class.ilUserCertificateRepository.php +++ b/Services/Certificate/classes/User/class.ilUserCertificateRepository.php @@ -52,16 +52,17 @@ public function __construct( /** * @param ilUserCertificate $userCertificate + * @return ilUserCertificate * @throws ilDatabaseException */ - public function save(ilUserCertificate $userCertificate) + public function save(ilUserCertificate $userCertificate) : ilUserCertificate { $this->logger->info('START - saving of user certificate'); - $version = $this->fetchLatestVersion($userCertificate->getObjId(), $userCertificate->getUserId()); + $version = (int) $this->fetchLatestVersion($userCertificate->getObjId(), $userCertificate->getUserId()); $version += 1; - $id = $this->database->nextId('il_cert_user_cert'); + $id = (int) $this->database->nextId('il_cert_user_cert'); $objId = $userCertificate->getObjId(); $userId = $userCertificate->getUserId(); @@ -89,6 +90,8 @@ public function save(ilUserCertificate $userCertificate) $this->logger->debug(sprintf('END - Save certificate with following values: %s', json_encode($columns, JSON_PRETTY_PRINT))); $this->database->insert('il_cert_user_cert', $columns); + + return $userCertificate->withId($id)->withVersion($version); } /** diff --git a/Services/Certificate/classes/class.ilCertificateCron.php b/Services/Certificate/classes/class.ilCertificateCron.php index a0d54e4b2f18..e6f7df09b4de 100644 --- a/Services/Certificate/classes/class.ilCertificateCron.php +++ b/Services/Certificate/classes/class.ilCertificateCron.php @@ -334,7 +334,7 @@ public function processEntry(int $entryCounter, ilCertificateQueueEntry $entry, $thumbnailImagePath ); - $this->userRepository->save($userCertificate); + $persistedUserCertificate = $this->userRepository->save($userCertificate); $succeededGenerations[] = implode('/', [ 'obj_id: ' . $objId, @@ -343,6 +343,12 @@ public function processEntry(int $entryCounter, ilCertificateQueueEntry $entry, $this->queueRepository->removeFromQueue($entry->getId()); + $this->dic->event()->raise( + 'Services/Certificate', + 'certificateIssued', + ['certificate' => $persistedUserCertificate] + ); + return $succeededGenerations; } } diff --git a/Services/Certificate/service.xml b/Services/Certificate/service.xml index 3a81bd7215a2..78ccf76fb079 100644 --- a/Services/Certificate/service.xml +++ b/Services/Certificate/service.xml @@ -13,6 +13,7 @@ + diff --git a/Services/Classification/classes/class.ilClassificationBlockGUI.php b/Services/Classification/classes/class.ilClassificationBlockGUI.php index 7fcb1782830e..2b6bc42008d6 100644 --- a/Services/Classification/classes/class.ilClassificationBlockGUI.php +++ b/Services/Classification/classes/class.ilClassificationBlockGUI.php @@ -210,9 +210,8 @@ protected function filterContainer() if ($this->repo->isEmpty()) { exit(); } - + $all_matching_provider_object_ids = null; - foreach ($this->providers as $provider) { $id = get_class($provider); $current = $this->repo->getValueForProvider($id); @@ -226,9 +225,9 @@ protected function filterContainer() } } } - $has_content = false; - + + $ltpl = new ilTemplate("tpl.classification_object_list.html", true, true, "Services/Classification"); if (is_array($all_matching_provider_object_ids) && sizeof($all_matching_provider_object_ids)) { @@ -239,9 +238,11 @@ protected function filterContainer() ,"object_data.title" ,"object_data.description" ); - //$matching = $tree->getSubTreeFilteredByObjIds($this->parent_ref_id, $all_matching_provider_object_ids, - //$fields); - $matching = $this->getSubItemIds($all_matching_provider_object_ids); + // see #28883 (tags + filter still work on current level only) + // see also JF comment on https://docu.ilias.de/goto.php?target=wiki_1357_Tagging_in_Categories + $matching = $tree->getSubTreeFilteredByObjIds($this->parent_ref_id, $all_matching_provider_object_ids, + $fields); + //$matching = $this->getSubItemIds($all_matching_provider_object_ids); if (sizeof($matching)) { $valid_objects = array(); @@ -358,7 +359,6 @@ protected function initProviders($a_check_post = false) ); } $this->providers = self::$providers_cache[$this->parent_ref_id]; - if ($a_check_post && (bool) !$_REQUEST["rdrw"]) { foreach ($this->providers as $provider) { $id = get_class($provider); diff --git a/Services/Component/classes/class.ilCachedComponentData.php b/Services/Component/classes/class.ilCachedComponentData.php index e32717d8c7cd..340e97d619b4 100644 --- a/Services/Component/classes/class.ilCachedComponentData.php +++ b/Services/Component/classes/class.ilCachedComponentData.php @@ -138,7 +138,7 @@ protected function readFromDB() $parent = $rec['parent']; $this->subobj_for_parent[$parent][] = $rec; } - $set = $ilDB->query('SELECT DISTINCT(id) AS sid, parent, il_object_def.* FROM il_object_def, il_object_subobj WHERE NOT (system = 1) AND NOT (sideblock = 1) AND subobj = id'); + $set = $ilDB->query('SELECT DISTINCT(id) AS sid, parent, il_object_def.* FROM il_object_def, il_object_subobj WHERE NOT (' . $ilDB->quoteIdentifier('system') . ' = 1) AND NOT (sideblock = 1) AND subobj = id'); while ($rec = $ilDB->fetchAssoc($set)) { $this->grouped_rep_obj_types[$rec['parent']][] = $rec; } diff --git a/Services/Conditions/classes/class.ilConditionHandler.php b/Services/Conditions/classes/class.ilConditionHandler.php index 9fb6b96ece4b..19fac592dc21 100755 --- a/Services/Conditions/classes/class.ilConditionHandler.php +++ b/Services/Conditions/classes/class.ilConditionHandler.php @@ -743,6 +743,26 @@ public function deleteCondition($a_id) return true; } + /** + * get all conditions of trigger object + * @static + * @param string $a_trigger_obj_type + * @param int $a_trigger_id + * @return int + * @throws ilDatabaseException + */ + public static function getNumberOfConditionsOfTrigger($a_trigger_obj_type, $a_trigger_id) + { + global $DIC; + $db = $DIC->database(); + + $query = 'select count(*) num from conditions ' . + 'where trigger_obj_id = ' . $db->quote($a_trigger_id, ilDBConstants::T_INTEGER) . ' ' . + 'and trigger_type = ' . $db->quote($a_trigger_obj_type, ilDBConstants::T_TEXT); + $res = $db->query($query); + $row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT); + return (int) $row->num; + } /** * Get all persisted conditions of trigger object diff --git a/Services/Contact/BuddySystem/test/ilBuddyListTest.php b/Services/Contact/BuddySystem/test/ilBuddyListTest.php index 2e8127afcba8..0d60e0707b0a 100644 --- a/Services/Contact/BuddySystem/test/ilBuddyListTest.php +++ b/Services/Contact/BuddySystem/test/ilBuddyListTest.php @@ -448,7 +448,7 @@ public function testDifferentRelationStatesCanBeRetrieved() : void * @param ilBuddySystemRelationState $state * @throws ReflectionException */ - final private function setPriorRelationState( + private function setPriorRelationState( ilBuddySystemRelation $relation, ilBuddySystemRelationState $state ) : void { diff --git a/Services/Contact/classes/class.ilContactGUI.php b/Services/Contact/classes/class.ilContactGUI.php index ea71e24b4491..de40343cb156 100644 --- a/Services/Contact/classes/class.ilContactGUI.php +++ b/Services/Contact/classes/class.ilContactGUI.php @@ -374,7 +374,6 @@ protected function mailToUsers() $mail_data['rcp_to'], $mail_data['rcp_cc'], $mail_data['rcp_bcc'], - $mail_data['m_type'], $mail_data['m_email'], $mail_data['m_subject'], $mail_data['m_message'], diff --git a/Services/Contact/classes/class.ilMailSearchCoursesGUI.php b/Services/Contact/classes/class.ilMailSearchCoursesGUI.php index bc2a33728023..ddc80ee9996d 100644 --- a/Services/Contact/classes/class.ilMailSearchCoursesGUI.php +++ b/Services/Contact/classes/class.ilMailSearchCoursesGUI.php @@ -207,7 +207,6 @@ public function mailCourses() $mail_data["rcp_to"], $mail_data["rcp_cc"], $mail_data["rcp_bcc"], - $mail_data["m_type"], $mail_data["m_email"], $mail_data["m_subject"], $mail_data["m_message"], @@ -253,7 +252,6 @@ public function mailMembers() $mail_data["rcp_to"], $mail_data["rcp_cc"], $mail_data["rcp_bcc"], - $mail_data["m_type"], $mail_data["m_email"], $mail_data["m_subject"], $mail_data["m_message"], diff --git a/Services/Contact/classes/class.ilMailSearchGUI.php b/Services/Contact/classes/class.ilMailSearchGUI.php index 4c357bc4a9fa..09997652da3c 100644 --- a/Services/Contact/classes/class.ilMailSearchGUI.php +++ b/Services/Contact/classes/class.ilMailSearchGUI.php @@ -134,7 +134,6 @@ private function saveMailData() $mail_data["rcp_to"], $mail_data["rcp_cc"], $mail_data["rcp_bcc"], - $mail_data["m_type"], $mail_data["m_email"], $mail_data["m_subject"], $mail_data["m_message"], @@ -365,7 +364,7 @@ public function showResults() $query_parser->setMinWordLength(3); $query_parser->parse(); - $user_search = &ilObjectSearchFactory::_getUserSearchInstance($query_parser); + $user_search = ilObjectSearchFactory::_getUserSearchInstance($query_parser); $user_search->enableActiveCheck(true); $user_search->setFields(array('login')); $result_obj = $user_search->performSearch(); diff --git a/Services/Contact/classes/class.ilMailSearchGroupsGUI.php b/Services/Contact/classes/class.ilMailSearchGroupsGUI.php index e2a55fa91a40..3e3f8bf53254 100644 --- a/Services/Contact/classes/class.ilMailSearchGroupsGUI.php +++ b/Services/Contact/classes/class.ilMailSearchGroupsGUI.php @@ -203,7 +203,6 @@ public function mailGroups() $mail_data["rcp_to"], $mail_data["rcp_cc"], $mail_data["rcp_bcc"], - $mail_data["m_type"], $mail_data["m_email"], $mail_data["m_subject"], $mail_data["m_message"], @@ -248,7 +247,6 @@ public function mailMembers() $mail_data["rcp_to"], $mail_data["rcp_cc"], $mail_data["rcp_bcc"], - $mail_data["m_type"], $mail_data["m_email"], $mail_data["m_subject"], $mail_data["m_message"], diff --git a/Services/Contact/classes/class.ilMailingListsGUI.php b/Services/Contact/classes/class.ilMailingListsGUI.php index e37d53187424..eace3eb77c85 100644 --- a/Services/Contact/classes/class.ilMailingListsGUI.php +++ b/Services/Contact/classes/class.ilMailingListsGUI.php @@ -195,7 +195,6 @@ public function mailToList() $mail_data['rcp_to'], $mail_data['rcp_cc'], $mail_data['rcp_bcc'], - $mail_data['m_type'], $mail_data['m_email'], $mail_data['m_subject'], $mail_data['m_message'], diff --git a/Services/Container/classes/class.ilContainerContentGUI.php b/Services/Container/classes/class.ilContainerContentGUI.php index 3b6cfe1758a4..57067ae69c82 100644 --- a/Services/Container/classes/class.ilContainerContentGUI.php +++ b/Services/Container/classes/class.ilContainerContentGUI.php @@ -119,7 +119,7 @@ public function __construct(&$container_gui_obj) $this->log = ilLoggerFactory::getLogger('cont'); - $this->view_mode = (ilContainer::_lookupContainerSetting($this->container_obj->getId(), "list_presentation") == "tile" && !$this->container_gui->isActiveAdministrationPanel()) + $this->view_mode = (ilContainer::_lookupContainerSetting($this->container_obj->getId(), "list_presentation") == "tile" && !$this->container_gui->isActiveAdministrationPanel() && !$this->container_gui->isActiveOrdering()) ? self::VIEW_MODE_TILE : self::VIEW_MODE_LIST; } diff --git a/Services/Container/classes/class.ilContainerExporter.php b/Services/Container/classes/class.ilContainerExporter.php index 576d9f325db4..30746b8abe98 100644 --- a/Services/Container/classes/class.ilContainerExporter.php +++ b/Services/Container/classes/class.ilContainerExporter.php @@ -95,6 +95,13 @@ public function getXmlExportTailDependencies($a_entity, $a_target_release, $a_id "entity" => "common", "ids" => $a_ids); + // news settings + $res[] = [ + "component" => "Services/News", + "entity" => "news_settings", + "ids" => $a_ids + ]; + return $res; } diff --git a/Services/Container/classes/class.ilContainerGUI.php b/Services/Container/classes/class.ilContainerGUI.php index accedcdb6099..de719893bd65 100644 --- a/Services/Container/classes/class.ilContainerGUI.php +++ b/Services/Container/classes/class.ilContainerGUI.php @@ -352,7 +352,6 @@ public function &forwardToPageObject() $page_gui->setTemplateTargetVar("ADM_CONTENT"); $page_gui->setFileDownloadLink(""); - $page_gui->setFullscreenLink($this->ctrl->getLinkTarget($this, "showMediaFullscreen")); //$page_gui->setLinkParams($this->ctrl->getUrlParameterString()); // todo $page_gui->setPresentationTitle(""); $page_gui->setTemplateOutput(false); @@ -527,7 +526,9 @@ public function showTreeFlatIcon() */ public function setTitleAndDescription() { - if (!ilContainer::_lookupContainerSetting($this->object->getId(), "hide_header_icon_and_title")) { + if (ilContainer::_lookupContainerSetting($this->object->getId(), "hide_header_icon_and_title")) { + $this->tpl->setTitle($this->object->getTitle(), true); + } else { $this->tpl->setTitle($this->object->getTitle()); $this->tpl->setDescription($this->object->getLongDescription()); @@ -1601,7 +1602,7 @@ public function copyObject() $ilCtrl->setParameterByClass("ilobjectcopygui", "source_id", $_POST["id"][0]); $ilCtrl->redirectByClass("ilobjectcopygui", "initTargetSelection"); } else { - $ilCtrl->setParameterByClass("ilobjectcopygui", "source_ids", implode($_POST["id"], "_")); + $ilCtrl->setParameterByClass("ilobjectcopygui", "source_ids", implode("_", $_POST["id"])); $ilCtrl->redirectByClass("ilobjectcopygui", "initTargetSelection"); } @@ -2288,7 +2289,7 @@ public function pasteObject() $ilCtrl->redirectByClass("ilobjectcopygui", "saveTarget"); } else { $ilCtrl->setParameterByClass("ilobjectcopygui", "target", $this->object->getRefId()); - $ilCtrl->setParameterByClass("ilobjectcopygui", "source_ids", implode($ref_ids, "_")); + $ilCtrl->setParameterByClass("ilobjectcopygui", "source_ids", implode("_", $ref_ids)); $ilCtrl->redirectByClass("ilobjectcopygui", "saveTarget"); } diff --git a/Services/Container/classes/class.ilContainerNewsSettingsGUI.php b/Services/Container/classes/class.ilContainerNewsSettingsGUI.php index e39fd140ce90..d006bf40ff30 100644 --- a/Services/Container/classes/class.ilContainerNewsSettingsGUI.php +++ b/Services/Container/classes/class.ilContainerNewsSettingsGUI.php @@ -118,7 +118,6 @@ public function show() public function initForm() { $form = new ilPropertyFormGUI(); - //from crs/grp/cat settings - additional feature - news if ($this->setting->get('block_activated_news')) { @@ -246,7 +245,6 @@ public function save() $this->object->setNewsTimelineAutoEntries($form->getInput("news_timeline_auto_entries")); $this->object->setNewsTimelineLandingPage($form->getInput("news_timeline_landing_page")); } - if ($this->setting->get('block_activated_news')) { //save contextblock settings $context_block_settings = array( diff --git a/Services/Container/classes/class.ilContainerSorting.php b/Services/Container/classes/class.ilContainerSorting.php index 5041ca751b2e..25ae22f6979b 100644 --- a/Services/Container/classes/class.ilContainerSorting.php +++ b/Services/Container/classes/class.ilContainerSorting.php @@ -154,10 +154,10 @@ public function cloneSorting($a_target_id, $a_copy_id) return $block_id; }, explode(";", $rec["block_ids"]))); - $ilDB->insert("container_sorting_bl", array( - "obj_id" => array("integer", $target_obj_id), - "block_ids" => array("text", $new_ids) - )); + $ilDB->replace("container_sorting_bl", + array("obj_id" => array("integer", $target_obj_id)), + array("block_ids" => array("text", $new_ids)) + ); $ilLog->debug("Write block sorting for obj_id = " . $target_obj_id . ": " . $new_ids); } diff --git a/Services/Container/templates/default/tpl.container_list_block.html b/Services/Container/templates/default/tpl.container_list_block.html index 6e366c1dfc0a..3614aa08461e 100644 --- a/Services/Container/templates/default/tpl.container_list_block.html +++ b/Services/Container/templates/default/tpl.container_list_block.html @@ -11,7 +11,7 @@

{BLOCK_HEADER_CONTENT}

-

{BLOCK_HEADER_CONTENT}

+

{BLOCK_HEADER_CONTENT}

{CHR_COMMANDS}
diff --git a/Services/Container/templates/default/tpl.container_list_item.html b/Services/Container/templates/default/tpl.container_list_item.html index 2eca8ed4f163..bb364566edad 100644 --- a/Services/Container/templates/default/tpl.container_list_item.html +++ b/Services/Container/templates/default/tpl.container_list_item.html @@ -11,10 +11,10 @@ -

{TXT_TITLE}

+

{TXT_TITLE}

-

target="{TARGET_TITLE_LINKED}" >{TXT_TITLE_LINKED} {ALT_PREVIEW_ICON}

+

target="{TARGET_TITLE_LINKED}" >{TXT_TITLE_LINKED} {ALT_PREVIEW_ICON}

diff --git a/Services/ContainerReference/classes/class.ilContainerReferenceImporter.php b/Services/ContainerReference/classes/class.ilContainerReferenceImporter.php index c5b11fe54b98..f9b1dbad850c 100644 --- a/Services/ContainerReference/classes/class.ilContainerReferenceImporter.php +++ b/Services/ContainerReference/classes/class.ilContainerReferenceImporter.php @@ -76,7 +76,11 @@ public function importXmlRepresentation($a_entity, $a_id, $a_xml, $a_mapping) } try { + /** + * @var $parser ilContainerReferenceXmlParser + */ $parser = $this->initParser($a_xml); + $parser->setImportMapping($a_mapping); $parser->setReference($this->getReference()); $parser->setMode(ilContainerReferenceXmlParser::MODE_UPDATE); $parser->startParsing(); diff --git a/Services/ContainerReference/classes/class.ilContainerReferenceXmlParser.php b/Services/ContainerReference/classes/class.ilContainerReferenceXmlParser.php index 55c6e5ded638..565afe8e9f1b 100644 --- a/Services/ContainerReference/classes/class.ilContainerReferenceXmlParser.php +++ b/Services/ContainerReference/classes/class.ilContainerReferenceXmlParser.php @@ -47,6 +47,16 @@ class ilContainerReferenceXmlParser extends ilSaxParser private $ref = null; private $parent_id = 0; + + /** + * @var ilLogger + */ + protected $logger; + + /** + * @var ilImportMapping + */ + protected $import_mapping; /** * Constructor @@ -65,6 +75,16 @@ public function __construct($a_xml, $a_parent_id = 0) $this->mode = ilContainerReferenceXmlParser::MODE_CREATE; $this->setXMLContent($a_xml); + + $this->logger = $DIC->logger()->exp(); + } + + /** + * @param ilImportMapping $mapping + */ + public function setImportMapping(ilImportMapping $mapping) + { + $this->import_mapping = $mapping; } /** @@ -126,11 +146,39 @@ public function handlerBeginTag($a_xml_parser, $a_name, $a_attribs) break; case 'Target': - $this->getReference()->setTargetId($a_attribs['id']); + $target_id = $this->parseTargetId(isset($a_attribs['id']) ? (string) $a_attribs['id'] : ''); + if ($target_id) { + $this->logger->debug('Using mapped target_id: ' . $target_id); + $this->getReference()->setTargetId($target_id); + } else { + $this->logger->info('No mapping found for: ' . $a_attribs['id']); + $this->getReference()->setTargetId(0); + } break; } } + /** + * @param string $attribute_target + * @return int + */ + protected function parseTargetId(string $attribute_target) : int + { + if (!strlen($attribute_target)) { + $this->logger->debug('No target id provided'); + return 0; + } + if (!$this->import_mapping instanceof ilImportMapping) { + return 0; + } + $obj_mapping_id = $this->import_mapping->getMapping('Services/Container', 'objs', $attribute_target); + if (!$obj_mapping_id) { + $this->logger->debug('Cannot find object mapping for target_id: ' . $attribute_target); + return 0; + } + return $obj_mapping_id; + } + /** * Handler end tag diff --git a/Services/Dashboard/ItemsBlock/classes/class.ilPDSelectedItemsBlockViewGUI.php b/Services/Dashboard/ItemsBlock/classes/class.ilPDSelectedItemsBlockViewGUI.php index 3652abf29371..912169f0534a 100644 --- a/Services/Dashboard/ItemsBlock/classes/class.ilPDSelectedItemsBlockViewGUI.php +++ b/Services/Dashboard/ItemsBlock/classes/class.ilPDSelectedItemsBlockViewGUI.php @@ -44,7 +44,7 @@ abstract class ilPDSelectedItemsBlockViewGUI * @param ilPDSelectedItemsBlockViewSettings $viewSettings * @param ilPDSelectedItemsBlockProvider $provider */ - final private function __construct(ilPDSelectedItemsBlockViewSettings $viewSettings, ilPDSelectedItemsBlockProvider $provider) + private function __construct(ilPDSelectedItemsBlockViewSettings $viewSettings, ilPDSelectedItemsBlockProvider $provider) { global $DIC; diff --git a/Services/Database/classes/PDO/class.ilDBPdo.php b/Services/Database/classes/PDO/class.ilDBPdo.php index d8eb34c1738b..92793fc0ea3d 100644 --- a/Services/Database/classes/PDO/class.ilDBPdo.php +++ b/Services/Database/classes/PDO/class.ilDBPdo.php @@ -883,7 +883,7 @@ public function addIndex($table_name, $fields, $index_name = '', $fulltext = fal public function addFulltextIndex($a_table, $a_fields, $a_name = "in") { $i_name = $this->constraintName($a_table, $a_name) . "_idx"; - $f_str = implode($a_fields, ","); + $f_str = implode(",", $a_fields); $q = "ALTER TABLE $a_table ADD FULLTEXT $i_name ($f_str)"; $this->query($q); } diff --git a/Services/Database/classes/PDO/class.ilDBPdoPostgreSQL.php b/Services/Database/classes/PDO/class.ilDBPdoPostgreSQL.php index a19b92ef7917..31c07f30fea2 100644 --- a/Services/Database/classes/PDO/class.ilDBPdoPostgreSQL.php +++ b/Services/Database/classes/PDO/class.ilDBPdoPostgreSQL.php @@ -173,7 +173,7 @@ public function replace($a_table, $a_pk_columns, $a_other_columns) } // if ($lobs) // lobs -> use prepare execute (autoexecute broken in PEAR 2.4.1) // { - $this->manipulate("DELETE FROM " . $a_table . " WHERE " . implode($delwhere, " AND ")); + $this->manipulate("DELETE FROM " . $a_table . " WHERE " . implode(" AND ", $delwhere)); $this->insert($a_table, $a_columns); return true; diff --git a/Services/Database/classes/Setup/class.ilDatabaseServerIsConnectableObjective.php b/Services/Database/classes/Setup/class.ilDatabaseServerIsConnectableObjective.php index b5e81467edad..967df23094f4 100644 --- a/Services/Database/classes/Setup/class.ilDatabaseServerIsConnectableObjective.php +++ b/Services/Database/classes/Setup/class.ilDatabaseServerIsConnectableObjective.php @@ -8,12 +8,13 @@ class ilDatabaseServerIsConnectableObjective extends \ilDatabaseObjective { public function getHash() : string { + $pw = $this->config->getPassword(); return hash("sha256", implode("-", [ self::class, $this->config->getHost(), $this->config->getPort(), $this->config->getUser(), - $this->config->getPassword()->toString() + $pw ? $pw->toString() : "" ])); } diff --git a/Services/Database/classes/Setup/class.ilDatabaseSetupConfig.php b/Services/Database/classes/Setup/class.ilDatabaseSetupConfig.php index 58322f42aeee..8a1f9efb307c 100644 --- a/Services/Database/classes/Setup/class.ilDatabaseSetupConfig.php +++ b/Services/Database/classes/Setup/class.ilDatabaseSetupConfig.php @@ -160,7 +160,8 @@ public function readVariable($a_group, $a_var_name) case "port": return $this->config->getPort(); case "pass": - return $this->config->getPassword()->toString(); + $pw = $this->config->getPassword(); + return $pw ? $pw->toString() : null; case "name": return $this->config->getDatabase(); case "type": diff --git a/Services/Database/classes/class.ilDBGenerator.php b/Services/Database/classes/class.ilDBGenerator.php index 91ca76eb1d75..71723e6d47a7 100644 --- a/Services/Database/classes/class.ilDBGenerator.php +++ b/Services/Database/classes/class.ilDBGenerator.php @@ -698,11 +698,11 @@ public function buildInsertStatements($a_table, $a_file = "") $values[] = "'" . str_replace("'", "\'", $v) . "'"; $i_str[] = "'" . $f . "' => array('" . $this->fields[$f]["type"] . "', '" . str_replace("'", "\'", $v) . "')"; } - $fields_str = "(" . implode($fields, ",") . ")"; - $types_str = "array(" . implode($types, ",") . ")"; - $values_str = "array(" . implode($values, ",") . ")"; + $fields_str = "(" . implode(",", $fields) . ")"; + $types_str = "array(" . implode(",", $types) . ")"; + $values_str = "array(" . implode(",", $values) . ")"; $ins_st = "\n" . '$ilDB->insert("' . $a_table . '", array(' . "\n"; - $ins_st .= implode($i_str, ", ") . "));\n"; + $ins_st .= implode(", ", $i_str) . "));\n"; //$ins_st.= "\t".$fields_str."\n"; //$ins_st.= "\t".'VALUES '."(%s".str_repeat(",%s", count($fields) - 1).')"'.",\n"; //$ins_st.= "\t".$types_str.','.$values_str.');'."\n"; @@ -776,7 +776,7 @@ public function addTableToOverview($a_table, $a_tpl, $a_cnt) } $a_tpl->setCurrentBlock("index"); $a_tpl->setVariable("VAL_INDEX", $def["name"]); - $a_tpl->setVariable("VAL_FIELDS", implode($f2, ", ")); + $a_tpl->setVariable("VAL_FIELDS", implode(", ", $f2)); $a_tpl->parseCurrentBlock(); $indices_output = true; } @@ -795,7 +795,7 @@ public function addTableToOverview($a_table, $a_tpl, $a_cnt) $a_tpl->setCurrentBlock("constraint"); $a_tpl->setVariable("VAL_CONSTRAINT", $def["name"]); $a_tpl->setVariable("VAL_CTYPE", $def["type"]); - $a_tpl->setVariable("VAL_CFIELDS", implode($f2, ", ")); + $a_tpl->setVariable("VAL_CFIELDS", implode(", ", $f2)); $a_tpl->parseCurrentBlock(); $constraints_output = true; } diff --git a/Services/Exceptions/classes/class.ilPlainTextHandler.php b/Services/Exceptions/classes/class.ilPlainTextHandler.php index c924378eb1ab..3a669f22eb5c 100644 --- a/Services/Exceptions/classes/class.ilPlainTextHandler.php +++ b/Services/Exceptions/classes/class.ilPlainTextHandler.php @@ -110,6 +110,7 @@ protected function tables() $server = $_SERVER; $post = $this->hidePassword($post); + $server = $this->hidePassword($server); $server = $this->shortenPHPSessionId($server); return array( "GET Data" => $_GET @@ -123,19 +124,22 @@ protected function tables() } /** - * Replace passwort from post array with security message - * - * @param array $post + * Replace password from super global array with security message * + * @param array $superGlobal * @return array */ - private function hidePassword(array $post) + private function hidePassword(array $superGlobal) : array { - if (isset($post["password"])) { - $post["password"] = "REMOVED FOR SECURITY"; + if (isset($superGlobal["password"])) { + $superGlobal["password"] = "REMOVED FOR SECURITY"; + } + + if (isset($superGlobal["post_vars"]) && isset($superGlobal["post_vars"]["password"])) { + $superGlobal["post_vars"]["password"] = "REMOVED FOR SECURITY"; } - return $post; + return $superGlobal; } /** diff --git a/Services/Feeds/magpierss/extlib/Snoopy.class.inc b/Services/Feeds/magpierss/extlib/Snoopy.class.inc index 3ddecbab064d..851644a95c25 100644 --- a/Services/Feeds/magpierss/extlib/Snoopy.class.inc +++ b/Services/Feeds/magpierss/extlib/Snoopy.class.inc @@ -1,12 +1,11 @@ -Copyright (c): 1999-2000 ispi, all rights reserved -Version: 1.0 - + * + * Snoopy - the PHP net client + * Author: Monte Ohrt + * Copyright (c): 1999-2014, all rights reserved + * Version: 2.00 * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -20,881 +19,1047 @@ Version: 1.0 * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * You may contact the author of Snoopy by e-mail at: + * monte@ohrt.com + * + * The latest version of Snoopy can be obtained from: + * http://snoopy.sourceforge.net/ + *************************************************/ +class Snoopy +{ + /**** Public variables ****/ -You may contact the author of Snoopy by e-mail at: -monte@ispi.net + /* user definable vars */ -Or, write to: -Monte Ohrt -CTO, ispi -237 S. 70th suite 220 -Lincoln, NE 68510 + public $scheme = 'http'; // http or https + public $host = "www.php.net"; // host name we are connecting to + public $port = 80; // port we are connecting to + public $proxy_host = ""; // proxy host to use + public $proxy_port = ""; // proxy port to use + public $proxy_user = ""; // proxy user to use + public $proxy_pass = ""; // proxy password to use -The latest version of Snoopy can be obtained from: -http://snoopy.sourceforge.com + public $agent = "Snoopy v1.33-dev"; // agent we masquerade as + public $referer = ""; // referer info to pass + public $cookies = array(); // array of cookies to pass + // $cookies["username"]="joe"; + public $rawheaders = array(); // array of raw headers to send + // $rawheaders["Content-type"]="text/html"; -*************************************************/ + public $maxredirs = 5; // http redirection depth maximum. 0 = disallow + public $lastredirectaddr = ""; // contains address of last redirected address + public $offsiteok = true; // allows redirection off-site + public $maxframes = 0; // frame content depth maximum. 0 = disallow + public $expandlinks = true; // expand links to fully qualified URLs. + // this only applies to fetchlinks() + // submitlinks(), and submittext() + public $passcookies = true; // pass set cookies back through redirects + // NOTE: this currently does not respect + // dates, domains or paths. -class Snoopy -{ - /**** Public variables ****/ - - /* user definable vars */ - - var $host = "www.php.net"; // host name we are connecting to - var $port = 80; // port we are connecting to - var $proxy_host = ""; // proxy host to use - var $proxy_port = ""; // proxy port to use - var $agent = "Snoopy v1.0"; // agent we masquerade as - var $referer = ""; // referer info to pass - var $cookies = array(); // array of cookies to pass - // $cookies["username"]="joe"; - var $rawheaders = array(); // array of raw headers to send - // $rawheaders["Content-type"]="text/html"; - - var $maxredirs = 5; // http redirection depth maximum. 0 = disallow - var $lastredirectaddr = ""; // contains address of last redirected address - var $offsiteok = true; // allows redirection off-site - var $maxframes = 0; // frame content depth maximum. 0 = disallow - var $expandlinks = true; // expand links to fully qualified URLs. - // this only applies to fetchlinks() - // or submitlinks() - var $passcookies = true; // pass set cookies back through redirects - // NOTE: this currently does not respect - // dates, domains or paths. - - var $user = ""; // user for http authentication - var $pass = ""; // password for http authentication - - // http accept types - var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; - - var $results = ""; // where the content is put - - var $error = ""; // error messages sent here - var $response_code = ""; // response code returned from server - var $headers = array(); // headers returned from server sent here - var $maxlength = 500000; // max return data length (body) - var $read_timeout = 0; // timeout on read operations, in seconds - // supported only since PHP 4 Beta 4 - // set to 0 to disallow timeouts - var $timed_out = false; // if a read operation timed out - var $status = 0; // http request status - - var $curl_path = "/usr/bin/curl"; - // Snoopy will use cURL for fetching - // SSL content if a full system path to - // the cURL binary is supplied here. - // set to false if you do not have - // cURL installed. See http://curl.haxx.se - // for details on installing cURL. - // Snoopy does *not* use the cURL - // library functions built into php, - // as these functions are not stable - // as of this Snoopy release. - - // send Accept-encoding: gzip? - var $use_gzip = true; - - /**** Private variables ****/ - - var $_maxlinelen = 4096; // max line length (headers) - - var $_httpmethod = "GET"; // default http request method - var $_httpversion = "HTTP/1.0"; // default http request version - var $_submit_method = "POST"; // default submit method - var $_submit_type = "application/x-www-form-urlencoded"; // default submit type - var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type - var $_redirectaddr = false; // will be set if page fetched is a redirect - var $_redirectdepth = 0; // increments on an http redirect - var $_frameurls = array(); // frame src urls - var $_framedepth = 0; // increments on frame depth - - var $_isproxy = false; // set if using a proxy server - var $_fp_timeout = 30; // timeout for socket connection - -/*======================================================================*\ - Function: fetch - Purpose: fetch the contents of a web page - (and possibly other protocols in the - future like ftp, nntp, gopher, etc.) - Input: $URI the location of the page to fetch - Output: $this->results the output text from the fetch -\*======================================================================*/ - - function fetch($URI) - { - - //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS); - $URI_PARTS = parse_url($URI); - if (!empty($URI_PARTS["user"])) - $this->user = $URI_PARTS["user"]; - if (!empty($URI_PARTS["pass"])) - $this->pass = $URI_PARTS["pass"]; - - switch($URI_PARTS["scheme"]) - { - case "http": - $this->host = $URI_PARTS["host"]; - if(!empty($URI_PARTS["port"])) - $this->port = $URI_PARTS["port"]; - if($this->_connect($fp)) - { - if($this->_isproxy) - { - // using proxy, send entire URI - $this->_httprequest($URI,$fp,$URI,$this->_httpmethod); - } - else - { - $path = $URI_PARTS["path"].(isset($URI_PARTS["query"]) ? "?".$URI_PARTS["query"] : ""); - // no proxy, send only the path - $this->_httprequest($path, $fp, $URI, $this->_httpmethod); - } - - $this->_disconnect($fp); - - if($this->_redirectaddr) - { - /* url was redirected, check if we've hit the max depth */ - if($this->maxredirs > $this->_redirectdepth) - { - // only follow redirect if it's on this site, or offsiteok is true - if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) - { - /* follow the redirect */ - $this->_redirectdepth++; - $this->lastredirectaddr=$this->_redirectaddr; - $this->fetch($this->_redirectaddr); - } - } - } - - if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) - { - $frameurls = $this->_frameurls; - $this->_frameurls = array(); - - while(list(,$frameurl) = each($frameurls)) - { - if($this->_framedepth < $this->maxframes) - { - $this->fetch($frameurl); - $this->_framedepth++; - } - else - break; - } - } - } - else - { - return false; - } - return true; - break; - case "https": - if(!$this->curl_path || (!is_executable($this->curl_path))) { - $this->error = "Bad curl ($this->curl_path), can't fetch HTTPS \n"; - return false; - } - $this->host = $URI_PARTS["host"]; - if(!empty($URI_PARTS["port"])) - $this->port = $URI_PARTS["port"]; - if($this->_isproxy) - { - // using proxy, send entire URI - $this->_httpsrequest($URI,$URI,$this->_httpmethod); - } - else - { - $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); - // no proxy, send only the path - $this->_httpsrequest($path, $URI, $this->_httpmethod); - } - - if($this->_redirectaddr) - { - /* url was redirected, check if we've hit the max depth */ - if($this->maxredirs > $this->_redirectdepth) - { - // only follow redirect if it's on this site, or offsiteok is true - if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) - { - /* follow the redirect */ - $this->_redirectdepth++; - $this->lastredirectaddr=$this->_redirectaddr; - $this->fetch($this->_redirectaddr); - } - } - } - - if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) - { - $frameurls = $this->_frameurls; - $this->_frameurls = array(); - - while(list(,$frameurl) = each($frameurls)) - { - if($this->_framedepth < $this->maxframes) - { - $this->fetch($frameurl); - $this->_framedepth++; - } - else - break; - } - } - return true; - break; - default: - // not a valid protocol - $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; - return false; - break; - } - return true; - } - - - -/*======================================================================*\ - Private functions -\*======================================================================*/ - - -/*======================================================================*\ - Function: _striplinks - Purpose: strip the hyperlinks from an html document - Input: $document document to strip. - Output: $match an array of the links -\*======================================================================*/ - - function _striplinks($document) - { - preg_match_all("'<\s*a\s+.*href\s*=\s* # find results the output text from the fetch + \*======================================================================*/ + + public function fetch($URI) + { + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) { + $this->user = $URI_PARTS["user"]; + } + if (!empty($URI_PARTS["pass"])) { + $this->pass = $URI_PARTS["pass"]; + } + if (empty($URI_PARTS["query"])) { + $URI_PARTS["query"] = ''; + } + if (empty($URI_PARTS["path"])) { + $URI_PARTS["path"] = ''; + } + + $fp = null; + + switch (strtolower($URI_PARTS["scheme"])) { + case "https": + if (!extension_loaded('openssl')) { + trigger_error("openssl extension required for HTTPS", E_USER_ERROR); + exit; + } + $this->port = 443; + // no break + case "http": + $this->scheme = strtolower($URI_PARTS["scheme"]); + $this->host = $URI_PARTS["host"]; + if (!empty($URI_PARTS["port"])) { + $this->port = $URI_PARTS["port"]; + } + if ($this->_connect($fp)) { + if ($this->_isproxy) { + // using proxy, send entire URI + $this->_httprequest($URI, $fp, $URI, $this->_httpmethod); + } else { + $path = $URI_PARTS["path"] . ($URI_PARTS["query"] ? "?" . $URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_httpmethod); + } + + $this->_disconnect($fp); + + if ($this->_redirectaddr) { + /* url was redirected, check if we've hit the max depth */ + if ($this->maxredirs > $this->_redirectdepth) { + // only follow redirect if it's on this site, or offsiteok is true + if (preg_match("|^https?://" . preg_quote($this->host) . "|i", $this->_redirectaddr) || $this->offsiteok) { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr = $this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if ($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + foreach ($frameurls as $frameurl) { + if ($this->_framedepth < $this->maxframes) { + $this->fetch($frameurl); + $this->_framedepth++; + } else { + break; + } + } + } + } else { + return false; + } + return $this; + break; + default: + // not a valid protocol + $this->error = 'Invalid protocol "' . $URI_PARTS["scheme"] . '"\n'; + return false; + break; + } + return $this; + } + + /*======================================================================*\ + Function: submit + Purpose: submit an http(s) form + Input: $URI the location to post the data + $formvars the formvars to use. + format: $formvars["var"] = "val"; + $formfiles an array of files to submit + format: $formfiles["var"] = "/dir/filename.ext"; + Output: $this->results the text output from the post + \*======================================================================*/ + + public function submit($URI, $formvars = "", $formfiles = "") + { + unset($postdata); + + $postdata = $this->_prepare_post_body($formvars, $formfiles); + + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) { + $this->user = $URI_PARTS["user"]; + } + if (!empty($URI_PARTS["pass"])) { + $this->pass = $URI_PARTS["pass"]; + } + if (empty($URI_PARTS["query"])) { + $URI_PARTS["query"] = ''; + } + if (empty($URI_PARTS["path"])) { + $URI_PARTS["path"] = ''; + } + + switch (strtolower($URI_PARTS["scheme"])) { + case "https": + if (!extension_loaded('openssl')) { + trigger_error("openssl extension required for HTTPS", E_USER_ERROR); + exit; + } + $this->port = 443; + // no break + case "http": + $this->scheme = strtolower($URI_PARTS["scheme"]); + $this->host = $URI_PARTS["host"]; + if (!empty($URI_PARTS["port"])) { + $this->port = $URI_PARTS["port"]; + } + if ($this->_connect($fp)) { + if ($this->_isproxy) { + // using proxy, send entire URI + $this->_httprequest($URI, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } else { + $path = $URI_PARTS["path"] . ($URI_PARTS["query"] ? "?" . $URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + $this->_disconnect($fp); + + if ($this->_redirectaddr) { + /* url was redirected, check if we've hit the max depth */ + if ($this->maxredirs > $this->_redirectdepth) { + if (!preg_match("|^" . $URI_PARTS["scheme"] . "://|", $this->_redirectaddr)) { + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr, $URI_PARTS["scheme"] . "://" . $URI_PARTS["host"]); + } + + // only follow redirect if it's on this site, or offsiteok is true + if (preg_match("|^https?://" . preg_quote($this->host) . "|i", $this->_redirectaddr) || $this->offsiteok) { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr = $this->_redirectaddr; + if (strpos($this->_redirectaddr, "?") > 0) { + $this->fetch($this->_redirectaddr); + } // the redirect has changed the request method from post to get + else { + $this->submit($this->_redirectaddr, $formvars, $formfiles); + } + } + } + } + + if ($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + foreach ($frameurls as $frameurl) { + if ($this->_framedepth < $this->maxframes) { + $this->fetch($frameurl); + $this->_framedepth++; + } else { + break; + } + } + } + } else { + return false; + } + return $this; + break; + default: + // not a valid protocol + $this->error = 'Invalid protocol "' . $URI_PARTS["scheme"] . '"\n'; + return false; + break; + } + return $this; + } + + /*======================================================================*\ + Function: fetchlinks + Purpose: fetch the links from a web page + Input: $URI where you are fetching from + Output: $this->results an array of the URLs + \*======================================================================*/ + + public function fetchlinks($URI) + { + if ($this->fetch($URI) !== false) { + if ($this->lastredirectaddr) { + $URI = $this->lastredirectaddr; + } + if (is_array($this->results)) { + for ($x = 0; $x < count($this->results); $x++) { + $this->results[$x] = $this->_striplinks($this->results[$x]); + } + } else { + $this->results = $this->_striplinks($this->results); + } + + if ($this->expandlinks) { + $this->results = $this->_expandlinks($this->results, $URI); + } + return $this; + } else { + return false; + } + } + + /*======================================================================*\ + Function: fetchform + Purpose: fetch the form elements from a web page + Input: $URI where you are fetching from + Output: $this->results the resulting html form + \*======================================================================*/ + + public function fetchform($URI) + { + if ($this->fetch($URI) !== false) { + if (is_array($this->results)) { + for ($x = 0; $x < count($this->results); $x++) { + $this->results[$x] = $this->_stripform($this->results[$x]); + } + } else { + $this->results = $this->_stripform($this->results); + } + + return $this; + } else { + return false; + } + } + + + /*======================================================================*\ + Function: fetchtext + Purpose: fetch the text from a web page, stripping the links + Input: $URI where you are fetching from + Output: $this->results the text from the web page + \*======================================================================*/ + + public function fetchtext($URI) + { + if ($this->fetch($URI) !== false) { + if (is_array($this->results)) { + for ($x = 0; $x < count($this->results); $x++) { + $this->results[$x] = $this->_striptext($this->results[$x]); + } + } else { + $this->results = $this->_striptext($this->results); + } + return $this; + } else { + return false; + } + } + + /*======================================================================*\ + Function: submitlinks + Purpose: grab links from a form submission + Input: $URI where you are submitting from + Output: $this->results an array of the links from the post + \*======================================================================*/ + + public function submitlinks($URI, $formvars = "", $formfiles = "") + { + if ($this->submit($URI, $formvars, $formfiles) !== false) { + if ($this->lastredirectaddr) { + $URI = $this->lastredirectaddr; + } + if (is_array($this->results)) { + for ($x = 0; $x < count($this->results); $x++) { + $this->results[$x] = $this->_striplinks($this->results[$x]); + if ($this->expandlinks) { + $this->results[$x] = $this->_expandlinks($this->results[$x], $URI); + } + } + } else { + $this->results = $this->_striplinks($this->results); + if ($this->expandlinks) { + $this->results = $this->_expandlinks($this->results, $URI); + } + } + return $this; + } else { + return false; + } + } + + /*======================================================================*\ + Function: submittext + Purpose: grab text from a form submission + Input: $URI where you are submitting from + Output: $this->results the text from the web page + \*======================================================================*/ + + public function submittext($URI, $formvars = "", $formfiles = "") + { + if ($this->submit($URI, $formvars, $formfiles) !== false) { + if ($this->lastredirectaddr) { + $URI = $this->lastredirectaddr; + } + if (is_array($this->results)) { + for ($x = 0; $x < count($this->results); $x++) { + $this->results[$x] = $this->_striptext($this->results[$x]); + if ($this->expandlinks) { + $this->results[$x] = $this->_expandlinks($this->results[$x], $URI); + } + } + } else { + $this->results = $this->_striptext($this->results); + if ($this->expandlinks) { + $this->results = $this->_expandlinks($this->results, $URI); + } + } + return $this; + } else { + return false; + } + } + + + /*======================================================================*\ + Function: set_submit_multipart + Purpose: Set the form submission content type to + multipart/form-data + \*======================================================================*/ + public function set_submit_multipart() + { + $this->_submit_type = "multipart/form-data"; + return $this; + } + + + /*======================================================================*\ + Function: set_submit_normal + Purpose: Set the form submission content type to + application/x-www-form-urlencoded + \*======================================================================*/ + public function set_submit_normal() + { + $this->_submit_type = "application/x-www-form-urlencoded"; + return $this; + } + + + + + /*======================================================================*\ + Private functions + \*======================================================================*/ + + + /*======================================================================*\ + Function: _striplinks + Purpose: strip the hyperlinks from an html document + Input: $document document to strip. + Output: $match an array of the links + \*======================================================================*/ + + public function _striplinks($document) + { + preg_match_all("'<\s*a\s.*?href\s*=\s* # find ]+)) # if quote found, match up to next matching # quote, otherwise match up to next space - 'isx",$document,$links); - - - // catenate the non-empty matches from the conditional subpattern - - while(list($key,$val) = each($links[2])) - { - if(!empty($val)) - $match[] = $val; - } - - while(list($key,$val) = each($links[3])) - { - if(!empty($val)) - $match[] = $val; - } - - // return the links - return $match; - } - -/*======================================================================*\ - Function: _stripform - Purpose: strip the form elements from an html document - Input: $document document to strip. - Output: $match an array of the links -\*======================================================================*/ - - function _stripform($document) - { - preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements); - - // catenate the matches - $match = implode("\r\n",$elements[0]); - - // return the links - return $match; - } - - - -/*======================================================================*\ - Function: _striptext - Purpose: strip the text from an html document - Input: $document document to strip. - Output: $text the resulting text -\*======================================================================*/ - - function _striptext($document) - { - - // I didn't use preg eval (//e) since that is only available in PHP 4.0. - // so, list your entities one by one here. I included some of the - // more common ones. - - $search = array("']*?>.*?'si", // strip out javascript - "'<[\/\!]*?[^<>]*?>'si", // strip out html tags - "'([\r\n])[\s]+'", // strip out white space - "'&(quote|#34);'i", // replace html entities - "'&(amp|#38);'i", - "'&(lt|#60);'i", - "'&(gt|#62);'i", - "'&(nbsp|#160);'i", - "'&(iexcl|#161);'i", - "'&(cent|#162);'i", - "'&(pound|#163);'i", - "'&(copy|#169);'i" - ); - $replace = array( "", - "", - "\\1", - "\"", - "&", - "<", - ">", - " ", - chr(161), - chr(162), - chr(163), - chr(169)); - - $text = preg_replace($search,$replace,$document); - - return $text; - } - -/*======================================================================*\ - Function: _expandlinks - Purpose: expand each link into a fully qualified URL - Input: $links the links to qualify - $URI the full URI to get the base from - Output: $expandedLinks the expanded links -\*======================================================================*/ - - function _expandlinks($links,$URI) - { - - preg_match("/^[^\?]+/",$URI,$match); - - $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]); - - $search = array( "|^http://".preg_quote($this->host)."|i", - "|^(?!http://)(\/)?(?!mailto:)|i", - "|/\./|", - "|/[^\/]+/\.\./|" - ); - - $replace = array( "", - $match."/", - "/", - "/" - ); - - $expandedLinks = preg_replace($search,$replace,$links); - - return $expandedLinks; - } - -/*======================================================================*\ - Function: _httprequest - Purpose: go get the http data from the server - Input: $url the url to fetch - $fp the current open file pointer - $URI the full URI - $body body contents to send if any (POST) - Output: -\*======================================================================*/ - - function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="") - { - if($this->passcookies && $this->_redirectaddr) - $this->setcookies(); - - $URI_PARTS = parse_url($URI); - if(empty($url)) - $url = "/"; - $headers = $http_method." ".$url." ".$this->_httpversion."\r\n"; - if(!empty($this->agent)) - $headers .= "User-Agent: ".$this->agent."\r\n"; - if(!empty($this->host) && !isset($this->rawheaders['Host'])) - $headers .= "Host: ".$this->host."\r\n"; - if(!empty($this->accept)) - $headers .= "Accept: ".$this->accept."\r\n"; - - if($this->use_gzip) { - // make sure PHP was built with --with-zlib - // and we can handle gzipp'ed data - if ( function_exists(gzinflate) ) { - $headers .= "Accept-encoding: gzip\r\n"; - } - else { - trigger_error( - "use_gzip is on, but PHP was built without zlib support.". - " Requesting file(s) without gzip encoding.", - E_USER_NOTICE); - } - } - - if(!empty($this->referer)) - $headers .= "Referer: ".$this->referer."\r\n"; - if(!empty($this->cookies)) - { - if(!is_array($this->cookies)) - $this->cookies = (array)$this->cookies; - - reset($this->cookies); - if ( count($this->cookies) > 0 ) { - $cookie_headers .= 'Cookie: '; - foreach ( $this->cookies as $cookieKey => $cookieVal ) { - $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; "; - } - $headers .= substr($cookie_headers,0,-2) . "\r\n"; - } - } - if(!empty($this->rawheaders)) - { - if(!is_array($this->rawheaders)) - $this->rawheaders = (array)$this->rawheaders; - while(list($headerKey,$headerVal) = each($this->rawheaders)) - $headers .= $headerKey.": ".$headerVal."\r\n"; - } - if(!empty($content_type)) { - $headers .= "Content-type: $content_type"; - if ($content_type == "multipart/form-data") - $headers .= "; boundary=".$this->_mime_boundary; - $headers .= "\r\n"; - } - if(!empty($body)) - $headers .= "Content-length: ".strlen($body)."\r\n"; - if(!empty($this->user) || !empty($this->pass)) - $headers .= "Authorization: BASIC ".base64_encode($this->user.":".$this->pass)."\r\n"; - - $headers .= "\r\n"; - - // set the read timeout if needed - if ($this->read_timeout > 0) - socket_set_timeout($fp, $this->read_timeout); - $this->timed_out = false; - - fwrite($fp,$headers.$body,strlen($headers.$body)); - - $this->_redirectaddr = false; - unset($this->headers); - - // content was returned gzip encoded? - $is_gzipped = false; - - while($currentHeader = fgets($fp,$this->_maxlinelen)) - { - if ($this->read_timeout > 0 && $this->_check_timeout($fp)) - { - $this->status=-100; - return false; - } - - // if($currentHeader == "\r\n") - if(preg_match("/^\r?\n$/", $currentHeader) ) - break; - - // if a header begins with Location: or URI:, set the redirect - if(preg_match("/^(Location:|URI:)/i",$currentHeader)) - { - // get URL portion of the redirect - preg_match("/^(Location:|URI:)\s+(.*)/",chop($currentHeader),$matches); - // look for :// in the Location header to see if hostname is included - if(!preg_match("|\:\/\/|",$matches[2])) - { - // no host in the path, so prepend - $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; - // eliminate double slash - if(!preg_match("|^/|",$matches[2])) - $this->_redirectaddr .= "/".$matches[2]; - else - $this->_redirectaddr .= $matches[2]; - } - else - $this->_redirectaddr = $matches[2]; - } - - if(preg_match("|^HTTP/|",$currentHeader)) - { - if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status)) - { - $this->status= $status[1]; - } - $this->response_code = $currentHeader; - } - - if (preg_match("/Content-Encoding: gzip/", $currentHeader) ) { - $is_gzipped = true; - } - - $this->headers[] = $currentHeader; - } - - # $results = fread($fp, $this->maxlength); - $results = ""; - while ( $data = fread($fp, $this->maxlength) ) { - $results .= $data; - if ( - strlen($results) > $this->maxlength ) { - break; - } - } - - // gunzip - if ( $is_gzipped ) { - // per http://www.php.net/manual/en/function.gzencode.php - $results = substr($results, 10); - $results = gzinflate($results); - } - - if ($this->read_timeout > 0 && $this->_check_timeout($fp)) - { - $this->status=-100; - return false; - } - - // check if there is a a redirect meta tag - - if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]+URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) - { - $this->_redirectaddr = $this->_expandlinks($match[1],$URI); - } - - // have we hit our frame depth and is there frame src to fetch? - if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) - { - $this->results[] = $results; - for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); - } - // have we already fetched framed content? - elseif(is_array($this->results)) - $this->results[] = $results; - // no framed content - else - $this->results = $results; - - return true; - } - -/*======================================================================*\ - Function: _httpsrequest - Purpose: go get the https data from the server using curl - Input: $url the url to fetch - $URI the full URI - $body body contents to send if any (POST) - Output: -\*======================================================================*/ - - function _httpsrequest($url,$URI,$http_method,$content_type="",$body="") - { - if($this->passcookies && $this->_redirectaddr) - $this->setcookies(); - - $headers = array(); - - $URI_PARTS = parse_url($URI); - if(empty($url)) - $url = "/"; - // GET ... header not needed for curl - //$headers[] = $http_method." ".$url." ".$this->_httpversion; - if(!empty($this->agent)) - $headers[] = "User-Agent: ".$this->agent; - if(!empty($this->host)) - $headers[] = "Host: ".$this->host; - if(!empty($this->accept)) - $headers[] = "Accept: ".$this->accept; - if(!empty($this->referer)) - $headers[] = "Referer: ".$this->referer; - if(!empty($this->cookies)) - { - if(!is_array($this->cookies)) - $this->cookies = (array)$this->cookies; - - reset($this->cookies); - if ( count($this->cookies) > 0 ) { - $cookie_str = 'Cookie: '; - foreach ( $this->cookies as $cookieKey => $cookieVal ) { - $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; "; - } - $headers[] = substr($cookie_str,0,-2); - } - } - if(!empty($this->rawheaders)) - { - if(!is_array($this->rawheaders)) - $this->rawheaders = (array)$this->rawheaders; - while(list($headerKey,$headerVal) = each($this->rawheaders)) - $headers[] = $headerKey.": ".$headerVal; - } - if(!empty($content_type)) { - if ($content_type == "multipart/form-data") - $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary; - else - $headers[] = "Content-type: $content_type"; - } - if(!empty($body)) - $headers[] = "Content-length: ".strlen($body); - if(!empty($this->user) || !empty($this->pass)) - $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass); - - for($curr_header = 0; $curr_header < count($headers); $curr_header++) { - $cmdline_params .= " -H \"".$headers[$curr_header]."\""; - } - - if(!empty($body)) - $cmdline_params .= " -d \"$body\""; - - if($this->read_timeout > 0) - $cmdline_params .= " -m ".$this->read_timeout; - - $headerfile = uniqid(time()); - - # accept self-signed certs - $cmdline_params .= " -k"; - exec($this->curl_path." -D \"/tmp/$headerfile\"".escapeshellcmd($cmdline_params)." ".escapeshellcmd($URI),$results,$return); - - if($return) - { - $this->error = "Error: cURL could not retrieve the document, error $return."; - return false; - } - - - $results = implode("\r\n",$results); - - $result_headers = file("/tmp/$headerfile"); - - $this->_redirectaddr = false; - unset($this->headers); - - for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++) - { - - // if a header begins with Location: or URI:, set the redirect - if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader])) - { - // get URL portion of the redirect - preg_match("/^(Location: |URI:)(.*)/",chop($result_headers[$currentHeader]),$matches); - // look for :// in the Location header to see if hostname is included - if(!preg_match("|\:\/\/|",$matches[2])) - { - // no host in the path, so prepend - $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; - // eliminate double slash - if(!preg_match("|^/|",$matches[2])) - $this->_redirectaddr .= "/".$matches[2]; - else - $this->_redirectaddr .= $matches[2]; - } - else - $this->_redirectaddr = $matches[2]; - } - - if(preg_match("|^HTTP/|",$result_headers[$currentHeader])) - { - $this->response_code = $result_headers[$currentHeader]; - if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$this->response_code, $match)) - { - $this->status= $match[1]; - } - } - $this->headers[] = $result_headers[$currentHeader]; - } - - // check if there is a a redirect meta tag - - if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]+URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) - { - $this->_redirectaddr = $this->_expandlinks($match[1],$URI); - } - - // have we hit our frame depth and is there frame src to fetch? - if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) - { - $this->results[] = $results; - for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); - } - // have we already fetched framed content? - elseif(is_array($this->results)) - $this->results[] = $results; - // no framed content - else - $this->results = $results; - - unlink("/tmp/$headerfile"); - - return true; - } - -/*======================================================================*\ - Function: setcookies() - Purpose: set cookies for a redirection -\*======================================================================*/ - - function setcookies() - { - for($x=0; $xheaders); $x++) - { - if(preg_match("/^set-cookie:[\s]+([^=]+)=([^;]+)/i", $this->headers[$x],$match)) - $this->cookies[$match[1]] = $match[2]; - } - } - - -/*======================================================================*\ - Function: _check_timeout - Purpose: checks whether timeout has occurred - Input: $fp file pointer -\*======================================================================*/ - - function _check_timeout($fp) - { - if ($this->read_timeout > 0) { - $fp_status = socket_get_status($fp); - if ($fp_status["timed_out"]) { - $this->timed_out = true; - return true; - } - } - return false; - } - -/*======================================================================*\ - Function: _connect - Purpose: make a socket connection - Input: $fp file pointer -\*======================================================================*/ - - function _connect(&$fp) - { - if(!empty($this->proxy_host) && !empty($this->proxy_port)) - { - $this->_isproxy = true; - $host = $this->proxy_host; - $port = $this->proxy_port; - } - else - { - $host = $this->host; - $port = $this->port; - } - - $this->status = 0; - - if($fp = fsockopen( - $host, - $port, - $errno, - $errstr, - $this->_fp_timeout - )) - { - // socket connection succeeded - - return true; - } - else - { - // socket connection failed - $this->status = $errno; - switch($errno) - { - case -3: - $this->error="socket creation failed (-3)"; - case -4: - $this->error="dns lookup failure (-4)"; - case -5: - $this->error="connection refused or timed out (-5)"; - default: - $this->error="connection failed (".$errno.")"; - } - return false; - } - } -/*======================================================================*\ - Function: _disconnect - Purpose: disconnect a socket connection - Input: $fp file pointer -\*======================================================================*/ - - function _disconnect($fp) - { - return(fclose($fp)); - } - - -/*======================================================================*\ - Function: _prepare_post_body - Purpose: Prepare post body according to encoding type - Input: $formvars - form variables - $formfiles - form upload files - Output: post body -\*======================================================================*/ - - function _prepare_post_body($formvars, $formfiles) - { - settype($formvars, "array"); - settype($formfiles, "array"); - - if (count($formvars) == 0 && count($formfiles) == 0) - return; - - switch ($this->_submit_type) { - case "application/x-www-form-urlencoded": - reset($formvars); - while(list($key,$val) = each($formvars)) { - if (is_array($val) || is_object($val)) { - while (list($cur_key, $cur_val) = each($val)) { - $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&"; - } - } else - $postdata .= urlencode($key)."=".urlencode($val)."&"; - } - break; - - case "multipart/form-data": - $this->_mime_boundary = "Snoopy".md5(uniqid(microtime())); - - reset($formvars); - while(list($key,$val) = each($formvars)) { - if (is_array($val) || is_object($val)) { - while (list($cur_key, $cur_val) = each($val)) { - $postdata .= "--".$this->_mime_boundary."\r\n"; - $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n"; - $postdata .= "$cur_val\r\n"; - } - } else { - $postdata .= "--".$this->_mime_boundary."\r\n"; - $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"; - $postdata .= "$val\r\n"; - } - } - - reset($formfiles); - while (list($field_name, $file_names) = each($formfiles)) { - settype($file_names, "array"); - while (list(, $file_name) = each($file_names)) { - if (!is_readable($file_name)) continue; - - $fp = fopen($file_name, "r"); - $file_content = fread($fp, filesize($file_name)); - fclose($fp); - $base_name = basename($file_name); - - $postdata .= "--".$this->_mime_boundary."\r\n"; - $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n"; - $postdata .= "$file_content\r\n"; - } - } - $postdata .= "--".$this->_mime_boundary."--\r\n"; - break; - } - - return $postdata; - } -} + 'isx", $document, $links); + + + // catenate the non-empty matches from the conditional subpattern + + foreach ($links[2] as $val) { + if (!empty($val)) { + $match[] = $val; + } + } + + foreach ($links[3] as $val) { + if (!empty($val)) { + $match[] = $val; + } + } + + // return the links + return $match; + } + + /*======================================================================*\ + Function: _stripform + Purpose: strip the form elements from an html document + Input: $document document to strip. + Output: $match an array of the links + \*======================================================================*/ + + public function _stripform($document) + { + preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi", $document, $elements); + + // catenate the matches + $match = implode("\r\n", $elements[0]); -?> + // return the links + return $match; + } + + + /*======================================================================*\ + Function: _striptext + Purpose: strip the text from an html document + Input: $document document to strip. + Output: $text the resulting text + \*======================================================================*/ + + public function _striptext($document) + { + + // I didn't use preg eval (//e) since that is only available in PHP 4.0. + // so, list your entities one by one here. I included some of the + // more common ones. + + $search = array("']*?>.*?'si", // strip out javascript + "'<[\/\!]*?[^<>]*?>'si", // strip out html tags + "'([\r\n])[\s]+'", // strip out white space + "'&(quot|#34|#034|#x22);'i", // replace html entities + "'&(amp|#38|#038|#x26);'i", // added hexadecimal values + "'&(lt|#60|#060|#x3c);'i", + "'&(gt|#62|#062|#x3e);'i", + "'&(nbsp|#160|#xa0);'i", + "'&(iexcl|#161);'i", + "'&(cent|#162);'i", + "'&(pound|#163);'i", + "'&(copy|#169);'i", + "'&(reg|#174);'i", + "'&(deg|#176);'i", + "'&(#39|#039|#x27);'", + "'&(euro|#8364);'i", // europe + "'&a(uml|UML);'", // german + "'&o(uml|UML);'", + "'&u(uml|UML);'", + "'&A(uml|UML);'", + "'&O(uml|UML);'", + "'&U(uml|UML);'", + "'ß'i", + ); + $replace = array("", + "", + "\\1", + "\"", + "&", + "<", + ">", + " ", + chr(161), + chr(162), + chr(163), + chr(169), + chr(174), + chr(176), + chr(39), + chr(128), + "ä", + "ö", + "ü", + "Ä", + "Ö", + "Ü", + "ß", + ); + + $text = preg_replace($search, $replace, $document); + + return $text; + } + + /*======================================================================*\ + Function: _expandlinks + Purpose: expand each link into a fully qualified URL + Input: $links the links to qualify + $URI the full URI to get the base from + Output: $expandedLinks the expanded links + \*======================================================================*/ + + public function _expandlinks($links, $URI) + { + preg_match("/^[^\?]+/", $URI, $match); + + $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|", "", $match[0]); + $match = preg_replace("|/$|", "", $match); + $match_part = parse_url($match); + $match_root = + $match_part["scheme"] . "://" . $match_part["host"]; + + $search = array("|^http://" . preg_quote($this->host) . "|i", + "|^(\/)|i", + "|^(?!http://)(?!mailto:)|i", + "|/\./|", + "|/[^\/]+/\.\./|" + ); + + $replace = array("", + $match_root . "/", + $match . "/", + "/", + "/" + ); + + $expandedLinks = preg_replace($search, $replace, $links); + + return $expandedLinks; + } + + /*======================================================================*\ + Function: _httprequest + Purpose: go get the http(s) data from the server + Input: $url the url to fetch + $fp the current open file pointer + $URI the full URI + $body body contents to send if any (POST) + Output: + \*======================================================================*/ + + public function _httprequest($url, $fp, $URI, $http_method, $content_type = "", $body = "") + { + $cookie_headers = ''; + if ($this->passcookies && $this->_redirectaddr) { + $this->setcookies(); + } + + $URI_PARTS = parse_url($URI); + if (empty($url)) { + $url = "/"; + } + $headers = $http_method . " " . $url . " " . $this->_httpversion . "\r\n"; + if (!empty($this->host) && !isset($this->rawheaders['Host'])) { + $headers .= "Host: " . $this->host; +// if (!empty($this->port) && $this->port != '80') +// $headers .= ":" . $this->port; + $headers .= "\r\n"; + } + if (!empty($this->agent)) { + $headers .= "User-Agent: " . $this->agent . "\r\n"; + } + if (!empty($this->accept)) { + $headers .= "Accept: " . $this->accept . "\r\n"; + } + if ($this->use_gzip) { + // make sure PHP was built with --with-zlib + // and we can handle gzipp'ed data + if (function_exists('gzinflate')) { + $headers .= "Accept-encoding: gzip\r\n"; + } else { + trigger_error( + "use_gzip is on, but PHP was built without zlib support." . + " Requesting file(s) without gzip encoding.", + E_USER_NOTICE + ); + } + } + if (!empty($this->referer)) { + $headers .= "Referer: " . $this->referer . "\r\n"; + } + if (!empty($this->cookies)) { + if (!is_array($this->cookies)) { + $this->cookies = (array) $this->cookies; + } + + reset($this->cookies); + if (count($this->cookies) > 0) { + $cookie_headers .= 'Cookie: '; + foreach ($this->cookies as $cookieKey => $cookieVal) { + $cookie_headers .= $cookieKey . "=" . urlencode($cookieVal) . "; "; + } + $headers .= substr($cookie_headers, 0, -2) . "\r\n"; + } + } + if (!empty($this->rawheaders)) { + if (!is_array($this->rawheaders)) { + $this->rawheaders = (array) $this->rawheaders; + } + foreach ($this->rawheaders as $headerKey => $headerVal) { + $headers .= $headerKey . ": " . $headerVal . "\r\n"; + } + } + if (!empty($content_type)) { + $headers .= "Content-type: $content_type"; + if ($content_type == "multipart/form-data") { + $headers .= "; boundary=" . $this->_mime_boundary; + } + $headers .= "\r\n"; + } + if (!empty($body)) { + $headers .= "Content-length: " . strlen($body) . "\r\n"; + } + if (!empty($this->user) || !empty($this->pass)) { + $headers .= "Authorization: Basic " . base64_encode($this->user . ":" . $this->pass) . "\r\n"; + } + + //add proxy auth headers + if (!empty($this->proxy_user)) { + $headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass) . "\r\n"; + } + + + $headers .= "\r\n"; + + // set the read timeout if needed + if ($this->read_timeout > 0) { + socket_set_timeout($fp, $this->read_timeout); + } + $this->timed_out = false; + + fwrite($fp, $headers . $body, strlen($headers . $body)); + + $this->_redirectaddr = false; + unset($this->headers); + + // content was returned gzip encoded? + $is_gzipped = false; + + while ($currentHeader = fgets($fp, $this->_maxlinelen)) { + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) { + $this->status = -100; + return false; + } + + if ($currentHeader == "\r\n") { + break; + } + + // if a header begins with Location: or URI:, set the redirect + if (preg_match("/^(Location:|URI:)/i", $currentHeader)) { + // get URL portion of the redirect + preg_match("/^(Location:|URI:)[ ]+(.*)/i", chop($currentHeader), $matches); + // look for :// in the Location header to see if hostname is included + if (!preg_match("|\:\/\/|", $matches[2])) { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"] . "://" . $this->host . ":" . $this->port; + // eliminate double slash + if (!preg_match("|^/|", $matches[2])) { + $this->_redirectaddr .= "/" . $matches[2]; + } else { + $this->_redirectaddr .= $matches[2]; + } + } else { + $this->_redirectaddr = $matches[2]; + } + } + + if (preg_match("|^HTTP/|", $currentHeader)) { + if (preg_match("|^HTTP/[^\s]*\s(.*?)\s|", $currentHeader, $status)) { + $this->status = $status[1]; + } + $this->response_code = $currentHeader; + } + + if (preg_match("/Content-Encoding: gzip/", $currentHeader)) { + $is_gzipped = true; + } + + $this->headers[] = $currentHeader; + } + + $results = ''; + do { + $_data = fread($fp, $this->maxlength); + if (strlen($_data) == 0) { + break; + } + $results .= $_data; + } while (true); + + // gunzip + if ($is_gzipped) { + // per http://www.php.net/manual/en/function.gzencode.php + $results = substr($results, 10); + $results = gzinflate($results); + } + + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) { + $this->status = -100; + return false; + } + + // check if there is a a redirect meta tag + + if (preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i", $results, $match)) { + $this->_redirectaddr = $this->_expandlinks($match[1], $URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if (($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i", $results, $match)) { + $this->results[] = $results; + for ($x = 0; $x < count($match[1]); $x++) { + $this->_frameurls[] = $this->_expandlinks($match[1][$x], $URI_PARTS["scheme"] . "://" . $this->host); + } + } // have we already fetched framed content? + elseif (is_array($this->results)) { + $this->results[] = $results; + } + // no framed content + else { + $this->results = $results; + } + + return $this; + } + + /*======================================================================*\ + Function: setcookies() + Purpose: set cookies for a redirection + \*======================================================================*/ + + public function setcookies() + { + for ($x = 0; $x < count($this->headers); $x++) { + if (preg_match('/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers[$x], $match)) { + $this->cookies[$match[1]] = urldecode($match[2]); + } + } + return $this; + } + + + /*======================================================================*\ + Function: _check_timeout + Purpose: checks whether timeout has occurred + Input: $fp file pointer + \*======================================================================*/ + + public function _check_timeout($fp) + { + if ($this->read_timeout > 0) { + $fp_status = socket_get_status($fp); + if ($fp_status["timed_out"]) { + $this->timed_out = true; + return true; + } + } + return false; + } + + /*======================================================================*\ + Function: _connect + Purpose: make a socket connection + Input: $fp file pointer + \*======================================================================*/ + + public function _connect(&$fp) + { + if (!empty($this->proxy_host) && !empty($this->proxy_port)) { + $this->_isproxy = true; + + $host = $this->proxy_host; + $port = $this->proxy_port; + + if ($this->scheme == 'https') { + trigger_error("HTTPS connections over proxy are currently not supported", E_USER_ERROR); + exit; + } + } else { + $host = $this->host; + $port = $this->port; + } + + $this->status = 0; + + $context_opts = array(); + + if ($this->scheme == 'https') { + // if cafile or capath is specified, enable certificate + // verification (including name checks) + if (isset($this->cafile) || isset($this->capath)) { + $context_opts['ssl'] = array( + 'verify_peer' => true, + 'CN_match' => $this->host, + 'disable_compression' => true, + ); + + if (isset($this->cafile)) { + $context_opts['ssl']['cafile'] = $this->cafile; + } + if (isset($this->capath)) { + $context_opts['ssl']['capath'] = $this->capath; + } + } + + $host = 'ssl://' . $host; + } + + $context = stream_context_create($context_opts); + + if (version_compare(PHP_VERSION, '5.0.0', '>')) { + if ($this->scheme == 'http') { + $host = "tcp://" . $host; + } + $fp = stream_socket_client( + "$host:$port", + $errno, + $errmsg, + $this->_fp_timeout, + STREAM_CLIENT_CONNECT, + $context + ); + } else { + $fp = fsockopen( + $host, + $port, + $errno, + $errstr, + $this->_fp_timeout, + $context + ); + } + + if ($fp) { + // socket connection succeeded + return true; + } else { + // socket connection failed + $this->status = $errno; + switch ($errno) { + case -3: + $this->error = "socket creation failed (-3)"; + // no break + case -4: + $this->error = "dns lookup failure (-4)"; + // no break + case -5: + $this->error = "connection refused or timed out (-5)"; + // no break + default: + $this->error = "connection failed (" . $errno . ")"; + } + return false; + } + } + + /*======================================================================*\ + Function: _disconnect + Purpose: disconnect a socket connection + Input: $fp file pointer + \*======================================================================*/ + + public function _disconnect($fp) + { + return (fclose($fp)); + } + + + /*======================================================================*\ + Function: _prepare_post_body + Purpose: Prepare post body according to encoding type + Input: $formvars - form variables + $formfiles - form upload files + Output: post body + \*======================================================================*/ + + public function _prepare_post_body($formvars, $formfiles) + { + settype($formvars, "array"); + settype($formfiles, "array"); + $postdata = ''; + + if (count($formvars) == 0 && count($formfiles) == 0) { + return; + } + + switch ($this->_submit_type) { + case "application/x-www-form-urlencoded": + reset($formvars); + foreach ($formvars as $key => $val) { + if (is_array($val) || is_object($val)) { + foreach ($val as $cur_key => $cur_val) { + $postdata .= urlencode($key) . "[]=" . urlencode($cur_val) . "&"; + } + } else { + $postdata .= urlencode($key) . "=" . urlencode($val) . "&"; + } + } + break; + + case "multipart/form-data": + $this->_mime_boundary = "Snoopy" . md5(uniqid(microtime())); + + reset($formvars); + foreach ($formvars as $key => $val) { + if (is_array($val) || is_object($val)) { + foreach ($val as $cur_key => $cur_val) { + $postdata .= "--" . $this->_mime_boundary . "\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n"; + $postdata .= "$cur_val\r\n"; + } + } else { + $postdata .= "--" . $this->_mime_boundary . "\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"; + $postdata .= "$val\r\n"; + } + } + + reset($formfiles); + foreach ($formfiles as $field_name => $file_names) { + settype($file_names, "array"); + foreach ($file_names as $file_name) { + if (!is_readable($file_name)) { + continue; + } + + $fp = fopen($file_name, "r"); + $file_content = fread($fp, filesize($file_name)); + fclose($fp); + $base_name = basename($file_name); + + $postdata .= "--" . $this->_mime_boundary . "\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n"; + $postdata .= "$file_content\r\n"; + } + } + $postdata .= "--" . $this->_mime_boundary . "--\r\n"; + break; + } + + return $postdata; + } + + /*======================================================================*\ + Function: getResults + Purpose: return the results of a request + Output: string results + \*======================================================================*/ + + public function getResults() + { + return $this->results; + } +} diff --git a/Services/FileSystem/classes/Setup/class.ilFileSystemComponentDataDirectoryCreatedObjective.php b/Services/FileSystem/classes/Setup/class.ilFileSystemComponentDataDirectoryCreatedObjective.php index 799d60f3f53a..2db3b2cf0654 100644 --- a/Services/FileSystem/classes/Setup/class.ilFileSystemComponentDataDirectoryCreatedObjective.php +++ b/Services/FileSystem/classes/Setup/class.ilFileSystemComponentDataDirectoryCreatedObjective.php @@ -3,16 +3,13 @@ /* Copyright (c) 2020 Nils Haagen Extended GPL, see docs/LICENSE */ use ILIAS\Setup; +use ILIAS\Setup\UnachievableException; -class ilFileSystemComponentDataDirectoryCreatedObjective extends Setup\DirectoryCreatedObjective implements Setup\Objective +class ilFileSystemComponentDataDirectoryCreatedObjective implements Setup\Objective { const DATADIR = 1; const WEBDIR = 2; - - /** - * @var string - */ - protected $path; + const DEFAULT_DIRECTORY_PERMISSIONS = 0755; /** * @var string @@ -24,13 +21,19 @@ class ilFileSystemComponentDataDirectoryCreatedObjective extends Setup\Directory */ protected $base_location; + /** + * @var int + */ + protected $permissions; public function __construct( string $component_dir, - int $base_location = self::DATADIR + int $base_location = self::DATADIR, + int $permissions = self::DEFAULT_DIRECTORY_PERMISSIONS ) { $this->component_dir = $component_dir; $this->base_location = $base_location; + $this->permissions = $permissions; } /** @@ -41,20 +44,28 @@ public function getHash() : string return hash("sha256", self::class . "::" . $this->component_dir . (string) $this->base_location); } - protected function buildPath(Setup\Environment $environment) : string + /** + * @inheritdocs + */ + public function getLabel() : string { - $common_config = $environment->getConfigFor("common"); - $fs_config = $environment->getConfigFor("filesystem"); + $dir = ''; if ($this->base_location === self::DATADIR) { - $data_dir = $fs_config->getDataDir(); + $dir = 'data'; } if ($this->base_location === self::WEBDIR) { - $data_dir = $fs_config->getWebDir(); + $dir = 'web'; } - $client_data_dir = $data_dir . '/' . $common_config->getClientId(); - $new_dir = $client_data_dir . '/' . $this->component_dir; - return $new_dir; + return "Create $dir directory in component directory $this->component_dir"; + } + + /** + * @inheritdocs + */ + public function isNotable() : bool + { + return true; } public function getPreconditions(Setup\Environment $environment) : array @@ -67,7 +78,35 @@ public function getPreconditions(Setup\Environment $environment) : array public function achieve(Setup\Environment $environment) : Setup\Environment { - $this->path = $this->buildPath($environment); - return parent::achieve($environment); + $path = $this->buildPath($environment); + + if (!file_exists($path)) { + mkdir($path, $this->permissions); + } + if (!is_dir($path)) { + throw new UnachievableException( + "Could not create directory '{$path}'" + ); + } + return $environment; + } + + protected function buildPath(Setup\Environment $environment) : string + { + $common_config = $environment->getConfigFor("common"); + $fs_config = $environment->getConfigFor("filesystem"); + + if ($this->base_location === self::DATADIR) { + $data_dir = $fs_config->getDataDir(); + } + + if ($this->base_location === self::WEBDIR) { + $data_dir = $fs_config->getWebDir(); + } + + $client_data_dir = $data_dir . '/' . $common_config->getClientId(); + $new_dir = $client_data_dir . '/' . $this->component_dir; + + return $new_dir; } } diff --git a/Services/FileSystem/classes/class.ilFileSystemTableGUI.php b/Services/FileSystem/classes/class.ilFileSystemTableGUI.php index 6d1b6ac84f11..d042531a7b4f 100644 --- a/Services/FileSystem/classes/class.ilFileSystemTableGUI.php +++ b/Services/FileSystem/classes/class.ilFileSystemTableGUI.php @@ -118,7 +118,7 @@ public function getEntries() if ($this->label_enable) { $label = (is_array($this->file_labels[$cfile])) - ? implode($this->file_labels[$cfile], ", ") + ? implode(", ", $this->file_labels[$cfile]) : ""; } diff --git a/Services/Form/classes/class.ilFlashFileInputGUI.php b/Services/Form/classes/class.ilFlashFileInputGUI.php index 537e548a21db..fb4b7364c785 100755 --- a/Services/Form/classes/class.ilFlashFileInputGUI.php +++ b/Services/Form/classes/class.ilFlashFileInputGUI.php @@ -333,10 +333,10 @@ public function insert($a_tpl) $index++; } $template->setCurrentBlock("applet_parameter"); - $template->setVariable("PARAM_VALUE", join($params, "&")); + $template->setVariable("PARAM_VALUE", join("&", $params)); $template->parseCurrentBlock(); $template->setCurrentBlock("flash_vars"); - $template->setVariable("PARAM_VALUE", join($params, "&")); + $template->setVariable("PARAM_VALUE", join("&", $params)); $template->parseCurrentBlock(); } $template->setCurrentBlock("applet"); diff --git a/Services/Form/templates/default/tpl.property_form.html b/Services/Form/templates/default/tpl.property_form.html index d73f2e16ae4a..6ad7dd913717 100644 --- a/Services/Form/templates/default/tpl.property_form.html +++ b/Services/Form/templates/default/tpl.property_form.html @@ -10,7 +10,7 @@ -

{TXT_TITLE}

+

{TXT_TITLE}

  Ω   diff --git a/Services/GlobalScreen/classes/Provider/GSMetaBarProvider.php b/Services/GlobalScreen/classes/Provider/GSMetaBarProvider.php deleted file mode 100644 index 2d62423c7cc9..000000000000 --- a/Services/GlobalScreen/classes/Provider/GSMetaBarProvider.php +++ /dev/null @@ -1,37 +0,0 @@ - - */ -class GSMetaBarProvider extends AbstractStaticMetaBarProvider -{ - - /** - * @inheritDoc - */ - public function getMetaBarItems() : array - { - return []; // currently removed - // REMOVE AFTER TESTING - $t = new ModeToggle(); - $identification = $this->if->identifier('toggle'); - $symbol = $this->dic->ui()->factory()->symbol()->glyph()->settings(); - - $top_item = $this->meta_bar->topLegacyItem($identification) - ->withTitle("Toggle") - ->withLegacyContent($this->dic->ui() - ->factory() - ->legacy("Zum Testen kann hier umgestellt werden,
ob Slates in der MainBar offen bleiben sollen oder nicht.

Aktueller Modus: {$t->getMode()}

(none: keine, all: alle typen)


Umschalten: Hier klicken...")) - ->withSymbol($symbol); - - return [$top_item]; - // END REMOVE - } -} diff --git a/Services/Imprint/classes/class.ilImprintGUI.php b/Services/Imprint/classes/class.ilImprintGUI.php index eeb928c9d81a..cf6b9d0a507f 100644 --- a/Services/Imprint/classes/class.ilImprintGUI.php +++ b/Services/Imprint/classes/class.ilImprintGUI.php @@ -113,31 +113,22 @@ public function postOutputProcessing($a_output) return $a_output; } - + protected function renderFullscreen() { $tpl = $this->tpl; $lng = $this->lng; - $ilMainMenu = $this->main_menu; - + if (!ilImprint::isActive()) { ilUtil::redirect("ilias.php?baseClass=ilDashboardGUI"); } - + $tpl->setTitle($lng->txt("imprint")); $tpl->loadStandardTemplate(); - + $this->setRawPageContent(true); $html = $this->showPage(); - - $itpl = new ilTemplate("tpl.imprint.html", true, true, "Services/Imprint"); - $itpl->setVariable("PAGE_TITLE", $lng->txt("imprint")); - $itpl->setVariable("IMPRINT", $html); - unset($html); - - $tpl->setContent($itpl->get()); - - $ilMainMenu->showLogoOnly(true); - + $tpl->setContent($html); + $tpl->printToStdout("DEFAULT", true, false); exit(); } diff --git a/Services/Imprint/templates/default/tpl.imprint.html b/Services/Imprint/templates/default/tpl.imprint.html deleted file mode 100644 index 4ce44ebd0c9b..000000000000 --- a/Services/Imprint/templates/default/tpl.imprint.html +++ /dev/null @@ -1,2 +0,0 @@ -

{PAGE_TITLE}

-{IMPRINT} \ No newline at end of file diff --git a/Services/InfoScreen/templates/default/tpl.infoscreen.html b/Services/InfoScreen/templates/default/tpl.infoscreen.html index 81f096400f78..f192f645a99c 100755 --- a/Services/InfoScreen/templates/default/tpl.infoscreen.html +++ b/Services/InfoScreen/templates/default/tpl.infoscreen.html @@ -5,7 +5,7 @@ {TOP_FORMBUTTONS}
-

{TXT_SECTION}

+

{TXT_SECTION}

{TXT_PROPERTY}
diff --git a/Services/Init/classes/Provider/StartUpMetaBarProvider.php b/Services/Init/classes/Provider/StartUpMetaBarProvider.php index 9b3e033a0161..bf6871918f97 100644 --- a/Services/Init/classes/Provider/StartUpMetaBarProvider.php +++ b/Services/Init/classes/Provider/StartUpMetaBarProvider.php @@ -20,6 +20,8 @@ class StartUpMetaBarProvider extends AbstractStaticMetaBarProvider public function getMetaBarItems() : array { $factory = $this->dic->ui()->factory(); + $request = $this->dic->http()->request(); + $languages = $this->dic->language()->getInstalledLanguages(); $if = function (string $id) : IdentificationInterface { return $this->if->identifier($id); @@ -31,9 +33,16 @@ public function getMetaBarItems() : array // Login-Button // Only visible, if not on login-page but not logged in + $target_str = ''; + if ($ref_id = $request->getQueryParams()['ref_id']) { + $target_str = 'target=' . \ilObject::_lookupType($ref_id, true) . '_' . (int) $ref_id . '&'; + } elseif ($target = $request->getQueryParams()['target']) { + $target_str = 'target=' . $target . '&'; + } + $login_glyph = $factory->symbol()->glyph()->login(); $login = $this->meta_bar->topLinkItem($if('login')) - ->withAction("login.php?client_id=" . rawurlencode(CLIENT_ID) . "&cmd=force_login&lang=" . $this->dic->user()->getCurrentLanguage()) + ->withAction("login.php?" . $target_str . "client_id=" . rawurlencode(CLIENT_ID) . "&cmd=force_login&lang=" . $this->dic->user()->getCurrentLanguage()) ->withSymbol($login_glyph) ->withPosition(2) ->withTitle($txt('log_in')) @@ -51,8 +60,8 @@ public function getMetaBarItems() : array ->withAvailableCallable(function () { return !$this->isUserLoggedIn(); }) - ->withVisibilityCallable(function () { - return true; + ->withVisibilityCallable(function () use ($languages) { + return count($languages) > 1; }) ->withTitle($txt('language')); @@ -61,7 +70,7 @@ public function getMetaBarItems() : array /** * @var $language_selection TopParentItem */ - foreach ($this->dic->language()->getInstalledLanguages() as $lang_key) { + foreach ($languages as $lang_key) { $link = $this->appendUrlParameterString($base, "lang=" . $lang_key); $language_name = $this->dic->language()->_lookupEntry($lang_key, "meta", "meta_l_" . $lang_key); diff --git a/Services/Init/classes/class.ilInitialisation.php b/Services/Init/classes/class.ilInitialisation.php index b98e629005fa..ccd9de072e38 100644 --- a/Services/Init/classes/class.ilInitialisation.php +++ b/Services/Init/classes/class.ilInitialisation.php @@ -772,6 +772,7 @@ public static function initUserAccount() } // init console log handler ilLoggerFactory::getInstance()->initUser($DIC->user()->getLogin()); + \ilOnlineTracking::updateAccess($DIC->user()); } else { if (is_object($GLOBALS['ilLog'])) { $GLOBALS['ilLog']->logStack(); @@ -1902,6 +1903,14 @@ protected static function redirect($a_target, $a_message_id = '', array $a_messa $a_target = ILIAS_HTTP_PATH . "/" . $a_target; } + foreach (['ext_uid', 'soap_pw'] as $param) { + if (false === strpos($a_target, $param . '=') && isset($GLOBALS['DIC']->http()->request()->getQueryParams()[$param])) { + $a_target = \ilUtil::appendUrlParameterString($a_target, $param . '=' . \ilUtil::stripSlashes( + $GLOBALS['DIC']->http()->request()->getQueryParams()[$param] + )); + } + } + if (ilContext::supportsRedirects()) { ilUtil::redirect($a_target); } else { diff --git a/Services/Init/classes/class.ilPasswordAssistanceGUI.php b/Services/Init/classes/class.ilPasswordAssistanceGUI.php index 3e2a224b37df..38ae0dbdbd39 100755 --- a/Services/Init/classes/class.ilPasswordAssistanceGUI.php +++ b/Services/Init/classes/class.ilPasswordAssistanceGUI.php @@ -333,7 +333,7 @@ public function sendPasswordAssistanceMail(ilObjUser $userObj) $sender = $senderFactory->system(); $mm = new ilMimeMail(); - $mm->Subject($this->lng->txt('pwassist_mail_subject')); + $mm->Subject($this->lng->txt('pwassist_mail_subject'), true); $mm->From($sender); $mm->To($userObj->getEmail()); $mm->Body( @@ -660,7 +660,7 @@ public function sendUsernameAssistanceMail($email, array $logins) $sender = $senderFactory->system(); $mm = new ilMimeMail(); - $mm->Subject($this->lng->txt('pwassist_mail_subject')); + $mm->Subject($this->lng->txt('pwassist_mail_subject'), true); $mm->From($sender); $mm->To($email); $mm->Body( @@ -669,7 +669,7 @@ public function sendUsernameAssistanceMail($email, array $logins) array("\n", "\t"), sprintf( $this->lng->txt('pwassist_username_mail_body'), - join($logins, ",\n"), + join(",\n", $logins), $this->getBaseUrl() . '/', $_SERVER['REMOTE_ADDR'], $email, diff --git a/Services/Init/classes/class.ilStartUpGUI.php b/Services/Init/classes/class.ilStartUpGUI.php index 2ae7af3c3dba..c7831648ab48 100755 --- a/Services/Init/classes/class.ilStartUpGUI.php +++ b/Services/Init/classes/class.ilStartUpGUI.php @@ -206,6 +206,23 @@ protected function showLoginPage(ilPropertyFormGUI $form = null) $this->getLogger()->debug('Showing login page'); + $extUid = ''; + if (isset($_GET['ext_uid']) && is_string($_GET['ext_uid'])) { + $extUid = $_GET['ext_uid']; + } + $soapPw = ''; + if (isset($_GET['soap_pw']) && is_string($_GET['soap_pw'])) { + $soapPw = $_GET['soap_pw']; + } + + require_once 'Services/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsSoap.php'; + $credentials = new ilAuthFrontendCredentialsSoap($GLOBALS['DIC']->http()->request(), $this->ctrl, $ilSetting); + $credentials->setUsername(ilUtil::stripSlashes($extUid)); + $credentials->setPassword(ilUtil::stripSlashes($soapPw)); + $credentials->tryAuthenticationOnLoginPage(); + + // try apache auth + include_once './Services/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsApache.php'; $frontend = new ilAuthFrontendCredentialsApache($this->httpRequest, $this->ctrl); $frontend->tryAuthenticationOnLoginPage(); @@ -1901,7 +1918,7 @@ public static function initStartUpTemplate($a_tmpl, $a_show_back = false, $a_sho } PageContentProvider::setShortTitle($short_title); - $header_title = ilObjSystemFolder::_getHeaderTitle(); + $header_title = (string) ilObjSystemFolder::_getHeaderTitle(); PageContentProvider::setTitle($header_title); return $tpl; @@ -1950,10 +1967,15 @@ protected function showOpenIdConnectLoginForm($page_editor_html) { global $DIC; + $lang = $DIC->language(); + $oidc_settings = ilOpenIdConnectSettings::getInstance(); if ($oidc_settings->getActive()) { $tpl = new ilTemplate('tpl.login_element.html', true, true, 'Services/OpenIdConnect'); + $lang->loadLanguageModule('auth'); + $tpl->setVariable('TXT_OIDCONNECT_HEADER', $lang->txt('auth_oidc_login_element_info')); + $target = empty($_GET['target']) ? '' : ('?target=' . (string) $_GET['target']); switch ($oidc_settings->getLoginElementType()) { case ilOpenIdConnectSettings::LOGIN_ELEMENT_TYPE_TXT: diff --git a/Services/JavaScript/js/Basic.js b/Services/JavaScript/js/Basic.js index 60113e412dad..7bb916c10757 100644 --- a/Services/JavaScript/js/Basic.js +++ b/Services/JavaScript/js/Basic.js @@ -1051,3 +1051,70 @@ function startSAHS(SAHSurl, SAHStarget, SAHSopenMode, SAHSwidth, SAHSheight) } } +/** + * Related to https://mantis.ilias.de/view.php?id=26494 + * jQuery "inputFilter" Extension. + */ +(function($) { + /** + * @param {mixed} inputFilter + * @returns {jQuery} + */ + $.fn.inputFilter = function(inputFilter) { + return this.on("input keydown keyup mousedown mouseup select contextmenu drop", function(e) { + if ("-" === $.trim(this.value)) { + // https://mantis.ilias.de/view.php?id=29417 + } else if (inputFilter(this.value)) { + this.oldValue = this.value; + this.oldSelectionStart = this.selectionStart; + this.oldSelectionEnd = this.selectionEnd; + } else if (this.hasOwnProperty("oldValue")) { + this.value = this.oldValue; + this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd); + } else { + this.value = ""; + } + }); + }; +}(jQuery)); + +/** + * Related to https://mantis.ilias.de/view.php?id=26494 + * UI-Feedback : check if a numeric field isset but value is not numeric. + */ +function numericInputCheck() { + + const numericInput = $( '.ilcqinput_NumericInput' ); + + // Only if present. + if ( numericInput.length ) { + + // Append ilcqinput_NumericInputInvalid class for visually distinguishable numeric input fields. + // -> Onload. + let value = $( numericInput ).val().toString().replace( ',', '.' ); + if ( value && !$.isNumeric( value ) ) { + $( numericInput ).addClass( 'ilcqinput_NumericInputInvalid' ); + } else { + $( numericInput ).removeClass( 'ilcqinput_NumericInputInvalid' ); + } + // -> OnChange. + $( numericInput ).on( 'change', function() { + let value = $( this ).val().toString().replace( ',', '.' ); + if ( value && !$.isNumeric( value ) ) { + $( this ).addClass( 'ilcqinput_NumericInputInvalid' ); + } else { + $( this ).removeClass( 'ilcqinput_NumericInputInvalid' ); + } + } ); + + // Only allow numeric values foreach ".ilcqinput_NumericInput" classified input field. + $( numericInput ).inputFilter( function( value ) { + value = value.toString().replace( ',', '.' ); + return !$.trim( value ) || $.isNumeric( value ); + } ); + } +} + +$(document).ready( function( ) { + numericInputCheck(); +}); diff --git a/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php b/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php index b6d03790fcdc..6b1492078d19 100644 --- a/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php +++ b/Services/LDAP/classes/class.ilLDAPRoleGroupMapping.php @@ -33,6 +33,9 @@ */ class ilLDAPRoleGroupMapping { + /** + * @var ilLogger + */ private $log = null; private static $instance = null; private $servers = null; @@ -40,6 +43,11 @@ class ilLDAPRoleGroupMapping private $mapping_members = array(); private $query = array(); private $active_servers = false; + + /** + * @var array + */ + private $users = []; /** * Singleton contructor @@ -51,9 +59,8 @@ private function __construct() { global $DIC; - $ilLog = $DIC['ilLog']; + $this->log = $DIC->logger()->auth(); - $this->log = $ilLog; $this->initServers(); } @@ -118,10 +125,10 @@ public function assign($a_role_id, $a_usr_id) return false; } if (!$this->isHandledUser($a_usr_id)) { - $this->log->write('LDAP assign: User ID: ' . $a_usr_id . ' has no LDAP account'); + $this->log->info('LDAP assign: User ID: ' . $a_usr_id . ' has no LDAP account'); return false; } - $this->log->write('LDAP assign: User ID: ' . $a_usr_id . ' Role Id: ' . $a_role_id); + $this->log->info('LDAP assigned: User ID: ' . $a_usr_id . ' Role Id: ' . $a_role_id); $this->assignToGroup($a_role_id, $a_usr_id); return true; @@ -179,7 +186,7 @@ public function deassign($a_role_id, $a_usr_id) if (!$this->isHandledUser($a_usr_id)) { return false; } - $this->log->write('LDAP deassign: User ID: ' . $a_usr_id . ' Role Id: ' . $a_role_id); + $this->log->info('LDAP deassigned: User ID: ' . $a_usr_id . ' Role Id: ' . $a_role_id); $this->deassignFromGroup($a_role_id, $a_usr_id); return true; @@ -220,9 +227,14 @@ private function initServers() $this->active_servers = true; $this->mappings = array(); + $this->users = []; foreach ($server_ids as $server_id) { $this->servers[$server_id] = new ilLDAPServer($server_id); $this->mappings = ilLDAPRoleGroupMappingSettings::_getAllActiveMappings(); + $this->users[$server_id] = ilObjUser::_getExternalAccountsByAuthMode( + 'ldap_' . $server_id, + true + ); } $this->mapping_info = array(); $this->mapping_info_strict = array(); @@ -236,8 +248,6 @@ private function initServers() } } } - $this->users = ilObjUser::_getExternalAccountsByAuthMode('ldap', true); - return true; } @@ -261,7 +271,12 @@ private function isHandledRole($a_role_id) */ private function isHandledUser($a_usr_id) { - return array_key_exists($a_usr_id, $this->users); + foreach ($this->users as $server_id => $users) { + if (array_key_exists($a_usr_id, $users)) { + return true; + } + } + return false; } @@ -279,23 +294,14 @@ private function assignToGroup($a_role_id, $a_usr_id) if ($data['isdn']) { $external_account = $this->readDN($a_usr_id, $data['server_id']); } else { - $external_account = $this->users[$a_usr_id]; + $external_account = $this->users[$data['server_id']][$a_usr_id]; } - // Forcing modAdd since Active directory is too slow and i cannot check if a user is member or not. - #if($this->isMember($external_account,$data)) - #{ - # $this->log->write("LDAP assign: User already assigned to group '".$data['dn']."'"); - #} - #else - { - // Add user - $query_obj = $this->getLDAPQueryInstance($data['server_id'], $data['url']); - $query_obj->modAdd($data['dn'], array($data['member'] => $external_account)); - $this->log->write('LDAP assign: Assigned ' . $external_account . ' to group ' . $data['dn']); - } + $query_obj = $this->getLDAPQueryInstance($data['server_id'], $data['url']); + $query_obj->modAdd($data['dn'], array($data['member'] => $external_account)); + $this->log->info('LDAP assign: Assigned ' . $external_account . ' to group ' . $data['dn']); } catch (ilLDAPQueryException $exc) { - $this->log->write($exc->getMessage()); + $this->log->warning($exc->getMessage()); // try next mapping continue; } @@ -317,25 +323,18 @@ private function deassignFromGroup($a_role_id, $a_usr_id) if ($data['isdn']) { $external_account = $this->readDN($a_usr_id, $data['server_id']); } else { - $external_account = $this->users[$a_usr_id]; + $external_account = $this->users[$data['server_id']][$a_usr_id]; } // Check for other role membership if ($role_id = $this->checkOtherMembership($a_usr_id, $a_role_id, $data)) { - $this->log->write('LDAP deassign: User is still assigned to role "' . $role_id . '".'); + $this->log->info('LDAP deassign: User is still assigned to role "' . $role_id . '".'); continue; } - /* - if(!$this->isMember($external_account,$data)) - { - $this->log->write("LDAP deassign: User not assigned to group '".$data['dn']."'"); - continue; - } - */ // Deassign user $query_obj = $this->getLDAPQueryInstance($data['server_id'], $data['url']); $query_obj->modDelete($data['dn'], array($data['member'] => $external_account)); - $this->log->write('LDAP deassign: Deassigned ' . $external_account . ' from group ' . $data['dn']); + $this->log->info('LDAP deassign: Deassigned ' . $external_account . ' from group ' . $data['dn']); // Delete from cache if (is_array($this->mapping_members[$data['mapping_id']])) { @@ -345,50 +344,14 @@ private function deassignFromGroup($a_role_id, $a_usr_id) } } } catch (ilLDAPQueryException $exc) { - $this->log->write($exc->getMessage()); + $this->log->warning($exc->getMessage()); // try next mapping continue; } } } - /** - * Check if user is member - * - * @access private - * @throws ilLDAPQueryException - */ - private function isMember($a_uid, $data) - { - if (!isset($this->mapping_members["$data[mapping_id]"])) { - // Read members - try { - $server = $this->servers["$data[server_id]"]; - $query_obj = $this->getLDAPQueryInstance($data['server_id'], $server->getUrl()); - // query for members - $res = $query_obj->query( - $data['dn'], - '(objectClass=*)', - IL_LDAP_SCOPE_BASE, - array($data['member']) - ); - - $this->storeMembers($data['mapping_id'], $res->get()); - unset($res); - } catch (ilLDAPQueryException $exc) { - throw $exc; - } - } - #var_dump("
",$a_uid,$this->mapping_members,"
"); - - // Now check for membership in stored result - if (in_array($a_uid, $this->mapping_members["$data[mapping_id]"])) { - return true; - } - return false; - } - /** * Check other membership * @@ -461,7 +424,7 @@ private function readDN($a_usr_id, $a_server_id) return $this->user_dns[$a_usr_id]; } - $external_account = $this->users[$a_usr_id]; + $external_account = $this->users[$a_server_id][$a_usr_id]; try { $server = $this->servers[$a_server_id]; diff --git a/Services/LTI/classes/InternalProvider/class.ilLTIToolProvider.php b/Services/LTI/classes/InternalProvider/class.ilLTIToolProvider.php index b2f9c61ff4e9..c8ea2c1d4cb3 100644 --- a/Services/LTI/classes/InternalProvider/class.ilLTIToolProvider.php +++ b/Services/LTI/classes/InternalProvider/class.ilLTIToolProvider.php @@ -788,7 +788,7 @@ private function authenticate() $this->context->save();//ACHTUNG TODO UWE } - $this->logger->dump(get_class($this->context)); + // $this->logger->dump(get_class($this->context)); if ($this->ok && isset($this->resourceLink)) { diff --git a/Services/Language/classes/Setup/class.ilSetupLanguage.php b/Services/Language/classes/Setup/class.ilSetupLanguage.php index 11f0cb7a07fe..8840194934ba 100755 --- a/Services/Language/classes/Setup/class.ilSetupLanguage.php +++ b/Services/Language/classes/Setup/class.ilSetupLanguage.php @@ -555,7 +555,7 @@ protected function insertLanguage($lang_key, $scope = '') $lang_file = "ilias_" . $lang_key . ".lang" . $scopeExtension; - if ($lang_file) { + if (is_file($lang_file)) { // initialize the array for updating lng_modules below $lang_array = array(); $lang_array["common"] = array(); @@ -566,12 +566,20 @@ protected function insertLanguage($lang_key, $scope = '') if (empty($scope)) { $local_changes = $this->getLocalChanges($lang_key); } elseif ($scope == 'local') { + // set the change date to import time for a local file + // get the modification date of the local file + // get the newer local changes for a local file $change_date = date("Y-m-d H:i:s", time()); $min_date = date("Y-m-d H:i:s", filemtime($lang_file)); $local_changes = $this->getLocalChanges($lang_key, $min_date); } foreach ($content as $key => $val) { + // split the line of the language file + // [0]: module + // [1]: identifier + // [2]: value + // [3]: comment (optional) $separated = explode($this->separator, trim($val)); //get position of the comment_separator @@ -627,7 +635,7 @@ protected function insertLanguage($lang_key, $scope = '') foreach ($lang_array as $module => $lang_arr) { if ($scope == "local") { $q = "SELECT * FROM lng_modules WHERE " . - " lang_key = " . $ilDB->quote($this->key, "text") . + " lang_key = " . $ilDB->quote($lang_key, "text") . " AND module = " . $ilDB->quote($module, "text"); $set = $ilDB->query($q); $row = $ilDB->fetchAssoc($set); diff --git a/Services/Language/classes/class.ilCachedLanguage.php b/Services/Language/classes/class.ilCachedLanguage.php index 79e3f08c3444..fa5c88848ef2 100755 --- a/Services/Language/classes/class.ilCachedLanguage.php +++ b/Services/Language/classes/class.ilCachedLanguage.php @@ -76,6 +76,20 @@ public function writeToCache() } } + /** + * Delete the cache entry for this language without flushing the whole global cache + * Using this function avoids a flush loop when languages are updated + * A missing entry will cause the next request to refill the cache in the constructor of this class + * @see mantis #28818 + */ + public function deleteInCache() { + if ($this->global_cache->isActive()) { + $this->global_cache->delete('translations_' . $this->getLanguageKey()); + $this->setLoaded(false); + } + } + + protected function readFromDB() { diff --git a/Services/Language/classes/class.ilObjLanguage.php b/Services/Language/classes/class.ilObjLanguage.php index 981cf4998de7..6e261472a510 100755 --- a/Services/Language/classes/class.ilObjLanguage.php +++ b/Services/Language/classes/class.ilObjLanguage.php @@ -574,7 +574,8 @@ final public static function replaceLangModule($a_key, $a_module, $a_array) global $DIC; $ilDB = $DIC->database(); - ilGlobalCache::flushAll(); + // avoid flushing the whole cache (see mantis #28818) + ilCachedLanguage::getInstance($a_key)->deleteInCache(); $ilDB->manipulate(sprintf( "DELETE FROM lng_modules WHERE lang_key = %s AND module = %s", @@ -627,7 +628,8 @@ final public static function replaceLangEntry( global $DIC; $ilDB = $DIC->database(); - ilGlobalCache::flushAll(); + // avoid a cache flush here (see mantis #28818) + // ilGlobalCache::flushAll(); if (isset($a_remarks)) { $a_remarks = substr($a_remarks, 0, 250); diff --git a/Services/Mail/classes/Mime/Sender/class.ilMailMimeSenderFactory.php b/Services/Mail/classes/Mime/Sender/class.ilMailMimeSenderFactory.php index a84dc0035b2e..2ead2d84f792 100644 --- a/Services/Mail/classes/Mime/Sender/class.ilMailMimeSenderFactory.php +++ b/Services/Mail/classes/Mime/Sender/class.ilMailMimeSenderFactory.php @@ -25,7 +25,7 @@ public function __construct(ilSetting $settings, int $anonymousUsrId = null) { $this->settings = $settings; if (null === $anonymousUsrId && defined('ANONYMOUS_USER_ID')) { - $anonymousUsrId = ANONYMOUS_USER_ID; + $anonymousUsrId = (int) ANONYMOUS_USER_ID; } $this->anonymousUsrId = $anonymousUsrId; } diff --git a/Services/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php b/Services/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php index 7de03f40a54c..25b621c8480a 100644 --- a/Services/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php +++ b/Services/Mail/classes/Mime/Transport/class.ilMailMimeTransportBase.php @@ -53,6 +53,7 @@ protected function resetMailer() : void $this->getMailer()->clearAllRecipients(); $this->getMailer()->clearAttachments(); $this->getMailer()->clearReplyTos(); + $this->getMailer()->ErrorInfo = ''; } /** diff --git a/Services/Mail/classes/class.ilAccountMail.php b/Services/Mail/classes/class.ilAccountMail.php index 3a1548983940..7b8f69c11dd4 100644 --- a/Services/Mail/classes/class.ilAccountMail.php +++ b/Services/Mail/classes/class.ilAccountMail.php @@ -267,7 +267,7 @@ public function send() include_once 'Services/Mail/classes/class.ilMimeMail.php'; $mmail = new ilMimeMail(); $mmail->From($senderFactory->system()); - $mmail->Subject($mail_subject); + $mmail->Subject($mail_subject, true); $mmail->To($user->getEmail()); $mmail->Body($mail_body); diff --git a/Services/Mail/classes/class.ilMail.php b/Services/Mail/classes/class.ilMail.php index 3cc4a59858ff..0e8a9666430a 100755 --- a/Services/Mail/classes/class.ilMail.php +++ b/Services/Mail/classes/class.ilMail.php @@ -834,7 +834,6 @@ protected function sendChanneledMails( $individualMessage = $message; if ($usePlaceholders) { $individualMessage = $this->replacePlaceholders($message, $user->getId()); - ; $usrIdToMessageMap[$user->getId()] = $individualMessage; } @@ -857,12 +856,24 @@ protected function sendChanneledMails( continue; } else { $this->logger->debug(sprintf( - "Recipient with id %s will additionally receive external emails sent to: %s", + "Recipient with id %s will additionally receive external emails " . + "(because the user wants to receive it externally, or the user cannot access " . + "the internal mail system) sent to: %s", $user->getId(), implode(', ', $emailAddresses) )); } + } else { + $this->logger->debug(sprintf( + "Recipient with id %s is does not want to receive external emails", + $user->getId() + )); } + } else { + $this->logger->debug(sprintf( + "Recipient with id %s is inactive and will not receive external emails", + $user->getId() + )); } $mbox = clone $this->mailbox; diff --git a/Services/Mail/classes/class.ilMailExplorer.php b/Services/Mail/classes/class.ilMailExplorer.php index ad9c491dee25..467944b061a9 100755 --- a/Services/Mail/classes/class.ilMailExplorer.php +++ b/Services/Mail/classes/class.ilMailExplorer.php @@ -53,6 +53,14 @@ protected function initFolder() : void $this->currentFolderId = (int) $folderId; } + /** + * @return string + */ + public function getTreeLabel() + { + return $this->lng->txt("mail_folders"); + } + /** * @inheritDoc */ @@ -61,7 +69,7 @@ public function getTreeComponent() : Tree $f = $this->ui->factory(); $tree = $f->tree() - ->expandable($this) + ->expandable($this->getTreeLabel(), $this) ->withData($this->tree->getChilds((int) $this->tree->readRootId())) ->withHighlightOnNodeClick(false); diff --git a/Services/Mail/classes/class.ilMailFolderGUI.php b/Services/Mail/classes/class.ilMailFolderGUI.php index 87dfd59add90..f29b797f7426 100644 --- a/Services/Mail/classes/class.ilMailFolderGUI.php +++ b/Services/Mail/classes/class.ilMailFolderGUI.php @@ -778,19 +778,19 @@ protected function showMail() : void } $to = new ilCustomInputGUI($this->lng->txt('mail_to') . ':'); - $to->setHtml(ilUtil::htmlencodePlainString($this->umail->formatNamesForOutput($mailData['rcp_to']), false)); + $to->setHtml(ilUtil::htmlencodePlainString($this->umail->formatNamesForOutput((string) $mailData['rcp_to']), false)); $form->addItem($to); if ($mailData['rcp_cc']) { $cc = new ilCustomInputGUI($this->lng->txt('cc') . ':'); - $cc->setHtml(ilUtil::htmlencodePlainString($this->umail->formatNamesForOutput($mailData['rcp_cc']), false)); + $cc->setHtml(ilUtil::htmlencodePlainString($this->umail->formatNamesForOutput((string) $mailData['rcp_cc']), false)); $form->addItem($cc); } if ($mailData['rcp_bcc']) { $bcc = new ilCustomInputGUI($this->lng->txt('bc') . ':'); $bcc->setHtml(ilUtil::htmlencodePlainString( - $this->umail->formatNamesForOutput($mailData['rcp_bcc']), + $this->umail->formatNamesForOutput((string) $mailData['rcp_bcc']), false )); $form->addItem($bcc); @@ -962,7 +962,7 @@ protected function deliverFile() : void } $filename = $this->httpRequest->getParsedBody()['filename'] ?? ''; - if (strlen(ilSession::get('filename')) > 0) { + if (is_string(ilSession::get('filename')) && strlen(ilSession::get('filename')) > 0) { $filename = ilSession::get('filename'); ilSession::set('filename', null); } diff --git a/Services/Mail/classes/class.ilMailTemplateContext.php b/Services/Mail/classes/class.ilMailTemplateContext.php index b02b173d8400..4f5aa428c886 100644 --- a/Services/Mail/classes/class.ilMailTemplateContext.php +++ b/Services/Mail/classes/class.ilMailTemplateContext.php @@ -92,7 +92,7 @@ abstract public function getDescription() : string; /** * @return array */ - final private function getGenericPlaceholders() : array + private function getGenericPlaceholders() : array { return [ 'mail_salutation' => [ diff --git a/Services/Mail/classes/class.ilObjMailGUI.php b/Services/Mail/classes/class.ilObjMailGUI.php index 571488ac79be..31f4742301ed 100755 --- a/Services/Mail/classes/class.ilObjMailGUI.php +++ b/Services/Mail/classes/class.ilObjMailGUI.php @@ -457,12 +457,14 @@ protected function getExternalSettingsForm() $user = new ilTextInputGUI($this->lng->txt('mail_smtp_user'), 'mail_smtp_user'); $user->setDisabled(!$this->isEditingAllowed()); + $user->setDisableHtmlAutoComplete(true); $smtp->addSubItem($user); $password = new ilPasswordInputGUI($this->lng->txt('mail_smtp_password'), 'mail_smtp_password'); $password->setRetype(false); $password->setSkipSyntaxCheck(true); $password->setDisabled(!$this->isEditingAllowed()); + $password->setDisableHtmlAutoComplete(true); $smtp->addSubItem($password); $pre = new ilTextInputGUI($this->lng->txt('mail_subject_prefix'), 'mail_subject_prefix'); diff --git a/Services/Mail/classes/class.ilPDMailGUI.php b/Services/Mail/classes/class.ilPDMailGUI.php index bb1ceb464c47..ce7fc22d32e4 100644 --- a/Services/Mail/classes/class.ilPDMailGUI.php +++ b/Services/Mail/classes/class.ilPDMailGUI.php @@ -105,12 +105,12 @@ public function getPDMailHTML($a_mail_id, $a_mobj_id) } $tpl->setVariable('TXT_TO', $this->lng->txt('mail_to')); - $tpl->setVariable('TO', $umail->formatNamesForOutput($mail_data['rcp_to'])); + $tpl->setVariable('TO', $umail->formatNamesForOutput((string) $mail_data['rcp_to'])); if ($mail_data['rcp_cc']) { $tpl->setCurrentBlock('cc'); $tpl->setVariable('TXT_CC', $this->lng->txt('cc')); - $tpl->setVariable('CC', $umail->formatNamesForOutput($mail_data['rcp_cc'])); + $tpl->setVariable('CC', $umail->formatNamesForOutput((string) $mail_data['rcp_cc'])); $tpl->parseCurrentBlock(); } diff --git a/Services/Mail/js/ilMailComposeFunctions.js b/Services/Mail/js/ilMailComposeFunctions.js index ebaf143f35fd..12e682f9eeb6 100644 --- a/Services/Mail/js/ilMailComposeFunctions.js +++ b/Services/Mail/js/ilMailComposeFunctions.js @@ -1,59 +1,115 @@ -// inserts placeholder at current coursor position -function insertTextIntoTextField(text, obj_id) -{ - if (text && obj_id) - { - var objTextField = document.getElementById(obj_id); - - if (document.selection) - { - objTextField.focus(); - sel = document.selection.createRange(); - sel.text = text; - } - else if (objTextField.selectionStart || objTextField.selectionStart == '0') - { - var startPos = objTextField.selectionStart; - var endPos = objTextField.selectionEnd; - var TextFieldValue = objTextField.value; - - objTextField.value = TextFieldValue.substring(0, startPos) + - text + - TextFieldValue.substring(endPos, TextFieldValue.length); +/* global il */ +il = il || {}; + +(function init(scope, factory) { + scope.Mail = factory(); +}(il, () => { + let browserSupportsTextareaTextNodes; + + function canManipulateViaTextNodes(input) { + if (input.nodeName !== 'TEXTAREA') { + return false; + } + + if (typeof browserSupportsTextareaTextNodes === 'undefined') { + const textarea = document.createElement('textarea'); + textarea.value = '1'; + browserSupportsTextareaTextNodes = !!textarea.firstChild; + } + + return browserSupportsTextareaTextNodes; + } + + const methods = {}; + + methods.insertTextIntoTextField = function (elementId, text) { + const input = document.getElementById(elementId); + + input.focus(); + + const isSuccess = document.execCommand('insertText', false, text); + if (!isSuccess) { + const start = input.selectionStart; + const end = input.selectionEnd; + + if (typeof input.setRangeText === 'function') { + input.setRangeText(text); + } else { + const range = document.createRange(); + const textNode = document.createTextNode(text); + + if (canManipulateViaTextNodes(input)) { + let node = input.firstChild; + + if (!node) { + input.appendChild(textNode); + } else { + let offset = 0; + let startNode = null; + let endNode = null; + + while (node && (startNode === null || endNode === null)) { + const nodeLength = node.nodeValue.length; + + if (start >= offset && start <= offset + nodeLength) { + range.setStart((startNode = node), start - offset); + } + + if (end >= offset && end <= offset + nodeLength) { + range.setEnd((endNode = node), end - offset); + } + + offset += nodeLength; + node = node.nextSibling; + } + + if (start !== end) { + range.deleteContents(); + } + } } - else - { - objTextField.value += text; + + if (canManipulateViaTextNodes(input) && range.commonAncestorContainer.nodeName === '#text') { + range.insertNode(textNode); + } else { + const { value } = input; + input.value = value.slice(0, start) + text + value.slice(end); } + } + + input.setSelectionRange(start + text.length, start + text.length); + + const e = document.createEvent('UIEvent'); + e.initEvent('input', true, false); + input.dispatchEvent(e); } -} + }; + + return methods; +})); // removes ',' at the ending of recipients textfield -function getStripCommaCallback(obj) -{ - return function () - { - var val = obj.value.replace(/^\s+/, '').replace(/\s+$/, ''); - var stripcount = 0; - var i; - for (i = 0; i < val.length && val.charAt(val.length - i - 1) == ','; i++) - stripcount++; - obj.value = val.substr(0, val.length - stripcount); - } +function getStripCommaCallback(obj) { + return function () { + const val = obj.value.replace(/^\s+/, '').replace(/\s+$/, ''); + let stripcount = 0; + let i; + for (i = 0; i < val.length && val.charAt(val.length - i - 1) === ','; i++) { + stripcount++; + } + obj.value = val.substr(0, val.length - stripcount); + }; } // initializes textfields for comma stripping on leaving recipients textfields il.Util.addOnLoad( - function() - { - var ar = ['rcp_to', 'rcp_cc', 'rcp_bcc']; - for(var i = 0; i < ar.length; i++) - { - var obj = document.getElementById(ar[i]); - if (obj) - { - obj.onblur = getStripCommaCallback(document.getElementById(ar[i])); - } - } - } + () => { + const ar = ['rcp_to', 'rcp_cc', 'rcp_bcc']; + for (let i = 0; i < ar.length; i++) { + const obj = document.getElementById(ar[i]); + if (obj) { + obj.onblur = getStripCommaCallback(document.getElementById(ar[i])); + } + } + } ); diff --git a/Services/Mail/templates/default/tpl.mail_manual_placeholders.html b/Services/Mail/templates/default/tpl.mail_manual_placeholders.html index 502a994a8faf..0815899bb215 100644 --- a/Services/Mail/templates/default/tpl.mail_manual_placeholders.html +++ b/Services/Mail/templates/default/tpl.mail_manual_placeholders.html @@ -4,7 +4,7 @@ {TXT_USE_PLACEHOLDERS}:
{TXT_PLACEHOLDERS_ADVISE}
- [{MANUAL_PLACEHOLDER}]: {TXT_MANUAL_PLACEHOLDER}
+ [{MANUAL_PLACEHOLDER}]: {TXT_MANUAL_PLACEHOLDER}
diff --git a/Services/Mail/templates/default/tpl.mail_new_placeholders.html b/Services/Mail/templates/default/tpl.mail_new_placeholders.html index 16cbdbcc2043..0cdb3965a605 100644 --- a/Services/Mail/templates/default/tpl.mail_new_placeholders.html +++ b/Services/Mail/templates/default/tpl.mail_new_placeholders.html @@ -4,7 +4,7 @@ {TXT_USE_PLACEHOLDERS}:
{TXT_PLACEHOLDERS_ADVISE}
- [{MANUAL_PLACEHOLDER}]: {TXT_MANUAL_PLACEHOLDER}
+ [{MANUAL_PLACEHOLDER}]: {TXT_MANUAL_PLACEHOLDER}
diff --git a/Services/Mail/templates/default/tpl.mail_print.html b/Services/Mail/templates/default/tpl.mail_print.html index 0df3607de626..ba34b8ca0890 100755 --- a/Services/Mail/templates/default/tpl.mail_print.html +++ b/Services/Mail/templates/default/tpl.mail_print.html @@ -42,5 +42,12 @@
{MAIL_MESSAGE}
+ + + \ No newline at end of file diff --git a/Services/MainMenu/classes/Administration/class.ilMMTopItemGUI.php b/Services/MainMenu/classes/Administration/class.ilMMTopItemGUI.php index 08ec0d4a6864..b7a7d6431b28 100644 --- a/Services/MainMenu/classes/Administration/class.ilMMTopItemGUI.php +++ b/Services/MainMenu/classes/Administration/class.ilMMTopItemGUI.php @@ -26,6 +26,7 @@ class ilMMTopItemGUI extends ilMMAbstractItemGUI const CMD_RENDER_INTERRUPTIVE = 'render_interruptive_modal'; const CMD_CONFIRM_RESTORE = 'confirmRestore'; const CMD_UPLOAD = 'upload'; + const CMD_FLUSH = 'flush'; private function dispatchCommand($cmd) { @@ -85,6 +86,10 @@ private function dispatchCommand($cmd) $this->access->checkAccessAndThrowException('write'); $this->renderInterruptiveModal(); break; + case self::CMD_FLUSH: + $this->access->checkAccessAndThrowException('write'); + $this->flush(); + break; case self::CMD_UPLOAD: $this->access->checkAccessAndThrowException('write'); return $this->upload(); @@ -151,6 +156,14 @@ private function index() : string $b->setCaption($this->lng->txt(self::CMD_RESTORE), false); $b->setUrl($this->ctrl->getLinkTarget($this, self::CMD_CONFIRM_RESTORE)); $this->toolbar->addButtonInstance($b); + + // REMOVE LOST ITEMS + if ($this->repository->hasLostItems()) { + $b = ilLinkButton::getInstance(); + $b->setUrl($this->ctrl->getLinkTarget($this, self::CMD_FLUSH)); + $b->setCaption($this->lng->txt(self::CMD_FLUSH), false); + $this->toolbar->addButtonInstance($b); + } } // TABLE @@ -268,6 +281,13 @@ private function confirmRestore() : string return $c->getHTML(); } + private function flush() : void + { + $this->repository->flushLostItems(); + ilUtil::sendSuccess($this->lng->txt("msg_subitem_flushed"), true); + $this->cancel(); + } + private function restore() : void { ilMMItemStorage::flushDB(); diff --git a/Services/MainMenu/classes/Items/class.ilMMItemRepository.php b/Services/MainMenu/classes/Items/class.ilMMItemRepository.php index 13de59e8b5a0..2a4193485924 100644 --- a/Services/MainMenu/classes/Items/class.ilMMItemRepository.php +++ b/Services/MainMenu/classes/Items/class.ilMMItemRepository.php @@ -6,6 +6,7 @@ use ILIAS\GlobalScreen\Scope\MainMenu\Collector\Handler\TypeHandler; use ILIAS\GlobalScreen\Scope\MainMenu\Factory\isItem; use ILIAS\GlobalScreen\Scope\MainMenu\Factory\isParent; +use ILIAS\GlobalScreen\Scope\MainMenu\Factory\Item\Lost; use ILIAS\GlobalScreen\Scope\MainMenu\Factory\TopItem\TopParentItem; use ILIAS\MainMenu\Provider\CustomMainBarProvider; @@ -106,6 +107,41 @@ public function getSubItemsForTable() : array return $return; } + public function flushLostItems() + { + foreach ($this->getTopItems() as $item) { + $item_facade = $this->getItemFacade($this->services->identification()->fromSerializedIdentification($item['identification'])); + if (Lost::class === $item_facade->getType()) { + $item_facade->delete(); + } + } + + foreach ($this->getSubItemsForTable() as $item) { + $item_facade = $this->getItemFacade($this->services->identification()->fromSerializedIdentification($item['identification'])); + if (Lost::class === $item_facade->getType()) { + $item_facade->delete(); + } + } + } + + public function hasLostItems() : bool + { + foreach ($this->getTopItems() as $item) { + $item_facade = $this->getItemFacade($this->services->identification()->fromSerializedIdentification($item['identification'])); + if (Lost::class === $item_facade->getType()) { + return true; + } + } + + foreach ($this->getSubItemsForTable() as $item) { + $item_facade = $this->getItemFacade($this->services->identification()->fromSerializedIdentification($item['identification'])); + if (Lost::class === $item_facade->getType()) { + return true; + } + } + return false; + } + /** * @param IdentificationInterface|null $identification * @return ilMMItemFacadeInterface diff --git a/Services/MetaData/classes/class.ilMDEditorGUI.php b/Services/MetaData/classes/class.ilMDEditorGUI.php index 786eed9e78c0..fec437d9b11c 100644 --- a/Services/MetaData/classes/class.ilMDEditorGUI.php +++ b/Services/MetaData/classes/class.ilMDEditorGUI.php @@ -222,7 +222,7 @@ public function listQuickEdit_scorm() $this->tpl->setCurrentBlock("keyword_loop"); $this->tpl->setVariable("KEYWORD_LOOP_VAL", ilUtil::prepareFormOutput( - implode($keyword_set, ", ") + implode(", ", $keyword_set) )); $this->tpl->setVariable("LANG", $lang); $this->tpl->setVariable("KEYWORD_LOOP_VAL_LANGUAGE", $this->__showLanguageSelect( diff --git a/Services/MetaData/classes/class.ilMDLanguage.php b/Services/MetaData/classes/class.ilMDLanguage.php index 7c7549b95ec5..a8a7758fc767 100644 --- a/Services/MetaData/classes/class.ilMDLanguage.php +++ b/Services/MetaData/classes/class.ilMDLanguage.php @@ -85,7 +85,6 @@ public function save() $fields = $this->__getFields(); $fields['meta_language_id'] = array('integer',$next_id = $ilDB->nextId('il_meta_language')); - if ($this->db->insert('il_meta_language', $fields)) { $this->setMetaId($next_id); return $this->getMetaId(); diff --git a/Services/MyStaff/classes/ListCompetences/Skills/class.ilMStListCompetencesSkills.php b/Services/MyStaff/classes/ListCompetences/Skills/class.ilMStListCompetencesSkills.php index 9966a62a2a01..2638ec0eab58 100644 --- a/Services/MyStaff/classes/ListCompetences/Skills/class.ilMStListCompetencesSkills.php +++ b/Services/MyStaff/classes/ListCompetences/Skills/class.ilMStListCompetencesSkills.php @@ -53,6 +53,14 @@ public function getData(array $options) $data = []; $users_per_position = ilMyStaffAccess::getInstance()->getUsersForUserPerPosition($this->dic->user()->getId()); + if (empty($users_per_position)) { + if ($options["count"]) { + return 0; + } else { + return []; + } + } + $arr_query = []; foreach ($users_per_position as $position_id => $users) { $obj_ids = ilMyStaffAccess::getInstance()->getIdsForUserAndOperation($this->dic->user()->getId(), $operation_access); diff --git a/Services/MyStaff/classes/ListCourses/class.ilMStListCourses.php b/Services/MyStaff/classes/ListCourses/class.ilMStListCourses.php index ea2c60616766..1c278c174d99 100644 --- a/Services/MyStaff/classes/ListCourses/class.ilMStListCourses.php +++ b/Services/MyStaff/classes/ListCourses/class.ilMStListCourses.php @@ -78,6 +78,14 @@ public function getData(array $arr_usr_ids = array(), array $options = array()) $data = []; $users_per_position = ilMyStaffAccess::getInstance()->getUsersForUserPerPosition($this->dic->user()->getId()); + if (empty($users_per_position)) { + if ($options["count"]) { + return 0; + } else { + return []; + } + } + $arr_query = []; foreach ($users_per_position as $position_id => $users) { $obj_ids = ilMyStaffAccess::getInstance()->getIdsForUserAndOperation($this->dic->user()->getId(), $operation_access); diff --git a/Services/News/classes/class.ilNewsDataSet.php b/Services/News/classes/class.ilNewsDataSet.php index 7107ec07a7d5..c12c1aa9415b 100644 --- a/Services/News/classes/class.ilNewsDataSet.php +++ b/Services/News/classes/class.ilNewsDataSet.php @@ -20,7 +20,7 @@ class ilNewsDataSet extends ilDataSet */ public function getSupportedVersions() { - return array("4.1.0"); + return array("5.4.0", "4.1.0"); } /** @@ -45,6 +45,7 @@ protected function getTypes($a_entity, $a_version) if ($a_entity == "news") { switch ($a_version) { case "4.1.0": + case "5.4.0": return array( "Id" => "integer", "Title" => "text", @@ -63,6 +64,20 @@ protected function getTypes($a_entity, $a_version) ); } } + if ($a_entity == "news_settings") { + switch ($a_version) { + case "5.4.0": + return array( + "ObjId" => "integer", + "PublicFeed" => "integer", + "DefaultVisibility" => "text", + "KeepRssMin" => "integer", + "HideNewsPerDate" => "integer", + "HideNewsDate" => "text", + "PublicNotifications" => "integer" + ); + } + } } /** @@ -82,6 +97,7 @@ public function readData($a_entity, $a_version, $a_ids, $a_field = "") if ($a_entity == "news") { switch ($a_version) { case "4.1.0": + case "5.4.0": $this->getDirectDataFromQuery("SELECT id, title, content, priority," . " context_obj_id, context_obj_type, context_sub_obj_id, context_sub_obj_type, " . " content_type, visibility, content_long, content_is_lang_var, mob_id, playtime" . @@ -91,6 +107,23 @@ public function readData($a_entity, $a_version, $a_ids, $a_field = "") break; } } + + if ($a_entity == "news_settings") { + switch ($a_version) { + case "5.4.0": + foreach ($a_ids as $obj_id) { + $this->data[$obj_id]["ObjId"] = $obj_id; + $this->data[$obj_id]["PublicFeed"] = ilBlockSetting::_lookup("news", "public_feed", 0, $obj_id); + $this->data[$obj_id]["KeepRssMin"] = (int) ilBlockSetting::_lookup("news", "keep_rss_min", 0, $obj_id); + $this->data[$obj_id]["DefaultVisibility"] = ilBlockSetting::_lookup("news", "default_visibility", 0, $obj_id); + $this->data[$obj_id]["HideNewsPerDate"] = (int) ilBlockSetting::_lookup("news", "hide_news_per_date", 0, $obj_id); + $this->data[$obj_id]["HideNewsDate"] = ilBlockSetting::_lookup("news", "hide_news_date", 0, $obj_id); + $this->data[$obj_id]["PublicNotifications"] = (int) ilBlockSetting::_lookup("news", "public_notifications", 0, $obj_id); + } + break; + } + } + } /** @@ -143,6 +176,32 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ $newObj->create(); $a_mapping->addMapping("Services/News", "news", $a_rec["Id"], $newObj->getId()); break; + + case "news_settings": + + $dummy_dataset = new ilObjectDataSet(); + $new_obj_id = $dummy_dataset->getNewObjId($a_mapping, $a_rec["ObjId"]); + + if ($new_obj_id > 0 && $a_schema_version == "5.4.0") { + foreach ([ + "public_feed" => "PublicFeed", + "keep_rss_min" => "KeepRssMin", + "default_visibility" => "DefaultVisibility", + "hide_news_per_date" => "HideNewsPerDate", + "hide_news_date" => "HideNewsDate", + "public_notifications" => "PublicNotifications" + ] as $set => $field) { + ilBlockSetting::_write( + "news", + $set, + $a_rec[$field], + 0, + $new_obj_id + ); + } + } + break; + } } } diff --git a/Services/News/classes/class.ilNewsExporter.php b/Services/News/classes/class.ilNewsExporter.php index 2f976a2ff5e6..c0d37e477836 100644 --- a/Services/News/classes/class.ilNewsExporter.php +++ b/Services/News/classes/class.ilNewsExporter.php @@ -77,6 +77,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/Services/News/news/5_4", + "xsd_file" => "ilias_news_5_4.xsd", + "uses_dataset" => true, + "min" => "5.4.0", + "max" => ""), "4.1.0" => array( "namespace" => "http://www.ilias.de/Services/News/news/4_1", "xsd_file" => "ilias_news_4_1.xsd", diff --git a/Services/News/classes/class.ilNewsForContextBlockGUI.php b/Services/News/classes/class.ilNewsForContextBlockGUI.php index 41cbfae36e71..144cd3ac5e8f 100755 --- a/Services/News/classes/class.ilNewsForContextBlockGUI.php +++ b/Services/News/classes/class.ilNewsForContextBlockGUI.php @@ -1181,12 +1181,11 @@ public static function addToSettingsForm(ilFormPropertyGUI $a_input) $block_id ); - $default_visibility = ilBlockSetting::_lookup(self::$block_type, "default_visibility_option", 0, $block_id); + $default_visibility = ilBlockSetting::_lookup(self::$block_type, "default_visibility", 0, $block_id); if ($default_visibility == "") { $default_visibility = ilNewsItem::_getDefaultVisibilityForRefId($_GET["ref_id"]); } - $radio_group = new ilRadioGroupInputGUI($lng->txt("news_default_visibility"), "default_visibility"); $radio_option = new ilRadioOption($lng->txt("news_visibility_users"), "users"); $radio_group->addOption($radio_option); @@ -1214,7 +1213,6 @@ public static function writeSettings($a_values) global $DIC; $block_id = $DIC->ctrl()->getContextObjId(); - foreach ($a_values as $key => $value) { ilBlockSetting::_write(self::$block_type, $key, $value, 0, $block_id); } @@ -1239,7 +1237,6 @@ public function saveSettings() $ilUser = $this->user; $this->initSettingsForm(); - if ($this->settings_form->checkInput()) { $news_set = new ilSetting("news"); $enable_internal_rss = $news_set->get("enable_rss_for_internal"); diff --git a/Services/Object/CommonSettings/TileImage/classes/class.ilObjectTileImage.php b/Services/Object/CommonSettings/TileImage/classes/class.ilObjectTileImage.php index c07e5fb4c558..21e83bf23764 100644 --- a/Services/Object/CommonSettings/TileImage/classes/class.ilObjectTileImage.php +++ b/Services/Object/CommonSettings/TileImage/classes/class.ilObjectTileImage.php @@ -86,7 +86,7 @@ public function copy(int $target_obj_id) */ public function delete() { - if ($this->exists()) { + if ($this->web->hasDir($this->getRelativeDirectory())) { try { $this->web->deleteDir($this->getRelativeDirectory()); } catch (\Exception $e) { diff --git a/Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php b/Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php index 9b5f83f991e8..00572252f08b 100644 --- a/Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php +++ b/Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php @@ -79,7 +79,7 @@ public function copy(int $targetObjId) */ public function delete() { - if ($this->exists()) { + if ($this->webDirectory->hasDir($this->getIconDirectory())) { try { $this->webDirectory->deleteDir($this->getIconDirectory()); } catch (\Exception $e) { diff --git a/Services/Object/classes/class.ilObjectActivationGUI.php b/Services/Object/classes/class.ilObjectActivationGUI.php index 4cafb1414fe1..54232cb64794 100644 --- a/Services/Object/classes/class.ilObjectActivationGUI.php +++ b/Services/Object/classes/class.ilObjectActivationGUI.php @@ -288,6 +288,7 @@ public function update() $form = $this->initFormEdit(); if ($form->checkInput()) { + $valid = true; $activation = new ilObjectActivation(); $activation->read($this->getItemId()); @@ -295,9 +296,15 @@ public function update() $this->getActivation()->setTimingType(ilObjectActivation::TIMINGS_ACTIVATION); $timing_start = $form->getItemByPostVar('timing_start')->getDate(); - $this->getActivation()->setTimingStart($timing_start ? $timing_start->get(IL_CAL_UNIX) : null); - $timing_end = $form->getItemByPostVar('timing_end')->getDate(); + + if ($timing_start && $timing_end && ilDateTime::_after($timing_start, $timing_end)) { + $form->getItemByPostVar('timing_start')->setAlert($this->lng->txt('crs_timing_err_start_end')); + $form->getItemByPostVar('timing_end')->setAlert($this->lng->txt('crs_timing_err_start_end')); + $valid = false; + } + + $this->getActivation()->setTimingStart($timing_start ? $timing_start->get(IL_CAL_UNIX) : null); $this->getActivation()->setTimingEnd($timing_end ? $timing_end->get(IL_CAL_UNIX) : null); $this->getActivation()->toggleVisible((bool) $form->getInput('visible')); @@ -305,14 +312,18 @@ public function update() $this->getActivation()->setTimingType(ilObjectActivation::TIMINGS_DEACTIVATED); } + if ($valid) { $this->getActivation()->update($this->getItemId(), $this->getParentId()); ilUtil::sendSuccess($this->lng->txt('settings_saved'), true); $this->ctrl->redirect($this, "edit"); } else { + ilUtil::sendFailure($this->lng->txt('form_input_not_valid')); + } + } + $form->setValuesByPost(); $this->edit($form); } - } /** * @return bool diff --git a/Services/Object/classes/class.ilObjectDataSet.php b/Services/Object/classes/class.ilObjectDataSet.php index 6442d50d9a75..fb25bd24b2f8 100644 --- a/Services/Object/classes/class.ilObjectDataSet.php +++ b/Services/Object/classes/class.ilObjectDataSet.php @@ -349,7 +349,7 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ * @param $old_id * @return mixed */ - protected function getNewObjId($a_mapping, $old_id) + public function getNewObjId($a_mapping, $old_id) { global $DIC; diff --git a/Services/Object/templates/default/tpl.creation_acc_head.html b/Services/Object/templates/default/tpl.creation_acc_head.html index daac0627b371..282e2a5c3805 100644 --- a/Services/Object/templates/default/tpl.creation_acc_head.html +++ b/Services/Object/templates/default/tpl.creation_acc_head.html @@ -1 +1 @@ -

{TITLE}

\ No newline at end of file +

{TITLE}

\ No newline at end of file diff --git a/Services/OpenIdConnect/templates/default/tpl.login_element.html b/Services/OpenIdConnect/templates/default/tpl.login_element.html index 4f618d633c3d..d4ee58a96ad7 100644 --- a/Services/OpenIdConnect/templates/default/tpl.login_element.html +++ b/Services/OpenIdConnect/templates/default/tpl.login_element.html @@ -1,6 +1,6 @@
-

Login to ILIAS via Open ID Connect

+

{TXT_OIDCONNECT_HEADER}

diff --git a/Services/PDFGeneration/classes/renderer/wkhtmltopdf/class.ilWkhtmlToPdfConfig.php b/Services/PDFGeneration/classes/renderer/wkhtmltopdf/class.ilWkhtmlToPdfConfig.php index c6e2fee7ef68..ea71324355ef 100644 --- a/Services/PDFGeneration/classes/renderer/wkhtmltopdf/class.ilWkhtmlToPdfConfig.php +++ b/Services/PDFGeneration/classes/renderer/wkhtmltopdf/class.ilWkhtmlToPdfConfig.php @@ -207,6 +207,7 @@ protected function readConfigFromArray($config) $this->setKeyIfExists('setFooterType', 'footer_select', $config); $this->setKeyIfExists('setHeaderHtmlSpacing', 'head_html_spacing', $config); $this->setKeyIfExists('setHeaderHtmlLine', 'head_html_line', $config); + $this->setKeyIfExists('setHeaderHtml', 'head_html', $config); $this->setKeyIfExists('setHeaderTextLine', 'head_text_line', $config); $this->setKeyIfExists('setHeaderTextSpacing', 'head_text_spacing', $config); $this->setKeyIfExists('setHeaderTextRight', 'head_text_right', $config); @@ -251,8 +252,6 @@ protected function readConfigFromObject($config) $this->setPageSize($config->getPageSize()); $this->setMarginLeft($config->getMarginLeft()); $this->setMarginRight($config->getMarginRight()); - $this->setFooterHtmlSpacing($config->getFooterHtmlSpacing()); - $this->setFooterHtml($config->getFooterHtml()); $this->setFooterTextLine($config->isFooterTextLine()); $this->setFooterTextCenter($config->getFooterTextCenter()); $this->setFooterTextSpacing($config->getFooterTextSpacing()); @@ -261,7 +260,11 @@ protected function readConfigFromObject($config) $this->setFooterType($config->getFooterType()); $this->setFooterHtmlSpacing($config->getFooterHtmlSpacing()); + $this->setFooterHtml($config->getFooterHtml()); $this->setFooterHtmlLine($config->isFooterHtmlLine()); + $this->setHeaderHtmlSpacing($config->getHeaderHtmlSpacing()); + $this->setHeaderHtml($config->getHeaderHtml()); + $this->setHeaderHtmlLine($config->isHeaderHtmlLine()); $this->setHeaderTextLine($config->isHeaderTextLine()); $this->setHeaderTextSpacing($config->getHeaderTextSpacing()); $this->setHeaderTextRight($config->getHeaderTextRight()); diff --git a/Services/QTI/classes/class.ilQTIParser.php b/Services/QTI/classes/class.ilQTIParser.php index 06406f63ae74..e1368359155d 100644 --- a/Services/QTI/classes/class.ilQTIParser.php +++ b/Services/QTI/classes/class.ilQTIParser.php @@ -1260,9 +1260,12 @@ public function handlerParseEndTag($a_xml_parser, $a_name) } require_once 'Services/QTI/classes/class.ilQtiMatImageSecurity.php'; - $matImageSecurity = new ilQtiMatImageSecurity($this->matimage); - $matImageSecurity->sanitizeLabel(); - + try { + $matImageSecurity = new ilQtiMatImageSecurity($this->matimage); + $matImageSecurity->sanitizeLabel(); + } catch (Exception $e) { + break; + } if (!$matImageSecurity->validate()) { break; } diff --git a/Services/RTE/classes/class.ilRTEGlobalTemplate.php b/Services/RTE/classes/class.ilRTEGlobalTemplate.php index e02bce697496..6b1168253049 100755 --- a/Services/RTE/classes/class.ilRTEGlobalTemplate.php +++ b/Services/RTE/classes/class.ilRTEGlobalTemplate.php @@ -753,7 +753,7 @@ public function loadStandardTemplate() * * Will override the header_page_title. */ - public function setTitle($a_title) + public function setTitle($a_title, $hidden = false) { $this->title = $a_title; $this->header_page_title = $a_title; diff --git a/Services/SOAPAuth/classes/class.ilAuthProviderSoap.php b/Services/SOAPAuth/classes/class.ilAuthProviderSoap.php new file mode 100644 index 000000000000..10696cf3477c --- /dev/null +++ b/Services/SOAPAuth/classes/class.ilAuthProviderSoap.php @@ -0,0 +1,226 @@ + + */ +class ilAuthProviderSoap extends ilAuthProvider implements ilAuthProviderInterface +{ + /** @var string */ + protected $server_host = ''; + /** @var string */ + protected $server_port = ''; + /** @var string */ + protected $server_uri = ''; + /** @var string */ + protected $server_https = ''; + /** @var string */ + protected $server_nms = ''; + /** @var string */ + protected $use_dot_net = false; + /** @var string */ + protected $uri = ''; + /** @var nusoap_client */ + protected $client; + /** @var ilLogger */ + protected $logger; + /** @var ilSetting */ + protected $settings; + /** @var ilLanguage */ + protected $language; + /** @var ilRbacAdmin */ + protected $rbacAdmin; + + /** + * @inheritDoc + */ + public function __construct(ilAuthCredentials $credentials) + { + global $DIC; + + $this->settings = $DIC->settings(); + $this->logger = $DIC->logger()->auth(); + $this->language = $DIC->language(); + $this->rbacAdmin = $DIC->rbac()->admin(); + + parent::__construct($credentials); + } + + /** + * + */ + private function initClient() + { + $this->server_host = (string) $this->settings->get('soap_auth_server', ''); + $this->server_port = (string) $this->settings->get('soap_auth_port', ''); + $this->server_uri = (string) $this->settings->get('soap_auth_uri', ''); + $this->server_nms = (string) $this->settings->get('soap_auth_namespace', ''); + $this->server_https = (bool) $this->settings->get('soap_auth_use_https', false); + $this->use_dot_net = (bool) $this->settings->get('use_dotnet', false); + + $this->uri = $this->server_https ? 'https://' : 'http://'; + $this->uri .= $this->server_host; + + if ($this->server_port > 0) { + $this->uri .= (':' . $this->server_port); + } + if ($this->server_uri) { + $this->uri .= ('/' . $this->server_uri); + } + + require_once './webservice/soap/lib/nusoap.php'; + $this->client = new nusoap_client($this->uri); + } + + /** + * @inheritDoc + */ + public function doAuthentication(ilAuthStatus $status) + { + try { + $this->initClient(); + $this->handleSoapAuth($status); + } catch (Exception $e) { + $this->getLogger()->error($e->getMessage()); + $status->setTranslatedReason($e->getMessage()); + } + + if ($status->getAuthenticatedUserId() > 0) { + $this->logger->info('Successfully authenticated user via SOAP: ' . $this->getCredentials()->getUsername()); + $status->setStatus(ilAuthStatus::STATUS_AUTHENTICATED); + ilSession::set('used_external_auth', true); + + return true; + } + + $status->setStatus(ilAuthStatus::STATUS_AUTHENTICATION_FAILED); + + return false; + } + + /** + * @param ilAuthStatus $status + * @return bool + */ + private function handleSoapAuth(ilAuthStatus $status) : bool + { + $this->logger->debug(sprintf( + 'Login observer called for SOAP authentication request of ext_account "%s" and auth_mode "%s".', + $this->getCredentials()->getUsername(), + 'soap' + )); + $this->logger->debug(sprintf( + 'Trying to find ext_account "%s" for auth_mode "%s".', + $this->getCredentials()->getUsername(), + 'soap' + )); + + $internalLogin = ilObjUser::_checkExternalAuthAccount( + 'soap', + $this->getCredentials()->getUsername() + ); + + $isNewUser = false; + if ('' === $internalLogin || false === $internalLogin) { + $isNewUser = true; + } + + $soapAction = ''; + $nspref = ''; + if ($this->use_dot_net) { + $soapAction = $this->server_nms . '/isValidSession'; + $nspref = 'ns1:'; + } + + $valid = $this->client->call( + 'isValidSession', + [ + $nspref . 'ext_uid' => $this->getCredentials()->getUsername(), + $nspref . 'soap_pw' => $this->getCredentials()->getPassword(), + $nspref . 'new_user' => $isNewUser + ], + $this->server_nms, + $soapAction + ); + + if ($valid['valid'] !== true) { + $valid['valid'] = false; + } + + if (!$valid['valid']) { + $status->setReason('err_wrong_login'); + return false; + } + + if (!$isNewUser) { + $status->setAuthenticatedUserId(ilObjUser::_lookupId($internalLogin)); + return true; + } elseif (!$this->settings->get('soap_auth_create_users')) { + // Translate the reasons, otherwise the default failure is displayed + $status->setTranslatedReason($this->language->txt('err_valid_login_account_creation_disabled')); + return false; + } + + $userObj = new ilObjUser(); + $internalLogin = ilAuthUtils::_generateLogin($this->getCredentials()->getUsername()); + + $usrData = []; + $usrData['firstname'] = $valid['firstname']; + $usrData['lastname'] = $valid['lastname']; + $usrData['email'] = $valid['email']; + $usrData['login'] = $internalLogin; + $usrData['passwd'] = ''; + $usrData['passwd_type'] = IL_PASSWD_CRYPTED; + + $password = ''; + if ($this->settings->get('soap_auth_allow_local')) { + $passwords = ilUtil::generatePasswords(1); + $password = $passwords[0]; + $usrData['passwd'] = $password; + $usrData['passwd_type'] = IL_PASSWD_PLAIN; + } + + $usrData['auth_mode'] = 'soap'; + $usrData['ext_account'] = $this->getCredentials()->getUsername(); + $usrData['profile_incomplete'] = 1; + + $userObj->assignData($usrData); + $userObj->setTitle($userObj->getFullname()); + $userObj->setDescription($userObj->getEmail()); + $userObj->setLanguage($this->language->getDefaultLanguage()); + + $userObj->setTimeLimitOwner(USER_FOLDER_ID); + $userObj->setTimeLimitUnlimited(1); + $userObj->setTimeLimitFrom(time()); + $userObj->setTimeLimitUntil(time()); + $userObj->setOwner(0); + $userObj->create(); + $userObj->setActive(1); + $userObj->updateOwner(); + $userObj->saveAsNew(false); + $userObj->writePrefs(); + + $this->rbacAdmin->assignUser( + $this->settings->get('soap_auth_user_default_role', 4), + $userObj->getId() + ); + + if ($this->settings->get('soap_auth_account_mail', false)) { + $registrationSettings = new ilRegistrationSettings(); + $registrationSettings->setPasswordGenerationStatus(true); + + $accountMail = new ilAccountRegistrationMail( + $registrationSettings, + $this->language, + $this->logger + ); + $accountMail + ->withDirectRegistrationMode() + ->send($userObj, $password, false); + } + + $status->setAuthenticatedUserId($userObj->getId()); + return true; + } +} \ No newline at end of file diff --git a/Services/SOAPAuth/classes/class.ilSOAPAuth.php b/Services/SOAPAuth/classes/class.ilSOAPAuth.php index cf948bcb89c5..accf9b9a2ac4 100755 --- a/Services/SOAPAuth/classes/class.ilSOAPAuth.php +++ b/Services/SOAPAuth/classes/class.ilSOAPAuth.php @@ -22,7 +22,6 @@ */ -include_once("Auth/Auth.php"); include_once("./webservice/soap/lib/nusoap.php"); /** @@ -31,9 +30,9 @@ * SOAP Authentication class. * */ -class ilSOAPAuth extends Auth +class ilSOAPAuth { - public $valid = array(); + public $valid = array(); /** * Constructor @@ -60,14 +59,14 @@ public static function testConnection($a_ext_uid, $a_soap_pw, $a_new_user) $uri = "http://"; } - $uri .= $server_hostname; + $uri.= $server_hostname; if ($server_port > 0) { - $uri .= ":" . $server_port; + $uri.= ":" . $server_port; } if ($server_uri != "") { - $uri .= "/" . $server_uri; + $uri.= "/" . $server_uri; } $soap_client = new nusoap_client($uri); diff --git a/Services/SOAPAuth/dummy_server.php b/Services/SOAPAuth/dummy_server.php index 15d0ac3b36f3..dbd8137ca3f7 100755 --- a/Services/SOAPAuth/dummy_server.php +++ b/Services/SOAPAuth/dummy_server.php @@ -32,8 +32,13 @@ */ chdir('../..'); +global $HTTP_RAW_POST_DATA; + +ini_set("display_errors", 0); +error_reporting(E_ALL & ~E_NOTICE); + include_once './Services/SOAPAuth/classes/class.ilSoapDummyAuthServer.php'; -$server =& new ilSoapDummyAuthServer(); +$server = new ilSoapDummyAuthServer(); $server->start(); -?> \ No newline at end of file +?> diff --git a/Services/Search/classes/Lucene/class.ilLuceneSearchGUI.php b/Services/Search/classes/Lucene/class.ilLuceneSearchGUI.php index 4d0d9adbbe64..96a04f41a19f 100644 --- a/Services/Search/classes/Lucene/class.ilLuceneSearchGUI.php +++ b/Services/Search/classes/Lucene/class.ilLuceneSearchGUI.php @@ -581,6 +581,7 @@ protected function showSearchForm() // begin-patch creation_date $this->tpl->setVariable('TXT_FILTER_BY_CDATE', $this->lng->txt('search_filter_cd')); $this->tpl->setVariable('TXT_CD_OFF', $this->lng->txt('search_off')); + $this->tpl->setVariable('TXT_CD_ON', $this->lng->txt('search_on')); $this->tpl->setVariable('FORM_CD', $this->getCreationDateForm()->getHTML()); $this->tpl->setVariable("ARR_IMG_CD", ilGlyphGUI::get(ilGlyphGUI::CARET)); // end-patch creation_date diff --git a/Services/Search/classes/Provider/SearchMetaBarProvider.php b/Services/Search/classes/Provider/SearchMetaBarProvider.php index e39dd40bd407..b18313954e58 100644 --- a/Services/Search/classes/Provider/SearchMetaBarProvider.php +++ b/Services/Search/classes/Provider/SearchMetaBarProvider.php @@ -66,7 +66,7 @@ public function getMetaBarItems() : array return !$this->dic->user()->isAnonymous(); }) ->withSymbol($this->dic->ui()->factory()->symbol()->glyph()->search()) - ->withTitle("Search") + ->withTitle($this->dic->language()->txt("Search")) ->withPosition(1) ->withAvailableCallable( function () { diff --git a/Services/Search/classes/class.ilMainMenuSearchGUI.php b/Services/Search/classes/class.ilMainMenuSearchGUI.php index 20d6f90bcfcd..3b3a53c8fe9d 100644 --- a/Services/Search/classes/class.ilMainMenuSearchGUI.php +++ b/Services/Search/classes/class.ilMainMenuSearchGUI.php @@ -103,14 +103,14 @@ public function getHTML() $this->tpl->setVariable('FORMACTION', 'ilias.php?baseClass=ilSearchController&cmd=post' . '&rtoken=' . $ilCtrl->getRequestToken() . '&fallbackCmd=remoteSearch'); $this->tpl->setVariable('BTN_SEARCH', $this->lng->txt('search')); - + $this->tpl->setVariable('SEARCH_INPUT_LABEL', $this->lng->txt('search_field')); $this->tpl->setVariable('AC_DATASOURCE', "ilias.php?baseClass=ilSearchController&cmd=autoComplete"); $this->tpl->setVariable('IMG_MM_SEARCH', ilUtil::img( ilUtil::getImagePath("icon_seas.svg"), $lng->txt("search") )); - + if ($ilUser->getId() != ANONYMOUS_USER_ID) { $this->tpl->setVariable('HREF_SEARCH_LINK', "ilias.php?baseClass=ilSearchController"); $this->tpl->setVariable('TXT_SEARCH_LINK', $lng->txt("last_search_result")); diff --git a/Services/Search/classes/class.ilSearchGUI.php b/Services/Search/classes/class.ilSearchGUI.php index 17023cd75ef9..15d6aa7877d6 100644 --- a/Services/Search/classes/class.ilSearchGUI.php +++ b/Services/Search/classes/class.ilSearchGUI.php @@ -283,6 +283,7 @@ public function showSearch() // begin-patch creation_date $this->tpl->setVariable('TXT_FILTER_BY_CDATE', $this->lng->txt('search_filter_cd')); $this->tpl->setVariable('TXT_CD_OFF', $this->lng->txt('search_off')); + $this->tpl->setVariable('TXT_CD_ON', $this->lng->txt('search_on')); $this->tpl->setVariable('FORM_CD', $this->getCreationDateForm()->getHTML()); $this->tpl->setVariable("ARR_IMG_CD", ilGlyphGUI::get(ilGlyphGUI::CARET)); // end-patch creation_date diff --git a/Services/Search/js/Search.js b/Services/Search/js/Search.js index e833aa79907a..507830e55497 100644 --- a/Services/Search/js/Search.js +++ b/Services/Search/js/Search.js @@ -81,11 +81,14 @@ il.Search = { // cdates ctype = $('input[name=screation]').is(':checked'); + + $('#sop_cd_on').hide(); + $('#sop_cd_off').hide(); if(!ctype) { - $('#sop_cd').html(il.Search.search_filter_by_cd_off); + $('#sop_cd').html($('#sop_cd_off').html()); } else { - $('#sop_cd').html('On'); + $('#sop_cd').html($('#sop_cd_on').html()); } var area = $('a[name=area_anchor]').html(); diff --git a/Services/Search/templates/default/tpl.lucene_search.html b/Services/Search/templates/default/tpl.lucene_search.html index a3fc0648803a..f50868fb4373 100644 --- a/Services/Search/templates/default/tpl.lucene_search.html +++ b/Services/Search/templates/default/tpl.lucene_search.html @@ -12,7 +12,12 @@ {TXT_FILTER_BY_CDATE}: - {TXT_CD_OFF} {ARR_IMG_CD} + + {TXT_CD_OFF} + {TXT_CD_ON} + {ARR_IMG_CD} + +
diff --git a/Services/Search/templates/default/tpl.main_menu_search.html b/Services/Search/templates/default/tpl.main_menu_search.html index 01916530daa0..7dcda11d9e38 100644 --- a/Services/Search/templates/default/tpl.main_menu_search.html +++ b/Services/Search/templates/default/tpl.main_menu_search.html @@ -1,7 +1,7 @@
- - + +
diff --git a/Services/Search/templates/default/tpl.search.html b/Services/Search/templates/default/tpl.search.html index e6c4fc82f8ee..7d93b2224a28 100644 --- a/Services/Search/templates/default/tpl.search.html +++ b/Services/Search/templates/default/tpl.search.html @@ -12,7 +12,11 @@ {TXT_FILTER_BY_CDATE}: - {TXT_CD_OFF} {ARR_IMG_CD} + + {TXT_CD_OFF} + {TXT_CD_ON} + {ARR_IMG_CD} +
diff --git a/Services/Skill/classes/class.ilSkillProfile.php b/Services/Skill/classes/class.ilSkillProfile.php index 8fdf0519dc84..ca7177aaca7c 100644 --- a/Services/Skill/classes/class.ilSkillProfile.php +++ b/Services/Skill/classes/class.ilSkillProfile.php @@ -23,6 +23,11 @@ class ilSkillProfile implements ilSkillUsageInfo */ protected $lng; + /** + * @var ilRbacReview + */ + protected $review; + protected $id; protected $title; protected $description; @@ -39,6 +44,7 @@ public function __construct($a_id = 0) $this->db = $DIC->database(); $this->lng = $DIC->language(); + $this->review = $DIC->rbac()->review(); if ($a_id > 0) { $this->setId($a_id); $this->read(); @@ -255,12 +261,26 @@ public function update() public function delete() { $ilDB = $this->db; + + // TODO: Split the deletions when refactoring to repository pattern // profile levels $ilDB->manipulate( "DELETE FROM skl_profile_level WHERE " . " profile_id = " . $ilDB->quote($this->getId(), "integer") ); + + // profile users + $ilDB->manipulate( + "DELETE FROM skl_profile_user WHERE " . + " profile_id = " . $ilDB->quote($this->getId(), "integer") + ); + + // profile roles + $ilDB->manipulate( + "DELETE FROM skl_profile_role WHERE " . + " profile_id = " . $ilDB->quote($this->getId(), "integer") + ); // profile $ilDB->manipulate( @@ -493,6 +513,7 @@ public function getAssignedRoles() { $ilDB = $this->db; $lng = $this->lng; + $review = $this->review; $set = $ilDB->query( "SELECT * FROM skl_profile_role " . @@ -500,14 +521,23 @@ public function getAssignedRoles() ); $roles = array(); while ($rec = $ilDB->fetchAssoc($set)) { - $name = ilObjRole::_lookupTitle($rec["role_id"]); + $name = ilObjRole::_getTranslation(ilObjRole::_lookupTitle($rec["role_id"])); $type = $lng->txt("role"); + // get object of role + $obj = ilObject::_lookupObjectId($review->getObjectReferenceOfRole($rec["role_id"])); + // get title of object if course or group + if (ilObject::_lookupType($obj) == "crs" || ilObject::_lookupType($obj) == "grp") { + $obj_title = ilObject::_lookupTitle($obj); + } + $roles[$rec["role_id"]] = array( "type" => $type, "name" => $name, - "id" => $rec["role_id"] + "id" => $rec["role_id"], + "object" => $obj_title ); } + return $roles; } diff --git a/Services/Skill/classes/class.ilSkillProfileUserTableGUI.php b/Services/Skill/classes/class.ilSkillProfileUserTableGUI.php index 0bd3cffcc18d..68f429c5e8a4 100644 --- a/Services/Skill/classes/class.ilSkillProfileUserTableGUI.php +++ b/Services/Skill/classes/class.ilSkillProfileUserTableGUI.php @@ -47,6 +47,7 @@ public function __construct($a_parent_obj, $a_parent_cmd, $a_profile, $a_write_p $this->addColumn("", "", "1px", true); $this->addColumn($this->lng->txt("type"), "type"); $this->addColumn($this->lng->txt("name"), "name"); + $this->addColumn($this->lng->txt("object"), "object"); // $this->addColumn($this->lng->txt("actions")); $this->setFormAction($ilCtrl->getFormAction($a_parent_obj)); @@ -69,5 +70,6 @@ protected function fillRow($a_set) $this->tpl->setVariable("TYPE", $a_set["type"]); $this->tpl->setVariable("NAME", $a_set["name"]); $this->tpl->setVariable("ID", $a_set["id"]); + $this->tpl->setVariable("OBJECT", $a_set["object"]); } } diff --git a/Services/Skill/classes/class.ilSkillTemplateCategoryGUI.php b/Services/Skill/classes/class.ilSkillTemplateCategoryGUI.php index 5657a8e8800b..a21387793279 100644 --- a/Services/Skill/classes/class.ilSkillTemplateCategoryGUI.php +++ b/Services/Skill/classes/class.ilSkillTemplateCategoryGUI.php @@ -302,7 +302,7 @@ public function updateItem() } $this->node_object->setTitle($this->form->getInput("title")); - $this->node_object->setDescription($this->form->getDescription("description")); + $this->node_object->setDescription($this->form->getInput("description")); $this->node_object->setOrderNr($this->form->getInput("order_nr")); $this->node_object->setSelfEvaluation($_POST["self_eval"]); $this->node_object->update(); diff --git a/Services/Skill/templates/default/tpl.profile_user_row.html b/Services/Skill/templates/default/tpl.profile_user_row.html index 57c8770596a1..a30918b03b48 100644 --- a/Services/Skill/templates/default/tpl.profile_user_row.html +++ b/Services/Skill/templates/default/tpl.profile_user_row.html @@ -8,4 +8,7 @@
{NAME} + {OBJECT} +