From 33fd1358507b4a5abb3dcebe78d407d0567717c1 Mon Sep 17 00:00:00 2001 From: Marcus Eibrink-Lunzenauer Date: Tue, 18 Jun 2024 13:18:06 +0000 Subject: Deprecate `StudipAutoloader` and use composer's `autoload` Closes #4282 Merge request studip/studip!3099 --- app/controllers/questionnaire.php | 2 - composer.json | 62 +- composer.lock | 1108 ++++---- db/migrations/1.2_step_102_datenfeldtypen.php | 2 +- .../1.6_step_25_raumzeit_db_conversion.php | 4 +- .../ConditionalAdmission.class.php | 563 ---- .../conditionaladmission/ConditionalAdmission.php | 563 ++++ .../CourseMemberAdmission.class.php | 260 -- .../CourseMemberAdmission.php | 260 ++ .../limitedadmission/LimitedAdmission.class.php | 284 -- .../limitedadmission/LimitedAdmission.php | 284 ++ .../lockedadmission/LockedAdmission.class.php | 136 - .../lockedadmission/LockedAdmission.php | 136 + .../ParticipantRestrictedAdmission.class.php | 230 -- .../ParticipantRestrictedAdmission.php | 230 ++ .../passwordadmission/PasswordAdmission.class.php | 241 -- .../passwordadmission/PasswordAdmission.php | 241 ++ .../PreferentialAdmission.class.php | 540 ---- .../PreferentialAdmission.php | 540 ++++ .../termsadmission/TermsAdmission.class.php | 171 -- .../termsadmission/TermsAdmission.php | 171 ++ .../timedadmission/TimedAdmission.class.php | 243 -- .../timedadmission/TimedAdmission.php | 243 ++ lib/bootstrap-autoload.php | 81 - lib/bootstrap.php | 2 +- lib/calendar/CalendarColumn.class.php | 371 --- lib/calendar/CalendarColumn.php | 371 +++ lib/calendar/CalendarView.class.php | 344 --- lib/calendar/CalendarView.php | 344 +++ lib/calendar/CalendarWeekView.class.php | 124 - lib/calendar/CalendarWeekView.php | 124 + lib/classes/AdminCourseFilter.class.php | 220 -- lib/classes/AdminCourseFilter.php | 220 ++ lib/classes/Assets.class.php | 439 --- lib/classes/Assets.php | 439 +++ lib/classes/AuthorObject.class.php | 139 - lib/classes/AuthorObject.php | 139 + lib/classes/AutoInsert.class.php | 342 --- lib/classes/AutoInsert.php | 342 +++ lib/classes/Avatar.class.php | 635 ----- lib/classes/Avatar.php | 635 +++++ lib/classes/BreadCrumb.class.php | 103 - lib/classes/BreadCrumb.php | 103 + lib/classes/Button.class.php | 58 - lib/classes/Button.php | 58 + lib/classes/CSVArrayObject.class.php | 47 - lib/classes/CSVArrayObject.php | 47 + lib/classes/Color.class.php | 370 --- lib/classes/Color.php | 370 +++ lib/classes/Config.class.php | 469 ---- lib/classes/Config.php | 469 ++++ lib/classes/CourseAvatar.class.php | 44 - lib/classes/CourseAvatar.php | 44 + lib/classes/CourseConfig.class.php | 23 - lib/classes/CourseConfig.php | 23 + lib/classes/CronJob.class.php | 140 - lib/classes/CronJob.php | 140 + lib/classes/CronjobScheduler.class.php | 333 --- lib/classes/CronjobScheduler.php | 333 +++ lib/classes/DBManager.class.php | 250 -- lib/classes/DBManager.php | 250 ++ lib/classes/DataFieldBoolEntry.class.php | 35 - lib/classes/DataFieldBoolEntry.php | 35 + lib/classes/DataFieldComboEntry.class.php | 79 - lib/classes/DataFieldComboEntry.php | 79 + lib/classes/DataFieldDateEntry.class.php | 80 - lib/classes/DataFieldDateEntry.php | 80 + lib/classes/DataFieldEmailEntry.class.php | 25 - lib/classes/DataFieldEmailEntry.php | 25 + lib/classes/DataFieldEntry.class.php | 588 ---- lib/classes/DataFieldEntry.php | 588 ++++ lib/classes/DataFieldLinkEntry.class.php | 54 - lib/classes/DataFieldLinkEntry.php | 54 + lib/classes/DataFieldPhoneEntry.class.php | 122 - lib/classes/DataFieldPhoneEntry.php | 122 + lib/classes/DataFieldRadioEntry.class.php | 39 - lib/classes/DataFieldRadioEntry.php | 39 + lib/classes/DataFieldSelectboxEntry.class.php | 100 - lib/classes/DataFieldSelectboxEntry.php | 100 + .../DataFieldSelectboxMultipleEntry.class.php | 93 - lib/classes/DataFieldSelectboxMultipleEntry.php | 93 + lib/classes/DataFieldTextareaEntry.class.php | 29 - lib/classes/DataFieldTextareaEntry.php | 29 + lib/classes/DataFieldTextareai18nEntry.class.php | 21 - lib/classes/DataFieldTextareai18nEntry.php | 21 + lib/classes/DataFieldTextlineEntry.class.php | 14 - lib/classes/DataFieldTextlineEntry.php | 14 + lib/classes/DataFieldTextlinei18nEntry.class.php | 21 - lib/classes/DataFieldTextlinei18nEntry.php | 21 + lib/classes/DataFieldTextmarkupEntry.class.php | 35 - lib/classes/DataFieldTextmarkupEntry.php | 35 + lib/classes/DataFieldTextmarkupi18nEntry.class.php | 47 - lib/classes/DataFieldTextmarkupi18nEntry.php | 47 + lib/classes/DataFieldTimeEntry.class.php | 50 - lib/classes/DataFieldTimeEntry.php | 50 + lib/classes/DatabaseObject.class.php | 149 - lib/classes/DatabaseObject.php | 149 + lib/classes/DateFormatter.class.php | 176 -- lib/classes/DateFormatter.php | 176 ++ lib/classes/DbSnapshot.class.php | 370 --- lib/classes/DbSnapshot.php | 370 +++ lib/classes/DbView.class.php | 378 --- lib/classes/DbView.php | 378 +++ lib/classes/Event.interface.php | 184 -- lib/classes/Event.php | 184 ++ lib/classes/Feedback.class.php | 101 - lib/classes/Feedback.php | 101 + lib/classes/FeedbackRange.interface.php | 55 - lib/classes/FeedbackRange.php | 55 + lib/classes/FileLock.class.php | 74 - lib/classes/FileLock.php | 74 + lib/classes/Fullcalendar.class.php | 109 - lib/classes/Fullcalendar.php | 109 + lib/classes/Icon.class.php | 395 --- lib/classes/Icon.php | 395 +++ lib/classes/InstituteAvatar.class.php | 45 - lib/classes/InstituteAvatar.php | 45 + lib/classes/InstituteCalendarHelper.class.php | 810 ------ lib/classes/InstituteCalendarHelper.php | 810 ++++++ lib/classes/InstituteConfig.class.php | 15 - lib/classes/InstituteConfig.php | 15 + lib/classes/Interactable.class.php | 204 -- lib/classes/Interactable.php | 204 ++ lib/classes/JSONArrayObject.class.php | 40 - lib/classes/JSONArrayObject.php | 40 + lib/classes/LayoutMessage.interface.php | 18 - lib/classes/LayoutMessage.php | 18 + lib/classes/LinkButton.class.php | 58 - lib/classes/LinkButton.php | 58 + lib/classes/LockRules.class.php | 247 -- lib/classes/LockRules.php | 247 ++ lib/classes/Loggable.class.php | 53 - lib/classes/Loggable.php | 53 + lib/classes/MVV.class.php | 849 ------ lib/classes/MVV.php | 849 ++++++ lib/classes/Markup.class.php | 788 ------ lib/classes/Markup.php | 788 ++++++ lib/classes/MessageBox.class.php | 177 -- lib/classes/MessageBox.php | 177 ++ lib/classes/ModulesNotification.class.php | 182 -- lib/classes/ModulesNotification.php | 182 ++ lib/classes/MultiDimArrayObject.class.php | 129 - lib/classes/MultiDimArrayObject.php | 129 + lib/classes/MultiPersonSearch.class.php | 548 ---- lib/classes/MultiPersonSearch.php | 548 ++++ lib/classes/NotificationCenter.class.php | 174 -- lib/classes/NotificationCenter.php | 174 ++ lib/classes/PrivacyObject.interface.php | 11 - lib/classes/PrivacyObject.php | 11 + lib/classes/QuestionType.interface.php | 118 - lib/classes/QuestionType.php | 118 + lib/classes/QuickSearch.class.php | 503 ---- lib/classes/QuickSearch.php | 503 ++++ lib/classes/Range.interface.php | 66 - lib/classes/Range.php | 66 + lib/classes/RangeConfig.class.php | 223 -- lib/classes/RangeConfig.php | 223 ++ lib/classes/RangeTreeObject.class.php | 294 -- lib/classes/RangeTreeObject.php | 294 ++ lib/classes/RangeTreeObjectFak.class.php | 56 - lib/classes/RangeTreeObjectFak.php | 56 + lib/classes/RangeTreeObjectInst.class.php | 71 - lib/classes/RangeTreeObjectInst.php | 71 + lib/classes/Request.class.php | 828 ------ lib/classes/Request.php | 828 ++++++ lib/classes/ResetButton.class.php | 53 - lib/classes/ResetButton.php | 53 + lib/classes/Score.class.php | 225 -- lib/classes/Score.php | 225 ++ lib/classes/SemBrowse.class.php | 1264 --------- lib/classes/SemBrowse.php | 1264 +++++++++ lib/classes/SemClass.class.php | 644 ----- lib/classes/SemClass.php | 644 +++++ lib/classes/SemType.class.php | 257 -- lib/classes/SemType.php | 257 ++ lib/classes/Seminar.class.php | 2439 ---------------- lib/classes/Seminar.php | 2439 ++++++++++++++++ lib/classes/SeminarCategories.class.php | 138 - lib/classes/SeminarCategories.php | 138 + lib/classes/SessionDecoder.class.php | 246 -- lib/classes/SessionDecoder.php | 246 ++ lib/classes/SimpleCollection.class.php | 782 ------ lib/classes/SimpleCollection.php | 782 ++++++ lib/classes/SimpleORMap.class.php | 2483 ---------------- lib/classes/SimpleORMap.php | 2483 ++++++++++++++++ lib/classes/SimpleORMapCollection.class.php | 258 -- lib/classes/SimpleORMapCollection.php | 258 ++ lib/classes/StudipArrayObject.class.php | 471 ---- lib/classes/StudipArrayObject.php | 471 ++++ lib/classes/StudipForm.class.php | 590 ---- lib/classes/StudipForm.php | 590 ++++ lib/classes/StudipItem.interface.php | 72 - lib/classes/StudipItem.php | 72 + lib/classes/StudipKing.class.php | 173 -- lib/classes/StudipKing.php | 173 ++ lib/classes/StudipLink.class.php | 47 - lib/classes/StudipLink.php | 47 + lib/classes/StudipLock.class.php | 99 - lib/classes/StudipLock.php | 99 + lib/classes/StudipLog.class.php | 380 --- lib/classes/StudipLog.php | 380 +++ lib/classes/StudipLvgruppeSelection.class.php | 359 --- lib/classes/StudipLvgruppeSelection.php | 359 +++ lib/classes/StudipMail.class.php | 487 ---- lib/classes/StudipMail.php | 487 ++++ lib/classes/StudipObject.class.php | 199 -- lib/classes/StudipObject.php | 199 ++ lib/classes/StudipPDO.class.php | 385 --- lib/classes/StudipPDO.php | 385 +++ lib/classes/StudipRangeTree.class.php | 222 -- lib/classes/StudipRangeTree.php | 222 ++ lib/classes/StudipRangeTreeView.class.php | 101 - lib/classes/StudipRangeTreeView.php | 101 + lib/classes/StudipRangeTreeViewAdmin.class.php | 837 ------ lib/classes/StudipRangeTreeViewAdmin.php | 837 ++++++ lib/classes/StudipSemRangeTreeViewSimple.class.php | 247 -- lib/classes/StudipSemRangeTreeViewSimple.php | 247 ++ lib/classes/StudipSemSearch.class.php | 191 -- lib/classes/StudipSemSearch.php | 191 ++ lib/classes/StudipSemSearchHelper.class.php | 239 -- lib/classes/StudipSemSearchHelper.php | 239 ++ lib/classes/StudipSemTree.class.php | 312 -- lib/classes/StudipSemTree.php | 312 ++ lib/classes/StudipSemTreeSearch.class.php | 241 -- lib/classes/StudipSemTreeSearch.php | 241 ++ lib/classes/StudipSemTreeView.class.php | 215 -- lib/classes/StudipSemTreeView.php | 215 ++ lib/classes/StudipSemTreeViewAdmin.class.php | 825 ------ lib/classes/StudipSemTreeViewAdmin.php | 825 ++++++ lib/classes/StudipSemTreeViewSimple.class.php | 244 -- lib/classes/StudipSemTreeViewSimple.php | 244 ++ lib/classes/StudipStudyAreaSelection.class.php | 332 --- lib/classes/StudipStudyAreaSelection.php | 332 +++ lib/classes/StudygroupAvatar.class.php | 16 - lib/classes/StudygroupAvatar.php | 16 + lib/classes/TreeAbstract.class.php | 431 --- lib/classes/TreeAbstract.php | 431 +++ lib/classes/TreeView.class.php | 446 --- lib/classes/TreeView.php | 446 +++ lib/classes/UpdateInformation.class.php | 118 - lib/classes/UpdateInformation.php | 118 + lib/classes/UserConfig.class.php | 40 - lib/classes/UserConfig.php | 40 + lib/classes/UserLookup.class.php | 503 ---- lib/classes/UserLookup.php | 503 ++++ lib/classes/UserManagement.class.php | 1353 --------- lib/classes/UserManagement.php | 1353 +++++++++ lib/classes/admission/AdmissionAlgorithm.class.php | 35 - lib/classes/admission/AdmissionAlgorithm.php | 35 + lib/classes/admission/AdmissionPriority.class.php | 253 -- lib/classes/admission/AdmissionPriority.php | 253 ++ lib/classes/admission/AdmissionRule.class.php | 456 --- lib/classes/admission/AdmissionRule.php | 456 +++ lib/classes/admission/AdmissionUserList.class.php | 405 --- lib/classes/admission/AdmissionUserList.php | 405 +++ lib/classes/admission/CourseSet.class.php | 1185 -------- lib/classes/admission/CourseSet.php | 1185 ++++++++ lib/classes/admission/RandomAlgorithm.class.php | 437 --- lib/classes/admission/RandomAlgorithm.php | 437 +++ lib/classes/admission/UserFilter.class.php | 279 -- lib/classes/admission/UserFilter.php | 279 ++ lib/classes/admission/UserFilterField.class.php | 473 ---- lib/classes/admission/UserFilterField.php | 473 ++++ .../userfilter/DatafieldCondition.class.php | 164 -- .../admission/userfilter/DatafieldCondition.php | 164 ++ .../admission/userfilter/DegreeCondition.class.php | 51 - .../admission/userfilter/DegreeCondition.php | 51 + .../userfilter/PermissionCondition.class.php | 46 - .../admission/userfilter/PermissionCondition.php | 46 + .../userfilter/SemesterOfStudyCondition.class.php | 81 - .../userfilter/SemesterOfStudyCondition.php | 81 + .../userfilter/StgteilVersionCondition.class.php | 83 - .../userfilter/StgteilVersionCondition.php | 83 + .../userfilter/SubjectCondition.class.php | 52 - .../admission/userfilter/SubjectCondition.php | 52 + .../userfilter/SubjectConditionAny.class.php | 50 - .../admission/userfilter/SubjectConditionAny.php | 50 + .../auth_plugins/StudipAuthAbstract.class.php | 578 ---- lib/classes/auth_plugins/StudipAuthAbstract.php | 578 ++++ lib/classes/auth_plugins/StudipAuthCAS.class.php | 89 - lib/classes/auth_plugins/StudipAuthCAS.php | 89 + lib/classes/auth_plugins/StudipAuthIP.class.php | 20 - lib/classes/auth_plugins/StudipAuthIP.php | 20 + lib/classes/auth_plugins/StudipAuthLTI.class.php | 135 - lib/classes/auth_plugins/StudipAuthLTI.php | 135 + lib/classes/auth_plugins/StudipAuthLdap.class.php | 216 -- lib/classes/auth_plugins/StudipAuthLdap.php | 216 ++ .../StudipAuthLdapReadAndBind.class.php | 81 - .../auth_plugins/StudipAuthLdapReadAndBind.php | 81 + lib/classes/auth_plugins/StudipAuthOIDC.class.php | 112 - lib/classes/auth_plugins/StudipAuthOIDC.php | 112 + lib/classes/auth_plugins/StudipAuthSSO.class.php | 51 - lib/classes/auth_plugins/StudipAuthSSO.php | 51 + lib/classes/auth_plugins/StudipAuthShib.class.php | 139 - lib/classes/auth_plugins/StudipAuthShib.php | 139 + .../auth_plugins/StudipAuthStandard.class.php | 89 - lib/classes/auth_plugins/StudipAuthStandard.php | 89 + lib/classes/cache/Cache.class.php | 213 -- lib/classes/cache/Cache.php | 213 ++ lib/classes/cache/DbCache.class.php | 143 - lib/classes/cache/DbCache.php | 143 + lib/classes/cache/Factory.class.php | 206 -- lib/classes/cache/Factory.php | 206 ++ lib/classes/cache/FileCache.class.php | 278 -- lib/classes/cache/FileCache.php | 278 ++ lib/classes/cache/Item.class.php | 164 -- lib/classes/cache/Item.php | 164 ++ lib/classes/cache/MemcachedCache.class.php | 151 - lib/classes/cache/MemcachedCache.php | 151 + lib/classes/cache/MemoryCache.class.php | 98 - lib/classes/cache/MemoryCache.php | 98 + lib/classes/cache/Proxy.class.php | 123 - lib/classes/cache/Proxy.php | 123 + lib/classes/cache/RedisCache.class.php | 198 -- lib/classes/cache/RedisCache.php | 198 ++ lib/classes/cache/Wrapper.class.php | 95 - lib/classes/cache/Wrapper.php | 95 + lib/classes/calendar/EventData.class.php | 114 - lib/classes/calendar/EventData.php | 114 + lib/classes/calendar/EventSource.interface.php | 64 - lib/classes/calendar/EventSource.php | 64 + lib/classes/calendar/ICalendarExport.class.php | 628 ----- lib/classes/calendar/ICalendarExport.php | 628 +++++ lib/classes/calendar/ICalendarImport.class.php | 678 ----- lib/classes/calendar/ICalendarImport.php | 678 +++++ lib/classes/calendar/Owner.interface.php | 40 - lib/classes/calendar/Owner.php | 40 + .../exportdocument/ExportDocument.interface.php | 59 - lib/classes/exportdocument/ExportDocument.php | 59 + lib/classes/exportdocument/ExportPDF.class.php | 369 --- lib/classes/exportdocument/ExportPDF.php | 369 +++ .../librarysearch/LibraryDocument.class.php | 442 --- lib/classes/librarysearch/LibraryDocument.php | 442 +++ .../LibraryResultParser.interface.php | 40 - lib/classes/librarysearch/LibraryResultParser.php | 40 + lib/classes/librarysearch/LibrarySearch.class.php | 150 - lib/classes/librarysearch/LibrarySearch.php | 150 + .../librarysearch/LibrarySearchManager.class.php | 174 -- lib/classes/librarysearch/LibrarySearchManager.php | 174 ++ .../BASELibraryResultParser.class.php | 158 -- .../resultparsers/BASELibraryResultParser.php | 158 ++ .../K10PlusLibraryResultParser.class.php | 84 - .../resultparsers/K10PlusLibraryResultParser.php | 84 + .../MarcxmlLibraryResultParser.class.php | 234 -- .../resultparsers/MarcxmlLibraryResultParser.php | 234 ++ .../resultparsers/SRULibraryResultParser.class.php | 96 - .../resultparsers/SRULibraryResultParser.php | 96 + .../searchmodules/BASELibrarySearch.class.php | 114 - .../searchmodules/BASELibrarySearch.php | 114 + .../K10PlusZentralLibrarySearch.class.php | 149 - .../searchmodules/K10PlusZentralLibrarySearch.php | 149 + .../searchmodules/SRULibrarySearch.class.php | 153 - .../searchmodules/SRULibrarySearch.php | 153 + lib/classes/searchtypes/MyCoursesSearch.class.php | 220 -- lib/classes/searchtypes/MyCoursesSearch.php | 220 ++ lib/classes/searchtypes/PermissionSearch.class.php | 219 -- lib/classes/searchtypes/PermissionSearch.php | 219 ++ lib/classes/searchtypes/RangeSearch.class.php | 87 - lib/classes/searchtypes/RangeSearch.php | 87 + lib/classes/searchtypes/ResourceSearch.class.php | 362 --- lib/classes/searchtypes/ResourceSearch.php | 362 +++ lib/classes/searchtypes/RoomSearch.class.php | 192 -- lib/classes/searchtypes/RoomSearch.php | 192 ++ lib/classes/searchtypes/SQLSearch.class.php | 179 -- lib/classes/searchtypes/SQLSearch.php | 179 ++ lib/classes/searchtypes/SearchType.class.php | 107 - lib/classes/searchtypes/SearchType.php | 107 + lib/classes/searchtypes/SeminarSearch.class.php | 101 - lib/classes/searchtypes/SeminarSearch.php | 101 + lib/classes/searchtypes/StandardSearch.class.php | 206 -- lib/classes/searchtypes/StandardSearch.php | 206 ++ lib/classes/searchtypes/TreeSearch.class.php | 96 - lib/classes/searchtypes/TreeSearch.php | 96 + lib/classes/sidebar/ClipboardWidget.class.php | 143 - lib/classes/sidebar/ClipboardWidget.php | 143 + .../sidebar/InstituteSelectWidget.class.php | 111 - lib/classes/sidebar/InstituteSelectWidget.php | 111 + lib/classes/sidebar/ResourceTreeWidget.class.php | 145 - lib/classes/sidebar/ResourceTreeWidget.php | 145 + lib/classes/sidebar/RoomClipboardWidget.class.php | 57 - lib/classes/sidebar/RoomClipboardWidget.php | 57 + lib/classes/sidebar/RoomSearchTreeWidget.class.php | 41 - lib/classes/sidebar/RoomSearchTreeWidget.php | 41 + lib/classes/sidebar/RoomSearchWidget.class.php | 595 ---- lib/classes/sidebar/RoomSearchWidget.php | 595 ++++ lib/cronjobs/check_admission.class.php | 147 - lib/cronjobs/check_admission.php | 147 + lib/cronjobs/cleanup_log.class.php | 114 - lib/cronjobs/cleanup_log.php | 114 + lib/cronjobs/garbage_collector.class.php | 202 -- lib/cronjobs/garbage_collector.php | 202 ++ lib/cronjobs/purge_cache.class.php | 92 - lib/cronjobs/purge_cache.php | 92 + lib/cronjobs/remind_oer_upload.class.php | 73 - lib/cronjobs/remind_oer_upload.php | 73 + lib/cronjobs/send_mail_notifications.class.php | 141 - lib/cronjobs/send_mail_notifications.php | 141 + lib/cronjobs/send_mail_queue.class.php | 70 - lib/cronjobs/send_mail_queue.php | 70 + lib/cronjobs/session_gc.class.php | 29 - lib/cronjobs/session_gc.php | 29 + lib/elearning/ConnectedCMS.class.php | 466 --- lib/elearning/ConnectedCMS.php | 466 +++ lib/elearning/ConnectedLink.class.php | 129 - lib/elearning/ConnectedLink.php | 129 + lib/elearning/ConnectedPermissions.class.php | 57 - lib/elearning/ConnectedPermissions.php | 57 + lib/elearning/ConnectedUser.class.php | 512 ---- lib/elearning/ConnectedUser.php | 512 ++++ lib/elearning/ContentModule.class.php | 388 --- lib/elearning/ContentModule.php | 388 +++ lib/elearning/ContentModuleView.class.php | 189 -- lib/elearning/ContentModuleView.php | 189 ++ lib/elearning/ELearningUtils.class.php | 612 ---- lib/elearning/ELearningUtils.php | 612 ++++ lib/elearning/Ilias3ConnectedCMS.class.php | 350 --- lib/elearning/Ilias3ConnectedCMS.php | 350 +++ lib/elearning/Ilias3ConnectedLink.class.php | 188 -- lib/elearning/Ilias3ConnectedLink.php | 188 ++ lib/elearning/Ilias3ConnectedPermissions.class.php | 256 -- lib/elearning/Ilias3ConnectedPermissions.php | 256 ++ lib/elearning/Ilias3ConnectedUser.class.php | 345 --- lib/elearning/Ilias3ConnectedUser.php | 345 +++ lib/elearning/Ilias3ContentModule.class.php | 292 -- lib/elearning/Ilias3ContentModule.php | 292 ++ lib/elearning/Ilias3ObjectXMLParser.class.php | 236 -- lib/elearning/Ilias3ObjectXMLParser.php | 236 ++ lib/elearning/Ilias3SaxParser.class.php | 230 -- lib/elearning/Ilias3SaxParser.php | 230 ++ lib/elearning/Ilias3Soap.class.php | 1069 ------- lib/elearning/Ilias3Soap.php | 1069 +++++++ lib/elearning/Ilias4ConnectedCMS.class.php | 272 -- lib/elearning/Ilias4ConnectedCMS.php | 272 ++ lib/elearning/Ilias4ConnectedLink.class.php | 124 - lib/elearning/Ilias4ConnectedLink.php | 124 + lib/elearning/Ilias4ConnectedPermissions.class.php | 126 - lib/elearning/Ilias4ConnectedPermissions.php | 126 + lib/elearning/Ilias4ConnectedUser.class.php | 168 -- lib/elearning/Ilias4ConnectedUser.php | 168 ++ lib/elearning/Ilias4ContentModule.class.php | 106 - lib/elearning/Ilias4ContentModule.php | 106 + lib/elearning/Ilias4Soap.class.php | 181 -- lib/elearning/Ilias4Soap.php | 181 ++ lib/elearning/Ilias5ConnectedCMS.class.php | 21 - lib/elearning/Ilias5ConnectedCMS.php | 21 + lib/elearning/Ilias5ConnectedLink.class.php | 16 - lib/elearning/Ilias5ConnectedLink.php | 16 + lib/elearning/Ilias5ConnectedPermissions.class.php | 17 - lib/elearning/Ilias5ConnectedPermissions.php | 17 + lib/elearning/Ilias5ConnectedUser.class.php | 40 - lib/elearning/Ilias5ConnectedUser.php | 40 + lib/elearning/Ilias5ContentModule.class.php | 16 - lib/elearning/Ilias5ContentModule.php | 16 + lib/elearning/Ilias5Soap.class.php | 114 - lib/elearning/Ilias5Soap.php | 114 + lib/elearning/LonCapaConnectedCMS.class.php | 67 - lib/elearning/LonCapaConnectedCMS.php | 67 + lib/elearning/LonCapaConnectedLink.class.php | 67 - lib/elearning/LonCapaConnectedLink.php | 67 + lib/elearning/LonCapaContentModule.class.php | 86 - lib/elearning/LonCapaContentModule.php | 86 + lib/elearning/LonCapaRequest.class.php | 110 - lib/elearning/LonCapaRequest.php | 110 + lib/elearning/ObjectConnections.class.php | 254 -- lib/elearning/ObjectConnections.php | 254 ++ lib/elearning/PmWikiConnectedCMS.class.php | 86 - lib/elearning/PmWikiConnectedCMS.php | 86 + lib/elearning/PmWikiConnectedLink.class.php | 134 - lib/elearning/PmWikiConnectedLink.php | 134 + lib/elearning/PmWikiContentModule.class.php | 115 - lib/elearning/PmWikiContentModule.php | 115 + lib/exceptions/ClipboardException.class.php | 25 - lib/exceptions/ClipboardException.php | 25 + .../GlobalResourceLockException.class.php | 25 - .../resources/GlobalResourceLockException.php | 25 + .../GlobalResourceLockOverlapException.class.php | 25 - .../GlobalResourceLockOverlapException.php | 25 + .../InvalidResourceCategoryException.class.php | 25 - .../resources/InvalidResourceCategoryException.php | 25 + .../InvalidResourceClassException.class.php | 24 - .../resources/InvalidResourceClassException.php | 24 + .../resources/InvalidResourceException.class.php | 25 - .../resources/InvalidResourceException.php | 25 + .../InvalidResourceRequestException.class.php | 22 - .../resources/InvalidResourceRequestException.php | 22 + .../resources/NoResourceClassException.class.php | 11 - .../resources/NoResourceClassException.php | 11 + .../resources/ResourceBookingException.class.php | 24 - .../resources/ResourceBookingException.php | 24 + .../ResourceBookingOverlapException.class.php | 24 - .../resources/ResourceBookingOverlapException.php | 24 + .../ResourceBookingRangeException.class.php | 24 - .../resources/ResourceBookingRangeException.php | 24 + .../resources/ResourceException.class.php | 23 - lib/exceptions/resources/ResourceException.php | 23 + .../ResourceNoTimeRangeException.class.php | 24 - .../resources/ResourceNoTimeRangeException.php | 24 + .../ResourcePermissionException.class.php | 24 - .../resources/ResourcePermissionException.php | 24 + .../ResourcePropertyDefinitionException.class.php | 25 - .../ResourcePropertyDefinitionException.php | 25 + .../resources/ResourcePropertyException.class.php | 25 - .../resources/ResourcePropertyException.php | 25 + .../ResourcePropertyStateException.class.php | 23 - .../resources/ResourcePropertyStateException.php | 23 + .../resources/ResourceRequestException.class.php | 24 - .../resources/ResourceRequestException.php | 24 + .../ResourceUnavailableException.class.php | 24 - .../resources/ResourceUnavailableException.php | 24 + .../ResourceUnlockableException.class.php | 24 - .../resources/ResourceUnlockableException.php | 24 + .../resources/SeparableRoomException.class.php | 24 - .../resources/SeparableRoomException.php | 24 + lib/filesystem/FileArchiveManager.class.php | 998 ------- lib/filesystem/FileArchiveManager.php | 998 +++++++ .../FileArchiveManagerException.class.php | 17 - lib/filesystem/FileArchiveManagerException.php | 17 + lib/filesystem/LibraryFile.class.php | 401 --- lib/filesystem/LibraryFile.php | 401 +++ lib/filesystem/ResourceFolder.class.php | 149 - lib/filesystem/ResourceFolder.php | 149 + lib/ilias_interface/ConnectedIlias.class.php | 1370 --------- lib/ilias_interface/ConnectedIlias.php | 1370 +++++++++ lib/ilias_interface/IliasModule.class.php | 371 --- lib/ilias_interface/IliasModule.php | 371 +++ .../IliasObjectConnections.class.php | 311 -- lib/ilias_interface/IliasObjectConnections.php | 311 ++ lib/ilias_interface/IliasSoap.class.php | 1709 ----------- lib/ilias_interface/IliasSoap.php | 1709 +++++++++++ lib/ilias_interface/IliasUser.class.php | 528 ---- lib/ilias_interface/IliasUser.php | 528 ++++ lib/models/AdmissionApplication.class.php | 296 -- lib/models/AdmissionApplication.php | 296 ++ lib/models/ArchivedCourse.class.php | 105 - lib/models/ArchivedCourse.php | 105 + lib/models/ArchivedCourseMember.class.php | 68 - lib/models/ArchivedCourseMember.php | 68 + lib/models/AuthUserMd5.class.php | 42 - lib/models/AuthUserMd5.php | 42 + lib/models/Banner.class.php | 232 -- lib/models/Banner.php | 232 ++ lib/models/BannerRoles.class.php | 122 - lib/models/BannerRoles.php | 122 + lib/models/Clipboard.class.php | 335 --- lib/models/Clipboard.php | 335 +++ lib/models/ClipboardItem.class.php | 69 - lib/models/ClipboardItem.php | 69 + lib/models/ColourValue.class.php | 104 - lib/models/ColourValue.php | 104 + lib/models/ConfigEntry.class.php | 51 - lib/models/ConfigEntry.php | 51 + lib/models/Contact.class.php | 51 - lib/models/Contact.php | 51 + lib/models/ContactGroup.class.php | 44 - lib/models/ContactGroup.php | 44 + lib/models/ContactGroupItem.class.php | 61 - lib/models/ContactGroupItem.php | 61 + lib/models/ContentTermsOfUse.class.php | 225 -- lib/models/ContentTermsOfUse.php | 225 ++ lib/models/Course.class.php | 1181 -------- lib/models/Course.php | 1181 ++++++++ lib/models/CourseDate.class.php | 665 ----- lib/models/CourseDate.php | 665 +++++ lib/models/CourseExDate.class.php | 422 --- lib/models/CourseExDate.php | 422 +++ lib/models/CourseMember.class.php | 465 --- lib/models/CourseMember.php | 465 +++ lib/models/CourseTopic.class.php | 261 -- lib/models/CourseTopic.php | 261 ++ lib/models/CronjobLog.class.php | 70 - lib/models/CronjobLog.php | 70 + lib/models/CronjobSchedule.class.php | 272 -- lib/models/CronjobSchedule.php | 272 ++ lib/models/CronjobTask.class.php | 239 -- lib/models/CronjobTask.php | 239 ++ lib/models/DataField.class.php | 289 -- lib/models/DataField.php | 289 ++ lib/models/DatafieldEntryModel.class.php | 261 -- lib/models/DatafieldEntryModel.php | 261 ++ lib/models/DatafieldEntryModelI18N.class.php | 36 - lib/models/DatafieldEntryModelI18N.php | 36 + lib/models/Degree.class.php | 84 - lib/models/Degree.php | 84 + lib/models/Freetext.php | 2 +- lib/models/HelpContent.class.php | 189 -- lib/models/HelpContent.php | 189 ++ lib/models/HelpTour.class.php | 407 --- lib/models/HelpTour.php | 407 +++ lib/models/HelpTourAudience.class.php | 48 - lib/models/HelpTourAudience.php | 48 + lib/models/HelpTourSettings.class.php | 48 - lib/models/HelpTourSettings.php | 48 + lib/models/HelpTourStep.class.php | 107 - lib/models/HelpTourStep.php | 107 + lib/models/HelpTourUser.class.php | 69 - lib/models/HelpTourUser.php | 69 + lib/models/Institute.class.php | 368 --- lib/models/Institute.php | 368 +++ lib/models/InstituteMember.class.php | 221 -- lib/models/InstituteMember.php | 221 ++ lib/models/InstitutePlanColumn.class.php | 72 - lib/models/InstitutePlanColumn.php | 72 + lib/models/Kategorie.class.php | 76 - lib/models/Kategorie.php | 76 + lib/models/LikertScale.php | 2 - lib/models/LockRule.class.php | 135 - lib/models/LockRule.php | 135 + lib/models/LoginBackground.class.php | 127 - lib/models/LoginBackground.php | 127 + lib/models/LoginFaq.class.php | 33 - lib/models/LoginFaq.php | 33 + lib/models/MailQueueEntry.class.php | 141 - lib/models/MailQueueEntry.php | 141 + lib/models/Message.class.php | 343 --- lib/models/Message.php | 343 +++ lib/models/MessageUser.class.php | 98 - lib/models/MessageUser.php | 98 + lib/models/MvvOverlappingConflict.class.php | 115 - lib/models/MvvOverlappingConflict.php | 115 + lib/models/MvvOverlappingSelection.class.php | 339 --- lib/models/MvvOverlappingSelection.php | 339 +++ lib/models/NewsRange.class.php | 71 - lib/models/NewsRange.php | 71 + lib/models/NewsRoles.class.php | 142 - lib/models/NewsRoles.php | 142 + lib/models/OpenGraphURL.class.php | 326 --- lib/models/OpenGraphURL.php | 326 +++ lib/models/OpenGraphURLCollection.class.php | 57 - lib/models/OpenGraphURLCollection.php | 57 + lib/models/PersonalNotifications.class.php | 470 ---- lib/models/PersonalNotifications.php | 470 ++++ lib/models/RangeScale.php | 2 - lib/models/Semester.class.php | 515 ---- lib/models/Semester.php | 515 ++++ lib/models/SemesterCourse.class.php | 45 - lib/models/SemesterCourse.php | 45 + lib/models/SemesterHoliday.class.php | 146 - lib/models/SemesterHoliday.php | 146 + lib/models/SeminarCycleDate.class.php | 819 ------ lib/models/SeminarCycleDate.php | 819 ++++++ lib/models/StudipComment.class.php | 115 - lib/models/StudipComment.php | 115 + lib/models/StudipNews.class.php | 696 ----- lib/models/StudipNews.php | 696 +++++ lib/models/StudipScmEntry.class.php | 55 - lib/models/StudipScmEntry.php | 55 + lib/models/StudipStudyArea.class.php | 637 ----- lib/models/StudipStudyArea.php | 637 +++++ lib/models/StudyCourse.class.php | 77 - lib/models/StudyCourse.php | 77 + lib/models/User.class.php | 1650 ----------- lib/models/User.php | 1650 +++++++++++ lib/models/UserInfo.class.php | 60 - lib/models/UserInfo.php | 60 + lib/models/UserOnline.class.php | 27 - lib/models/UserOnline.php | 27 + lib/models/UserStudyCourse.class.php | 94 - lib/models/UserStudyCourse.php | 94 + lib/models/Vote.php | 2 - lib/models/WebserviceAccessRule.class.php | 133 - lib/models/WebserviceAccessRule.php | 133 + lib/models/WikiPage.class.php | 311 -- lib/models/WikiPage.php | 311 ++ lib/models/calendar/CalendarCourseDate.class.php | 80 - lib/models/calendar/CalendarCourseDate.php | 80 + lib/models/calendar/CalendarCourseExDate.class.php | 35 - lib/models/calendar/CalendarCourseExDate.php | 35 + lib/models/calendar/CalendarDate.class.php | 946 ------- lib/models/calendar/CalendarDate.php | 946 +++++++ .../calendar/CalendarDateAssignment.class.php | 722 ----- lib/models/calendar/CalendarDateAssignment.php | 722 +++++ .../calendar/CalendarDateException.class.php | 40 - lib/models/calendar/CalendarDateException.php | 40 + lib/models/resources/BrokenResource.class.php | 274 -- lib/models/resources/BrokenResource.php | 274 ++ lib/models/resources/Building.class.php | 534 ---- lib/models/resources/Building.php | 534 ++++ lib/models/resources/GlobalResourceLock.class.php | 88 - lib/models/resources/GlobalResourceLock.php | 88 + lib/models/resources/Location.class.php | 455 --- lib/models/resources/Location.php | 455 +++ lib/models/resources/Resource.class.php | 2965 -------------------- lib/models/resources/Resource.php | 2965 ++++++++++++++++++++ lib/models/resources/ResourceBooking.class.php | 1946 ------------- lib/models/resources/ResourceBooking.php | 1946 +++++++++++++ .../resources/ResourceBookingInterval.class.php | 122 - lib/models/resources/ResourceBookingInterval.php | 122 + lib/models/resources/ResourceCategory.class.php | 806 ------ lib/models/resources/ResourceCategory.php | 806 ++++++ .../resources/ResourceCategoryProperty.class.php | 74 - lib/models/resources/ResourceCategoryProperty.php | 74 + lib/models/resources/ResourceLabel.class.php | 267 -- lib/models/resources/ResourceLabel.php | 267 ++ lib/models/resources/ResourcePermission.class.php | 188 -- lib/models/resources/ResourcePermission.php | 188 ++ lib/models/resources/ResourceProperty.class.php | 127 - lib/models/resources/ResourceProperty.php | 127 + .../resources/ResourcePropertyDefinition.class.php | 409 --- .../resources/ResourcePropertyDefinition.php | 409 +++ .../resources/ResourcePropertyGroup.class.php | 39 - lib/models/resources/ResourcePropertyGroup.php | 39 + lib/models/resources/ResourceRequest.class.php | 2478 ---------------- lib/models/resources/ResourceRequest.php | 2478 ++++++++++++++++ .../resources/ResourceRequestAppointment.class.php | 49 - .../resources/ResourceRequestAppointment.php | 49 + .../resources/ResourceRequestProperty.class.php | 80 - lib/models/resources/ResourceRequestProperty.php | 80 + .../ResourceTemporaryPermission.class.php | 254 -- .../resources/ResourceTemporaryPermission.php | 254 ++ lib/models/resources/Room.class.php | 723 ----- lib/models/resources/Room.php | 723 +++++ lib/models/resources/RoomRequest.class.php | 98 - lib/models/resources/RoomRequest.php | 98 + lib/models/resources/SeparableRoom.class.php | 190 -- lib/models/resources/SeparableRoom.php | 190 ++ lib/models/resources/SeparableRoomPart.class.php | 54 - lib/models/resources/SeparableRoomPart.php | 54 + lib/modules/Blubber.class.php | 136 - lib/modules/Blubber.php | 136 + lib/modules/ConsultationModule.class.php | 202 -- lib/modules/ConsultationModule.php | 202 ++ lib/modules/CoreAdmin.class.php | 142 - lib/modules/CoreAdmin.php | 142 + lib/modules/CoreCalendar.class.php | 65 - lib/modules/CoreCalendar.php | 65 + lib/modules/CoreDocuments.class.php | 205 -- lib/modules/CoreDocuments.php | 205 ++ lib/modules/CoreElearningInterface.class.php | 143 - lib/modules/CoreElearningInterface.php | 143 + lib/modules/CoreForum.class.php | 210 -- lib/modules/CoreForum.php | 210 ++ lib/modules/CoreOverview.class.php | 125 - lib/modules/CoreOverview.php | 125 + lib/modules/CoreParticipants.class.php | 224 -- lib/modules/CoreParticipants.php | 224 ++ lib/modules/CorePersonal.class.php | 61 - lib/modules/CorePersonal.php | 61 + lib/modules/CoreSchedule.class.php | 130 - lib/modules/CoreSchedule.php | 130 + lib/modules/CoreScm.class.php | 163 -- lib/modules/CoreScm.php | 163 ++ lib/modules/CoreStudygroupAdmin.class.php | 70 - lib/modules/CoreStudygroupAdmin.php | 70 + lib/modules/CoreStudygroupParticipants.class.php | 61 - lib/modules/CoreStudygroupParticipants.php | 61 + lib/modules/CoreWiki.class.php | 205 -- lib/modules/CoreWiki.php | 205 ++ lib/modules/CoursewareModule.class.php | 166 -- lib/modules/CoursewareModule.php | 166 ++ lib/modules/FeedbackModule.class.php | 71 - lib/modules/FeedbackModule.php | 71 + lib/modules/GradebookModule.class.php | 181 -- lib/modules/GradebookModule.php | 181 ++ lib/modules/IliasInterfaceModule.class.php | 164 -- lib/modules/IliasInterfaceModule.php | 164 ++ lib/modules/LtiToolModule.class.php | 124 - lib/modules/LtiToolModule.php | 124 + lib/modules/StudipModule.class.php | 72 - lib/modules/StudipModule.php | 72 + lib/phplib/CT_Cache.class.php | 68 - lib/phplib/CT_Cache.php | 68 + lib/phplib/CT_Sql.class.php | 101 - lib/phplib/CT_Sql.php | 101 + lib/phplib/DB_Sql.class.php | 260 -- lib/phplib/DB_Sql.php | 260 ++ lib/phplib/Seminar_Auth.class.php | 450 --- lib/phplib/Seminar_Auth.php | 450 +++ lib/phplib/Seminar_Default_Auth.class.php | 19 - lib/phplib/Seminar_Default_Auth.php | 19 + lib/phplib/Seminar_Perm.class.php | 350 --- lib/phplib/Seminar_Perm.php | 350 +++ lib/phplib/Seminar_Register_Auth.class.php | 242 -- lib/phplib/Seminar_Register_Auth.php | 242 ++ lib/phplib/Seminar_Session.class.php | 436 --- lib/phplib/Seminar_Session.php | 436 +++ lib/phplib/Seminar_User.class.php | 116 - lib/phplib/Seminar_User.php | 116 + lib/phplib/email_validation.class.php | 271 -- lib/phplib/email_validation.php | 271 ++ lib/plugins/core/AdminCourseAction.class.php | 22 - lib/plugins/core/AdminCourseAction.php | 22 + lib/plugins/core/AdminCourseContents.class.php | 25 - lib/plugins/core/AdminCourseContents.php | 25 + lib/plugins/core/AdminCourseWidgetPlugin.class.php | 42 - lib/plugins/core/AdminCourseWidgetPlugin.php | 42 + lib/plugins/core/AdministrationPlugin.class.php | 19 - lib/plugins/core/AdministrationPlugin.php | 19 + lib/plugins/core/DetailspagePlugin.class.php | 27 - lib/plugins/core/DetailspagePlugin.php | 27 + lib/plugins/core/FileUploadHook.class.php | 12 - lib/plugins/core/FileUploadHook.php | 12 + lib/plugins/core/FilesystemPlugin.class.php | 83 - lib/plugins/core/FilesystemPlugin.php | 83 + lib/plugins/core/ForumModule.class.php | 140 - lib/plugins/core/ForumModule.php | 140 + lib/plugins/core/HomepagePlugin.class.php | 33 - lib/plugins/core/HomepagePlugin.php | 33 + lib/plugins/core/LibraryPlugin.class.php | 55 - lib/plugins/core/LibraryPlugin.php | 55 + lib/plugins/core/MetricsPlugin.class.php | 23 - lib/plugins/core/MetricsPlugin.php | 23 + lib/plugins/core/PortalPlugin.class.php | 33 - lib/plugins/core/PortalPlugin.php | 33 + lib/plugins/core/PrivacyPlugin.class.php | 24 - lib/plugins/core/PrivacyPlugin.php | 24 + .../core/QuestionnaireAssignmentPlugin.class.php | 57 - lib/plugins/core/QuestionnaireAssignmentPlugin.php | 57 + lib/plugins/core/RESTAPIPlugin.class.php | 26 - lib/plugins/core/RESTAPIPlugin.php | 26 + lib/plugins/core/Role.class.php | 89 - lib/plugins/core/Role.php | 89 + lib/plugins/core/ScorePlugin.class.php | 29 - lib/plugins/core/ScorePlugin.php | 29 + lib/plugins/core/StandardPlugin.class.php | 17 - lib/plugins/core/StandardPlugin.php | 17 + lib/plugins/core/StudIPPlugin.class.php | 257 -- lib/plugins/core/StudIPPlugin.php | 257 ++ lib/plugins/core/SystemPlugin.class.php | 17 - lib/plugins/core/SystemPlugin.php | 17 + lib/plugins/core/WebServicePlugin.class.php | 14 - lib/plugins/core/WebServicePlugin.php | 14 + lib/plugins/db/RolePersistence.class.php | 721 ----- lib/plugins/db/RolePersistence.php | 721 +++++ lib/plugins/engine/PluginEngine.class.php | 154 - lib/plugins/engine/PluginEngine.php | 154 + lib/plugins/engine/PluginManager.class.php | 719 ----- lib/plugins/engine/PluginManager.php | 719 +++++ lib/plugins/engine/PluginRepository.class.php | 160 -- lib/plugins/engine/PluginRepository.php | 160 ++ lib/raumzeit/CycleData.class.php | 434 --- lib/raumzeit/CycleData.php | 434 +++ lib/raumzeit/CycleDataDB.class.php | 271 -- lib/raumzeit/CycleDataDB.php | 271 ++ lib/raumzeit/Issue.class.php | 268 -- lib/raumzeit/Issue.php | 268 ++ lib/raumzeit/IssueDB.class.php | 153 - lib/raumzeit/IssueDB.php | 153 + lib/raumzeit/MetaDate.class.php | 697 ----- lib/raumzeit/MetaDate.php | 697 +++++ lib/raumzeit/MetaDateDB.class.php | 55 - lib/raumzeit/MetaDateDB.php | 55 + lib/raumzeit/SeminarDB.class.php | 264 -- lib/raumzeit/SeminarDB.php | 264 ++ lib/raumzeit/SingleDate.class.php | 978 ------- lib/raumzeit/SingleDate.php | 978 +++++++ lib/raumzeit/SingleDateDB.class.php | 299 -- lib/raumzeit/SingleDateDB.php | 299 ++ lib/resources/ResourceManager.class.php | 1472 ---------- lib/resources/ResourceManager.php | 1472 ++++++++++ lib/resources/RoomManager.class.php | 994 ------- lib/resources/RoomManager.php | 994 +++++++ lib/soap/StudipSoapClient.class.php | 58 - lib/soap/StudipSoapClient.php | 58 + lib/soap/StudipSoapClient_PHP5.class.php | 63 - lib/soap/StudipSoapClient_PHP5.php | 63 + public/install.php | 14 +- tests/_support/Helper/StudipDb.php | 2 +- tests/functional/_bootstrap.php | 22 - tests/jsonapi/_bootstrap.php | 49 +- tests/unit/_bootstrap.php | 56 +- tests/unit/lib/CalendarcolumnClassTest.php | 2 +- tests/unit/lib/CalendarviewClassTest.php | 2 +- tests/unit/lib/VisualTest.php | 4 +- tests/unit/lib/classes/AvatarClassTest.php | 2 +- tests/unit/lib/classes/MarkupClassTest.php | 10 +- tests/unit/lib/classes/PluginRepositoryTest.php | 2 +- .../library/session/OAuthSessionAbstract.class.php | 44 - .../library/session/OAuthSessionAbstract.php | 44 + .../OAuthSignatureMethod.class.php | 69 - .../signature_method/OAuthSignatureMethod.php | 69 + .../library/store/OAuthStoreAbstract.class.php | 151 - .../oauth-php/library/store/OAuthStoreAbstract.php | 151 + 872 files changed, 109002 insertions(+), 108992 deletions(-) delete mode 100644 lib/admissionrules/conditionaladmission/ConditionalAdmission.class.php create mode 100644 lib/admissionrules/conditionaladmission/ConditionalAdmission.php delete mode 100644 lib/admissionrules/coursememberadmission/CourseMemberAdmission.class.php create mode 100644 lib/admissionrules/coursememberadmission/CourseMemberAdmission.php delete mode 100644 lib/admissionrules/limitedadmission/LimitedAdmission.class.php create mode 100644 lib/admissionrules/limitedadmission/LimitedAdmission.php delete mode 100644 lib/admissionrules/lockedadmission/LockedAdmission.class.php create mode 100644 lib/admissionrules/lockedadmission/LockedAdmission.php delete mode 100644 lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.class.php create mode 100644 lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.php delete mode 100644 lib/admissionrules/passwordadmission/PasswordAdmission.class.php create mode 100644 lib/admissionrules/passwordadmission/PasswordAdmission.php delete mode 100644 lib/admissionrules/preferentialadmission/PreferentialAdmission.class.php create mode 100644 lib/admissionrules/preferentialadmission/PreferentialAdmission.php delete mode 100644 lib/admissionrules/termsadmission/TermsAdmission.class.php create mode 100644 lib/admissionrules/termsadmission/TermsAdmission.php delete mode 100644 lib/admissionrules/timedadmission/TimedAdmission.class.php create mode 100644 lib/admissionrules/timedadmission/TimedAdmission.php delete mode 100644 lib/calendar/CalendarColumn.class.php create mode 100644 lib/calendar/CalendarColumn.php delete mode 100644 lib/calendar/CalendarView.class.php create mode 100644 lib/calendar/CalendarView.php delete mode 100644 lib/calendar/CalendarWeekView.class.php create mode 100644 lib/calendar/CalendarWeekView.php delete mode 100644 lib/classes/AdminCourseFilter.class.php create mode 100644 lib/classes/AdminCourseFilter.php delete mode 100644 lib/classes/Assets.class.php create mode 100644 lib/classes/Assets.php delete mode 100644 lib/classes/AuthorObject.class.php create mode 100644 lib/classes/AuthorObject.php delete mode 100644 lib/classes/AutoInsert.class.php create mode 100644 lib/classes/AutoInsert.php delete mode 100644 lib/classes/Avatar.class.php create mode 100644 lib/classes/Avatar.php delete mode 100644 lib/classes/BreadCrumb.class.php create mode 100644 lib/classes/BreadCrumb.php delete mode 100644 lib/classes/Button.class.php create mode 100644 lib/classes/Button.php delete mode 100644 lib/classes/CSVArrayObject.class.php create mode 100644 lib/classes/CSVArrayObject.php delete mode 100644 lib/classes/Color.class.php create mode 100644 lib/classes/Color.php delete mode 100644 lib/classes/Config.class.php create mode 100644 lib/classes/Config.php delete mode 100644 lib/classes/CourseAvatar.class.php create mode 100644 lib/classes/CourseAvatar.php delete mode 100644 lib/classes/CourseConfig.class.php create mode 100644 lib/classes/CourseConfig.php delete mode 100644 lib/classes/CronJob.class.php create mode 100644 lib/classes/CronJob.php delete mode 100644 lib/classes/CronjobScheduler.class.php create mode 100644 lib/classes/CronjobScheduler.php delete mode 100644 lib/classes/DBManager.class.php create mode 100644 lib/classes/DBManager.php delete mode 100644 lib/classes/DataFieldBoolEntry.class.php create mode 100644 lib/classes/DataFieldBoolEntry.php delete mode 100644 lib/classes/DataFieldComboEntry.class.php create mode 100644 lib/classes/DataFieldComboEntry.php delete mode 100644 lib/classes/DataFieldDateEntry.class.php create mode 100644 lib/classes/DataFieldDateEntry.php delete mode 100644 lib/classes/DataFieldEmailEntry.class.php create mode 100644 lib/classes/DataFieldEmailEntry.php delete mode 100644 lib/classes/DataFieldEntry.class.php create mode 100644 lib/classes/DataFieldEntry.php delete mode 100644 lib/classes/DataFieldLinkEntry.class.php create mode 100644 lib/classes/DataFieldLinkEntry.php delete mode 100644 lib/classes/DataFieldPhoneEntry.class.php create mode 100644 lib/classes/DataFieldPhoneEntry.php delete mode 100644 lib/classes/DataFieldRadioEntry.class.php create mode 100644 lib/classes/DataFieldRadioEntry.php delete mode 100644 lib/classes/DataFieldSelectboxEntry.class.php create mode 100644 lib/classes/DataFieldSelectboxEntry.php delete mode 100644 lib/classes/DataFieldSelectboxMultipleEntry.class.php create mode 100644 lib/classes/DataFieldSelectboxMultipleEntry.php delete mode 100644 lib/classes/DataFieldTextareaEntry.class.php create mode 100644 lib/classes/DataFieldTextareaEntry.php delete mode 100644 lib/classes/DataFieldTextareai18nEntry.class.php create mode 100644 lib/classes/DataFieldTextareai18nEntry.php delete mode 100644 lib/classes/DataFieldTextlineEntry.class.php create mode 100644 lib/classes/DataFieldTextlineEntry.php delete mode 100644 lib/classes/DataFieldTextlinei18nEntry.class.php create mode 100644 lib/classes/DataFieldTextlinei18nEntry.php delete mode 100644 lib/classes/DataFieldTextmarkupEntry.class.php create mode 100644 lib/classes/DataFieldTextmarkupEntry.php delete mode 100644 lib/classes/DataFieldTextmarkupi18nEntry.class.php create mode 100644 lib/classes/DataFieldTextmarkupi18nEntry.php delete mode 100644 lib/classes/DataFieldTimeEntry.class.php create mode 100644 lib/classes/DataFieldTimeEntry.php delete mode 100644 lib/classes/DatabaseObject.class.php create mode 100644 lib/classes/DatabaseObject.php delete mode 100644 lib/classes/DateFormatter.class.php create mode 100644 lib/classes/DateFormatter.php delete mode 100644 lib/classes/DbSnapshot.class.php create mode 100644 lib/classes/DbSnapshot.php delete mode 100644 lib/classes/DbView.class.php create mode 100644 lib/classes/DbView.php delete mode 100644 lib/classes/Event.interface.php create mode 100644 lib/classes/Event.php delete mode 100644 lib/classes/Feedback.class.php create mode 100644 lib/classes/Feedback.php delete mode 100644 lib/classes/FeedbackRange.interface.php create mode 100644 lib/classes/FeedbackRange.php delete mode 100644 lib/classes/FileLock.class.php create mode 100644 lib/classes/FileLock.php delete mode 100644 lib/classes/Fullcalendar.class.php create mode 100644 lib/classes/Fullcalendar.php delete mode 100644 lib/classes/Icon.class.php create mode 100644 lib/classes/Icon.php delete mode 100644 lib/classes/InstituteAvatar.class.php create mode 100644 lib/classes/InstituteAvatar.php delete mode 100644 lib/classes/InstituteCalendarHelper.class.php create mode 100644 lib/classes/InstituteCalendarHelper.php delete mode 100644 lib/classes/InstituteConfig.class.php create mode 100644 lib/classes/InstituteConfig.php delete mode 100644 lib/classes/Interactable.class.php create mode 100644 lib/classes/Interactable.php delete mode 100644 lib/classes/JSONArrayObject.class.php create mode 100644 lib/classes/JSONArrayObject.php delete mode 100644 lib/classes/LayoutMessage.interface.php create mode 100644 lib/classes/LayoutMessage.php delete mode 100644 lib/classes/LinkButton.class.php create mode 100644 lib/classes/LinkButton.php delete mode 100644 lib/classes/LockRules.class.php create mode 100644 lib/classes/LockRules.php delete mode 100644 lib/classes/Loggable.class.php create mode 100644 lib/classes/Loggable.php delete mode 100644 lib/classes/MVV.class.php create mode 100644 lib/classes/MVV.php delete mode 100644 lib/classes/Markup.class.php create mode 100644 lib/classes/Markup.php delete mode 100644 lib/classes/MessageBox.class.php create mode 100644 lib/classes/MessageBox.php delete mode 100644 lib/classes/ModulesNotification.class.php create mode 100644 lib/classes/ModulesNotification.php delete mode 100644 lib/classes/MultiDimArrayObject.class.php create mode 100644 lib/classes/MultiDimArrayObject.php delete mode 100644 lib/classes/MultiPersonSearch.class.php create mode 100644 lib/classes/MultiPersonSearch.php delete mode 100644 lib/classes/NotificationCenter.class.php create mode 100644 lib/classes/NotificationCenter.php delete mode 100644 lib/classes/PrivacyObject.interface.php create mode 100644 lib/classes/PrivacyObject.php delete mode 100644 lib/classes/QuestionType.interface.php create mode 100644 lib/classes/QuestionType.php delete mode 100644 lib/classes/QuickSearch.class.php create mode 100644 lib/classes/QuickSearch.php delete mode 100644 lib/classes/Range.interface.php create mode 100644 lib/classes/Range.php delete mode 100644 lib/classes/RangeConfig.class.php create mode 100644 lib/classes/RangeConfig.php delete mode 100644 lib/classes/RangeTreeObject.class.php create mode 100644 lib/classes/RangeTreeObject.php delete mode 100644 lib/classes/RangeTreeObjectFak.class.php create mode 100644 lib/classes/RangeTreeObjectFak.php delete mode 100644 lib/classes/RangeTreeObjectInst.class.php create mode 100644 lib/classes/RangeTreeObjectInst.php delete mode 100644 lib/classes/Request.class.php create mode 100644 lib/classes/Request.php delete mode 100644 lib/classes/ResetButton.class.php create mode 100644 lib/classes/ResetButton.php delete mode 100644 lib/classes/Score.class.php create mode 100644 lib/classes/Score.php delete mode 100644 lib/classes/SemBrowse.class.php create mode 100644 lib/classes/SemBrowse.php delete mode 100644 lib/classes/SemClass.class.php create mode 100644 lib/classes/SemClass.php delete mode 100644 lib/classes/SemType.class.php create mode 100644 lib/classes/SemType.php delete mode 100644 lib/classes/Seminar.class.php create mode 100644 lib/classes/Seminar.php delete mode 100644 lib/classes/SeminarCategories.class.php create mode 100644 lib/classes/SeminarCategories.php delete mode 100644 lib/classes/SessionDecoder.class.php create mode 100644 lib/classes/SessionDecoder.php delete mode 100644 lib/classes/SimpleCollection.class.php create mode 100644 lib/classes/SimpleCollection.php delete mode 100644 lib/classes/SimpleORMap.class.php create mode 100644 lib/classes/SimpleORMap.php delete mode 100644 lib/classes/SimpleORMapCollection.class.php create mode 100644 lib/classes/SimpleORMapCollection.php delete mode 100644 lib/classes/StudipArrayObject.class.php create mode 100644 lib/classes/StudipArrayObject.php delete mode 100644 lib/classes/StudipForm.class.php create mode 100644 lib/classes/StudipForm.php delete mode 100644 lib/classes/StudipItem.interface.php create mode 100644 lib/classes/StudipItem.php delete mode 100644 lib/classes/StudipKing.class.php create mode 100644 lib/classes/StudipKing.php delete mode 100644 lib/classes/StudipLink.class.php create mode 100644 lib/classes/StudipLink.php delete mode 100644 lib/classes/StudipLock.class.php create mode 100644 lib/classes/StudipLock.php delete mode 100644 lib/classes/StudipLog.class.php create mode 100644 lib/classes/StudipLog.php delete mode 100644 lib/classes/StudipLvgruppeSelection.class.php create mode 100644 lib/classes/StudipLvgruppeSelection.php delete mode 100644 lib/classes/StudipMail.class.php create mode 100644 lib/classes/StudipMail.php delete mode 100644 lib/classes/StudipObject.class.php create mode 100644 lib/classes/StudipObject.php delete mode 100644 lib/classes/StudipPDO.class.php create mode 100644 lib/classes/StudipPDO.php delete mode 100644 lib/classes/StudipRangeTree.class.php create mode 100644 lib/classes/StudipRangeTree.php delete mode 100644 lib/classes/StudipRangeTreeView.class.php create mode 100644 lib/classes/StudipRangeTreeView.php delete mode 100644 lib/classes/StudipRangeTreeViewAdmin.class.php create mode 100644 lib/classes/StudipRangeTreeViewAdmin.php delete mode 100644 lib/classes/StudipSemRangeTreeViewSimple.class.php create mode 100644 lib/classes/StudipSemRangeTreeViewSimple.php delete mode 100644 lib/classes/StudipSemSearch.class.php create mode 100644 lib/classes/StudipSemSearch.php delete mode 100644 lib/classes/StudipSemSearchHelper.class.php create mode 100644 lib/classes/StudipSemSearchHelper.php delete mode 100644 lib/classes/StudipSemTree.class.php create mode 100644 lib/classes/StudipSemTree.php delete mode 100644 lib/classes/StudipSemTreeSearch.class.php create mode 100644 lib/classes/StudipSemTreeSearch.php delete mode 100644 lib/classes/StudipSemTreeView.class.php create mode 100644 lib/classes/StudipSemTreeView.php delete mode 100644 lib/classes/StudipSemTreeViewAdmin.class.php create mode 100644 lib/classes/StudipSemTreeViewAdmin.php delete mode 100644 lib/classes/StudipSemTreeViewSimple.class.php create mode 100644 lib/classes/StudipSemTreeViewSimple.php delete mode 100644 lib/classes/StudipStudyAreaSelection.class.php create mode 100644 lib/classes/StudipStudyAreaSelection.php delete mode 100644 lib/classes/StudygroupAvatar.class.php create mode 100644 lib/classes/StudygroupAvatar.php delete mode 100644 lib/classes/TreeAbstract.class.php create mode 100644 lib/classes/TreeAbstract.php delete mode 100644 lib/classes/TreeView.class.php create mode 100644 lib/classes/TreeView.php delete mode 100644 lib/classes/UpdateInformation.class.php create mode 100644 lib/classes/UpdateInformation.php delete mode 100644 lib/classes/UserConfig.class.php create mode 100644 lib/classes/UserConfig.php delete mode 100644 lib/classes/UserLookup.class.php create mode 100644 lib/classes/UserLookup.php delete mode 100644 lib/classes/UserManagement.class.php create mode 100644 lib/classes/UserManagement.php delete mode 100644 lib/classes/admission/AdmissionAlgorithm.class.php create mode 100644 lib/classes/admission/AdmissionAlgorithm.php delete mode 100644 lib/classes/admission/AdmissionPriority.class.php create mode 100644 lib/classes/admission/AdmissionPriority.php delete mode 100644 lib/classes/admission/AdmissionRule.class.php create mode 100644 lib/classes/admission/AdmissionRule.php delete mode 100644 lib/classes/admission/AdmissionUserList.class.php create mode 100644 lib/classes/admission/AdmissionUserList.php delete mode 100644 lib/classes/admission/CourseSet.class.php create mode 100644 lib/classes/admission/CourseSet.php delete mode 100644 lib/classes/admission/RandomAlgorithm.class.php create mode 100644 lib/classes/admission/RandomAlgorithm.php delete mode 100644 lib/classes/admission/UserFilter.class.php create mode 100644 lib/classes/admission/UserFilter.php delete mode 100644 lib/classes/admission/UserFilterField.class.php create mode 100644 lib/classes/admission/UserFilterField.php delete mode 100644 lib/classes/admission/userfilter/DatafieldCondition.class.php create mode 100644 lib/classes/admission/userfilter/DatafieldCondition.php delete mode 100644 lib/classes/admission/userfilter/DegreeCondition.class.php create mode 100644 lib/classes/admission/userfilter/DegreeCondition.php delete mode 100644 lib/classes/admission/userfilter/PermissionCondition.class.php create mode 100644 lib/classes/admission/userfilter/PermissionCondition.php delete mode 100644 lib/classes/admission/userfilter/SemesterOfStudyCondition.class.php create mode 100644 lib/classes/admission/userfilter/SemesterOfStudyCondition.php delete mode 100644 lib/classes/admission/userfilter/StgteilVersionCondition.class.php create mode 100644 lib/classes/admission/userfilter/StgteilVersionCondition.php delete mode 100644 lib/classes/admission/userfilter/SubjectCondition.class.php create mode 100644 lib/classes/admission/userfilter/SubjectCondition.php delete mode 100644 lib/classes/admission/userfilter/SubjectConditionAny.class.php create mode 100644 lib/classes/admission/userfilter/SubjectConditionAny.php delete mode 100644 lib/classes/auth_plugins/StudipAuthAbstract.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthAbstract.php delete mode 100644 lib/classes/auth_plugins/StudipAuthCAS.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthCAS.php delete mode 100644 lib/classes/auth_plugins/StudipAuthIP.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthIP.php delete mode 100644 lib/classes/auth_plugins/StudipAuthLTI.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthLTI.php delete mode 100644 lib/classes/auth_plugins/StudipAuthLdap.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthLdap.php delete mode 100644 lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthLdapReadAndBind.php delete mode 100644 lib/classes/auth_plugins/StudipAuthOIDC.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthOIDC.php delete mode 100644 lib/classes/auth_plugins/StudipAuthSSO.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthSSO.php delete mode 100644 lib/classes/auth_plugins/StudipAuthShib.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthShib.php delete mode 100644 lib/classes/auth_plugins/StudipAuthStandard.class.php create mode 100644 lib/classes/auth_plugins/StudipAuthStandard.php delete mode 100644 lib/classes/cache/Cache.class.php create mode 100644 lib/classes/cache/Cache.php delete mode 100644 lib/classes/cache/DbCache.class.php create mode 100644 lib/classes/cache/DbCache.php delete mode 100644 lib/classes/cache/Factory.class.php create mode 100644 lib/classes/cache/Factory.php delete mode 100644 lib/classes/cache/FileCache.class.php create mode 100644 lib/classes/cache/FileCache.php delete mode 100644 lib/classes/cache/Item.class.php create mode 100644 lib/classes/cache/Item.php delete mode 100644 lib/classes/cache/MemcachedCache.class.php create mode 100644 lib/classes/cache/MemcachedCache.php delete mode 100644 lib/classes/cache/MemoryCache.class.php create mode 100644 lib/classes/cache/MemoryCache.php delete mode 100644 lib/classes/cache/Proxy.class.php create mode 100644 lib/classes/cache/Proxy.php delete mode 100644 lib/classes/cache/RedisCache.class.php create mode 100644 lib/classes/cache/RedisCache.php delete mode 100644 lib/classes/cache/Wrapper.class.php create mode 100644 lib/classes/cache/Wrapper.php delete mode 100644 lib/classes/calendar/EventData.class.php create mode 100644 lib/classes/calendar/EventData.php delete mode 100644 lib/classes/calendar/EventSource.interface.php create mode 100644 lib/classes/calendar/EventSource.php delete mode 100644 lib/classes/calendar/ICalendarExport.class.php create mode 100644 lib/classes/calendar/ICalendarExport.php delete mode 100644 lib/classes/calendar/ICalendarImport.class.php create mode 100644 lib/classes/calendar/ICalendarImport.php delete mode 100644 lib/classes/calendar/Owner.interface.php create mode 100644 lib/classes/calendar/Owner.php delete mode 100644 lib/classes/exportdocument/ExportDocument.interface.php create mode 100644 lib/classes/exportdocument/ExportDocument.php delete mode 100644 lib/classes/exportdocument/ExportPDF.class.php create mode 100644 lib/classes/exportdocument/ExportPDF.php delete mode 100644 lib/classes/librarysearch/LibraryDocument.class.php create mode 100644 lib/classes/librarysearch/LibraryDocument.php delete mode 100644 lib/classes/librarysearch/LibraryResultParser.interface.php create mode 100644 lib/classes/librarysearch/LibraryResultParser.php delete mode 100644 lib/classes/librarysearch/LibrarySearch.class.php create mode 100644 lib/classes/librarysearch/LibrarySearch.php delete mode 100644 lib/classes/librarysearch/LibrarySearchManager.class.php create mode 100644 lib/classes/librarysearch/LibrarySearchManager.php delete mode 100644 lib/classes/librarysearch/resultparsers/BASELibraryResultParser.class.php create mode 100644 lib/classes/librarysearch/resultparsers/BASELibraryResultParser.php delete mode 100644 lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.class.php create mode 100644 lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.php delete mode 100644 lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.class.php create mode 100644 lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.php delete mode 100644 lib/classes/librarysearch/resultparsers/SRULibraryResultParser.class.php create mode 100644 lib/classes/librarysearch/resultparsers/SRULibraryResultParser.php delete mode 100644 lib/classes/librarysearch/searchmodules/BASELibrarySearch.class.php create mode 100644 lib/classes/librarysearch/searchmodules/BASELibrarySearch.php delete mode 100644 lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.class.php create mode 100644 lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.php delete mode 100644 lib/classes/librarysearch/searchmodules/SRULibrarySearch.class.php create mode 100644 lib/classes/librarysearch/searchmodules/SRULibrarySearch.php delete mode 100644 lib/classes/searchtypes/MyCoursesSearch.class.php create mode 100644 lib/classes/searchtypes/MyCoursesSearch.php delete mode 100644 lib/classes/searchtypes/PermissionSearch.class.php create mode 100644 lib/classes/searchtypes/PermissionSearch.php delete mode 100644 lib/classes/searchtypes/RangeSearch.class.php create mode 100644 lib/classes/searchtypes/RangeSearch.php delete mode 100644 lib/classes/searchtypes/ResourceSearch.class.php create mode 100644 lib/classes/searchtypes/ResourceSearch.php delete mode 100644 lib/classes/searchtypes/RoomSearch.class.php create mode 100644 lib/classes/searchtypes/RoomSearch.php delete mode 100644 lib/classes/searchtypes/SQLSearch.class.php create mode 100644 lib/classes/searchtypes/SQLSearch.php delete mode 100644 lib/classes/searchtypes/SearchType.class.php create mode 100644 lib/classes/searchtypes/SearchType.php delete mode 100644 lib/classes/searchtypes/SeminarSearch.class.php create mode 100644 lib/classes/searchtypes/SeminarSearch.php delete mode 100644 lib/classes/searchtypes/StandardSearch.class.php create mode 100644 lib/classes/searchtypes/StandardSearch.php delete mode 100644 lib/classes/searchtypes/TreeSearch.class.php create mode 100644 lib/classes/searchtypes/TreeSearch.php delete mode 100644 lib/classes/sidebar/ClipboardWidget.class.php create mode 100644 lib/classes/sidebar/ClipboardWidget.php delete mode 100644 lib/classes/sidebar/InstituteSelectWidget.class.php create mode 100644 lib/classes/sidebar/InstituteSelectWidget.php delete mode 100644 lib/classes/sidebar/ResourceTreeWidget.class.php create mode 100644 lib/classes/sidebar/ResourceTreeWidget.php delete mode 100644 lib/classes/sidebar/RoomClipboardWidget.class.php create mode 100644 lib/classes/sidebar/RoomClipboardWidget.php delete mode 100644 lib/classes/sidebar/RoomSearchTreeWidget.class.php create mode 100644 lib/classes/sidebar/RoomSearchTreeWidget.php delete mode 100644 lib/classes/sidebar/RoomSearchWidget.class.php create mode 100644 lib/classes/sidebar/RoomSearchWidget.php delete mode 100644 lib/cronjobs/check_admission.class.php create mode 100644 lib/cronjobs/check_admission.php delete mode 100644 lib/cronjobs/cleanup_log.class.php create mode 100644 lib/cronjobs/cleanup_log.php delete mode 100644 lib/cronjobs/garbage_collector.class.php create mode 100644 lib/cronjobs/garbage_collector.php delete mode 100644 lib/cronjobs/purge_cache.class.php create mode 100644 lib/cronjobs/purge_cache.php delete mode 100644 lib/cronjobs/remind_oer_upload.class.php create mode 100644 lib/cronjobs/remind_oer_upload.php delete mode 100644 lib/cronjobs/send_mail_notifications.class.php create mode 100644 lib/cronjobs/send_mail_notifications.php delete mode 100644 lib/cronjobs/send_mail_queue.class.php create mode 100644 lib/cronjobs/send_mail_queue.php delete mode 100644 lib/cronjobs/session_gc.class.php create mode 100644 lib/cronjobs/session_gc.php delete mode 100644 lib/elearning/ConnectedCMS.class.php create mode 100644 lib/elearning/ConnectedCMS.php delete mode 100644 lib/elearning/ConnectedLink.class.php create mode 100644 lib/elearning/ConnectedLink.php delete mode 100644 lib/elearning/ConnectedPermissions.class.php create mode 100644 lib/elearning/ConnectedPermissions.php delete mode 100644 lib/elearning/ConnectedUser.class.php create mode 100644 lib/elearning/ConnectedUser.php delete mode 100644 lib/elearning/ContentModule.class.php create mode 100644 lib/elearning/ContentModule.php delete mode 100644 lib/elearning/ContentModuleView.class.php create mode 100644 lib/elearning/ContentModuleView.php delete mode 100644 lib/elearning/ELearningUtils.class.php create mode 100644 lib/elearning/ELearningUtils.php delete mode 100644 lib/elearning/Ilias3ConnectedCMS.class.php create mode 100644 lib/elearning/Ilias3ConnectedCMS.php delete mode 100644 lib/elearning/Ilias3ConnectedLink.class.php create mode 100644 lib/elearning/Ilias3ConnectedLink.php delete mode 100644 lib/elearning/Ilias3ConnectedPermissions.class.php create mode 100644 lib/elearning/Ilias3ConnectedPermissions.php delete mode 100644 lib/elearning/Ilias3ConnectedUser.class.php create mode 100644 lib/elearning/Ilias3ConnectedUser.php delete mode 100644 lib/elearning/Ilias3ContentModule.class.php create mode 100644 lib/elearning/Ilias3ContentModule.php delete mode 100644 lib/elearning/Ilias3ObjectXMLParser.class.php create mode 100644 lib/elearning/Ilias3ObjectXMLParser.php delete mode 100644 lib/elearning/Ilias3SaxParser.class.php create mode 100644 lib/elearning/Ilias3SaxParser.php delete mode 100644 lib/elearning/Ilias3Soap.class.php create mode 100644 lib/elearning/Ilias3Soap.php delete mode 100644 lib/elearning/Ilias4ConnectedCMS.class.php create mode 100644 lib/elearning/Ilias4ConnectedCMS.php delete mode 100644 lib/elearning/Ilias4ConnectedLink.class.php create mode 100644 lib/elearning/Ilias4ConnectedLink.php delete mode 100644 lib/elearning/Ilias4ConnectedPermissions.class.php create mode 100644 lib/elearning/Ilias4ConnectedPermissions.php delete mode 100644 lib/elearning/Ilias4ConnectedUser.class.php create mode 100644 lib/elearning/Ilias4ConnectedUser.php delete mode 100644 lib/elearning/Ilias4ContentModule.class.php create mode 100644 lib/elearning/Ilias4ContentModule.php delete mode 100644 lib/elearning/Ilias4Soap.class.php create mode 100644 lib/elearning/Ilias4Soap.php delete mode 100644 lib/elearning/Ilias5ConnectedCMS.class.php create mode 100644 lib/elearning/Ilias5ConnectedCMS.php delete mode 100644 lib/elearning/Ilias5ConnectedLink.class.php create mode 100644 lib/elearning/Ilias5ConnectedLink.php delete mode 100644 lib/elearning/Ilias5ConnectedPermissions.class.php create mode 100644 lib/elearning/Ilias5ConnectedPermissions.php delete mode 100644 lib/elearning/Ilias5ConnectedUser.class.php create mode 100644 lib/elearning/Ilias5ConnectedUser.php delete mode 100644 lib/elearning/Ilias5ContentModule.class.php create mode 100644 lib/elearning/Ilias5ContentModule.php delete mode 100644 lib/elearning/Ilias5Soap.class.php create mode 100644 lib/elearning/Ilias5Soap.php delete mode 100644 lib/elearning/LonCapaConnectedCMS.class.php create mode 100644 lib/elearning/LonCapaConnectedCMS.php delete mode 100644 lib/elearning/LonCapaConnectedLink.class.php create mode 100644 lib/elearning/LonCapaConnectedLink.php delete mode 100644 lib/elearning/LonCapaContentModule.class.php create mode 100644 lib/elearning/LonCapaContentModule.php delete mode 100644 lib/elearning/LonCapaRequest.class.php create mode 100644 lib/elearning/LonCapaRequest.php delete mode 100644 lib/elearning/ObjectConnections.class.php create mode 100644 lib/elearning/ObjectConnections.php delete mode 100644 lib/elearning/PmWikiConnectedCMS.class.php create mode 100644 lib/elearning/PmWikiConnectedCMS.php delete mode 100644 lib/elearning/PmWikiConnectedLink.class.php create mode 100644 lib/elearning/PmWikiConnectedLink.php delete mode 100644 lib/elearning/PmWikiContentModule.class.php create mode 100644 lib/elearning/PmWikiContentModule.php delete mode 100644 lib/exceptions/ClipboardException.class.php create mode 100644 lib/exceptions/ClipboardException.php delete mode 100644 lib/exceptions/resources/GlobalResourceLockException.class.php create mode 100644 lib/exceptions/resources/GlobalResourceLockException.php delete mode 100644 lib/exceptions/resources/GlobalResourceLockOverlapException.class.php create mode 100644 lib/exceptions/resources/GlobalResourceLockOverlapException.php delete mode 100644 lib/exceptions/resources/InvalidResourceCategoryException.class.php create mode 100644 lib/exceptions/resources/InvalidResourceCategoryException.php delete mode 100644 lib/exceptions/resources/InvalidResourceClassException.class.php create mode 100644 lib/exceptions/resources/InvalidResourceClassException.php delete mode 100644 lib/exceptions/resources/InvalidResourceException.class.php create mode 100644 lib/exceptions/resources/InvalidResourceException.php delete mode 100644 lib/exceptions/resources/InvalidResourceRequestException.class.php create mode 100644 lib/exceptions/resources/InvalidResourceRequestException.php delete mode 100644 lib/exceptions/resources/NoResourceClassException.class.php create mode 100644 lib/exceptions/resources/NoResourceClassException.php delete mode 100644 lib/exceptions/resources/ResourceBookingException.class.php create mode 100644 lib/exceptions/resources/ResourceBookingException.php delete mode 100644 lib/exceptions/resources/ResourceBookingOverlapException.class.php create mode 100644 lib/exceptions/resources/ResourceBookingOverlapException.php delete mode 100644 lib/exceptions/resources/ResourceBookingRangeException.class.php create mode 100644 lib/exceptions/resources/ResourceBookingRangeException.php delete mode 100644 lib/exceptions/resources/ResourceException.class.php create mode 100644 lib/exceptions/resources/ResourceException.php delete mode 100644 lib/exceptions/resources/ResourceNoTimeRangeException.class.php create mode 100644 lib/exceptions/resources/ResourceNoTimeRangeException.php delete mode 100644 lib/exceptions/resources/ResourcePermissionException.class.php create mode 100644 lib/exceptions/resources/ResourcePermissionException.php delete mode 100644 lib/exceptions/resources/ResourcePropertyDefinitionException.class.php create mode 100644 lib/exceptions/resources/ResourcePropertyDefinitionException.php delete mode 100644 lib/exceptions/resources/ResourcePropertyException.class.php create mode 100644 lib/exceptions/resources/ResourcePropertyException.php delete mode 100644 lib/exceptions/resources/ResourcePropertyStateException.class.php create mode 100644 lib/exceptions/resources/ResourcePropertyStateException.php delete mode 100644 lib/exceptions/resources/ResourceRequestException.class.php create mode 100644 lib/exceptions/resources/ResourceRequestException.php delete mode 100644 lib/exceptions/resources/ResourceUnavailableException.class.php create mode 100644 lib/exceptions/resources/ResourceUnavailableException.php delete mode 100644 lib/exceptions/resources/ResourceUnlockableException.class.php create mode 100644 lib/exceptions/resources/ResourceUnlockableException.php delete mode 100644 lib/exceptions/resources/SeparableRoomException.class.php create mode 100644 lib/exceptions/resources/SeparableRoomException.php delete mode 100644 lib/filesystem/FileArchiveManager.class.php create mode 100644 lib/filesystem/FileArchiveManager.php delete mode 100644 lib/filesystem/FileArchiveManagerException.class.php create mode 100644 lib/filesystem/FileArchiveManagerException.php delete mode 100644 lib/filesystem/LibraryFile.class.php create mode 100644 lib/filesystem/LibraryFile.php delete mode 100644 lib/filesystem/ResourceFolder.class.php create mode 100644 lib/filesystem/ResourceFolder.php delete mode 100644 lib/ilias_interface/ConnectedIlias.class.php create mode 100644 lib/ilias_interface/ConnectedIlias.php delete mode 100644 lib/ilias_interface/IliasModule.class.php create mode 100644 lib/ilias_interface/IliasModule.php delete mode 100644 lib/ilias_interface/IliasObjectConnections.class.php create mode 100644 lib/ilias_interface/IliasObjectConnections.php delete mode 100644 lib/ilias_interface/IliasSoap.class.php create mode 100644 lib/ilias_interface/IliasSoap.php delete mode 100644 lib/ilias_interface/IliasUser.class.php create mode 100644 lib/ilias_interface/IliasUser.php delete mode 100644 lib/models/AdmissionApplication.class.php create mode 100644 lib/models/AdmissionApplication.php delete mode 100644 lib/models/ArchivedCourse.class.php create mode 100644 lib/models/ArchivedCourse.php delete mode 100644 lib/models/ArchivedCourseMember.class.php create mode 100644 lib/models/ArchivedCourseMember.php delete mode 100644 lib/models/AuthUserMd5.class.php create mode 100644 lib/models/AuthUserMd5.php delete mode 100644 lib/models/Banner.class.php create mode 100644 lib/models/Banner.php delete mode 100644 lib/models/BannerRoles.class.php create mode 100644 lib/models/BannerRoles.php delete mode 100644 lib/models/Clipboard.class.php create mode 100644 lib/models/Clipboard.php delete mode 100644 lib/models/ClipboardItem.class.php create mode 100644 lib/models/ClipboardItem.php delete mode 100644 lib/models/ColourValue.class.php create mode 100644 lib/models/ColourValue.php delete mode 100644 lib/models/ConfigEntry.class.php create mode 100644 lib/models/ConfigEntry.php delete mode 100644 lib/models/Contact.class.php create mode 100644 lib/models/Contact.php delete mode 100644 lib/models/ContactGroup.class.php create mode 100644 lib/models/ContactGroup.php delete mode 100644 lib/models/ContactGroupItem.class.php create mode 100644 lib/models/ContactGroupItem.php delete mode 100644 lib/models/ContentTermsOfUse.class.php create mode 100644 lib/models/ContentTermsOfUse.php delete mode 100644 lib/models/Course.class.php create mode 100644 lib/models/Course.php delete mode 100644 lib/models/CourseDate.class.php create mode 100644 lib/models/CourseDate.php delete mode 100644 lib/models/CourseExDate.class.php create mode 100644 lib/models/CourseExDate.php delete mode 100644 lib/models/CourseMember.class.php create mode 100644 lib/models/CourseMember.php delete mode 100644 lib/models/CourseTopic.class.php create mode 100644 lib/models/CourseTopic.php delete mode 100644 lib/models/CronjobLog.class.php create mode 100644 lib/models/CronjobLog.php delete mode 100644 lib/models/CronjobSchedule.class.php create mode 100644 lib/models/CronjobSchedule.php delete mode 100644 lib/models/CronjobTask.class.php create mode 100644 lib/models/CronjobTask.php delete mode 100644 lib/models/DataField.class.php create mode 100644 lib/models/DataField.php delete mode 100644 lib/models/DatafieldEntryModel.class.php create mode 100644 lib/models/DatafieldEntryModel.php delete mode 100644 lib/models/DatafieldEntryModelI18N.class.php create mode 100644 lib/models/DatafieldEntryModelI18N.php delete mode 100644 lib/models/Degree.class.php create mode 100644 lib/models/Degree.php delete mode 100644 lib/models/HelpContent.class.php create mode 100644 lib/models/HelpContent.php delete mode 100644 lib/models/HelpTour.class.php create mode 100644 lib/models/HelpTour.php delete mode 100644 lib/models/HelpTourAudience.class.php create mode 100644 lib/models/HelpTourAudience.php delete mode 100644 lib/models/HelpTourSettings.class.php create mode 100644 lib/models/HelpTourSettings.php delete mode 100644 lib/models/HelpTourStep.class.php create mode 100644 lib/models/HelpTourStep.php delete mode 100644 lib/models/HelpTourUser.class.php create mode 100644 lib/models/HelpTourUser.php delete mode 100644 lib/models/Institute.class.php create mode 100644 lib/models/Institute.php delete mode 100644 lib/models/InstituteMember.class.php create mode 100644 lib/models/InstituteMember.php delete mode 100644 lib/models/InstitutePlanColumn.class.php create mode 100644 lib/models/InstitutePlanColumn.php delete mode 100644 lib/models/Kategorie.class.php create mode 100644 lib/models/Kategorie.php delete mode 100644 lib/models/LockRule.class.php create mode 100644 lib/models/LockRule.php delete mode 100644 lib/models/LoginBackground.class.php create mode 100644 lib/models/LoginBackground.php delete mode 100644 lib/models/LoginFaq.class.php create mode 100644 lib/models/LoginFaq.php delete mode 100644 lib/models/MailQueueEntry.class.php create mode 100644 lib/models/MailQueueEntry.php delete mode 100644 lib/models/Message.class.php create mode 100644 lib/models/Message.php delete mode 100644 lib/models/MessageUser.class.php create mode 100644 lib/models/MessageUser.php delete mode 100644 lib/models/MvvOverlappingConflict.class.php create mode 100644 lib/models/MvvOverlappingConflict.php delete mode 100644 lib/models/MvvOverlappingSelection.class.php create mode 100644 lib/models/MvvOverlappingSelection.php delete mode 100644 lib/models/NewsRange.class.php create mode 100644 lib/models/NewsRange.php delete mode 100644 lib/models/NewsRoles.class.php create mode 100644 lib/models/NewsRoles.php delete mode 100644 lib/models/OpenGraphURL.class.php create mode 100644 lib/models/OpenGraphURL.php delete mode 100644 lib/models/OpenGraphURLCollection.class.php create mode 100644 lib/models/OpenGraphURLCollection.php delete mode 100644 lib/models/PersonalNotifications.class.php create mode 100644 lib/models/PersonalNotifications.php delete mode 100644 lib/models/Semester.class.php create mode 100644 lib/models/Semester.php delete mode 100644 lib/models/SemesterCourse.class.php create mode 100644 lib/models/SemesterCourse.php delete mode 100644 lib/models/SemesterHoliday.class.php create mode 100644 lib/models/SemesterHoliday.php delete mode 100644 lib/models/SeminarCycleDate.class.php create mode 100644 lib/models/SeminarCycleDate.php delete mode 100644 lib/models/StudipComment.class.php create mode 100644 lib/models/StudipComment.php delete mode 100644 lib/models/StudipNews.class.php create mode 100644 lib/models/StudipNews.php delete mode 100644 lib/models/StudipScmEntry.class.php create mode 100644 lib/models/StudipScmEntry.php delete mode 100644 lib/models/StudipStudyArea.class.php create mode 100644 lib/models/StudipStudyArea.php delete mode 100644 lib/models/StudyCourse.class.php create mode 100644 lib/models/StudyCourse.php delete mode 100644 lib/models/User.class.php create mode 100644 lib/models/User.php delete mode 100644 lib/models/UserInfo.class.php create mode 100644 lib/models/UserInfo.php delete mode 100644 lib/models/UserOnline.class.php create mode 100644 lib/models/UserOnline.php delete mode 100644 lib/models/UserStudyCourse.class.php create mode 100644 lib/models/UserStudyCourse.php delete mode 100644 lib/models/WebserviceAccessRule.class.php create mode 100644 lib/models/WebserviceAccessRule.php delete mode 100644 lib/models/WikiPage.class.php create mode 100644 lib/models/WikiPage.php delete mode 100644 lib/models/calendar/CalendarCourseDate.class.php create mode 100644 lib/models/calendar/CalendarCourseDate.php delete mode 100644 lib/models/calendar/CalendarCourseExDate.class.php create mode 100644 lib/models/calendar/CalendarCourseExDate.php delete mode 100644 lib/models/calendar/CalendarDate.class.php create mode 100644 lib/models/calendar/CalendarDate.php delete mode 100644 lib/models/calendar/CalendarDateAssignment.class.php create mode 100644 lib/models/calendar/CalendarDateAssignment.php delete mode 100644 lib/models/calendar/CalendarDateException.class.php create mode 100644 lib/models/calendar/CalendarDateException.php delete mode 100644 lib/models/resources/BrokenResource.class.php create mode 100644 lib/models/resources/BrokenResource.php delete mode 100644 lib/models/resources/Building.class.php create mode 100644 lib/models/resources/Building.php delete mode 100644 lib/models/resources/GlobalResourceLock.class.php create mode 100644 lib/models/resources/GlobalResourceLock.php delete mode 100644 lib/models/resources/Location.class.php create mode 100644 lib/models/resources/Location.php delete mode 100644 lib/models/resources/Resource.class.php create mode 100644 lib/models/resources/Resource.php delete mode 100644 lib/models/resources/ResourceBooking.class.php create mode 100644 lib/models/resources/ResourceBooking.php delete mode 100644 lib/models/resources/ResourceBookingInterval.class.php create mode 100644 lib/models/resources/ResourceBookingInterval.php delete mode 100644 lib/models/resources/ResourceCategory.class.php create mode 100644 lib/models/resources/ResourceCategory.php delete mode 100644 lib/models/resources/ResourceCategoryProperty.class.php create mode 100644 lib/models/resources/ResourceCategoryProperty.php delete mode 100644 lib/models/resources/ResourceLabel.class.php create mode 100644 lib/models/resources/ResourceLabel.php delete mode 100644 lib/models/resources/ResourcePermission.class.php create mode 100644 lib/models/resources/ResourcePermission.php delete mode 100644 lib/models/resources/ResourceProperty.class.php create mode 100644 lib/models/resources/ResourceProperty.php delete mode 100644 lib/models/resources/ResourcePropertyDefinition.class.php create mode 100644 lib/models/resources/ResourcePropertyDefinition.php delete mode 100644 lib/models/resources/ResourcePropertyGroup.class.php create mode 100644 lib/models/resources/ResourcePropertyGroup.php delete mode 100644 lib/models/resources/ResourceRequest.class.php create mode 100644 lib/models/resources/ResourceRequest.php delete mode 100644 lib/models/resources/ResourceRequestAppointment.class.php create mode 100644 lib/models/resources/ResourceRequestAppointment.php delete mode 100644 lib/models/resources/ResourceRequestProperty.class.php create mode 100644 lib/models/resources/ResourceRequestProperty.php delete mode 100644 lib/models/resources/ResourceTemporaryPermission.class.php create mode 100644 lib/models/resources/ResourceTemporaryPermission.php delete mode 100644 lib/models/resources/Room.class.php create mode 100644 lib/models/resources/Room.php delete mode 100644 lib/models/resources/RoomRequest.class.php create mode 100644 lib/models/resources/RoomRequest.php delete mode 100644 lib/models/resources/SeparableRoom.class.php create mode 100644 lib/models/resources/SeparableRoom.php delete mode 100644 lib/models/resources/SeparableRoomPart.class.php create mode 100644 lib/models/resources/SeparableRoomPart.php delete mode 100644 lib/modules/Blubber.class.php create mode 100644 lib/modules/Blubber.php delete mode 100644 lib/modules/ConsultationModule.class.php create mode 100644 lib/modules/ConsultationModule.php delete mode 100644 lib/modules/CoreAdmin.class.php create mode 100644 lib/modules/CoreAdmin.php delete mode 100644 lib/modules/CoreCalendar.class.php create mode 100644 lib/modules/CoreCalendar.php delete mode 100644 lib/modules/CoreDocuments.class.php create mode 100644 lib/modules/CoreDocuments.php delete mode 100644 lib/modules/CoreElearningInterface.class.php create mode 100644 lib/modules/CoreElearningInterface.php delete mode 100644 lib/modules/CoreForum.class.php create mode 100644 lib/modules/CoreForum.php delete mode 100644 lib/modules/CoreOverview.class.php create mode 100644 lib/modules/CoreOverview.php delete mode 100644 lib/modules/CoreParticipants.class.php create mode 100644 lib/modules/CoreParticipants.php delete mode 100644 lib/modules/CorePersonal.class.php create mode 100644 lib/modules/CorePersonal.php delete mode 100644 lib/modules/CoreSchedule.class.php create mode 100644 lib/modules/CoreSchedule.php delete mode 100644 lib/modules/CoreScm.class.php create mode 100644 lib/modules/CoreScm.php delete mode 100644 lib/modules/CoreStudygroupAdmin.class.php create mode 100644 lib/modules/CoreStudygroupAdmin.php delete mode 100644 lib/modules/CoreStudygroupParticipants.class.php create mode 100644 lib/modules/CoreStudygroupParticipants.php delete mode 100644 lib/modules/CoreWiki.class.php create mode 100644 lib/modules/CoreWiki.php delete mode 100644 lib/modules/CoursewareModule.class.php create mode 100644 lib/modules/CoursewareModule.php delete mode 100644 lib/modules/FeedbackModule.class.php create mode 100644 lib/modules/FeedbackModule.php delete mode 100644 lib/modules/GradebookModule.class.php create mode 100644 lib/modules/GradebookModule.php delete mode 100644 lib/modules/IliasInterfaceModule.class.php create mode 100644 lib/modules/IliasInterfaceModule.php delete mode 100644 lib/modules/LtiToolModule.class.php create mode 100644 lib/modules/LtiToolModule.php delete mode 100644 lib/modules/StudipModule.class.php create mode 100644 lib/modules/StudipModule.php delete mode 100644 lib/phplib/CT_Cache.class.php create mode 100644 lib/phplib/CT_Cache.php delete mode 100644 lib/phplib/CT_Sql.class.php create mode 100644 lib/phplib/CT_Sql.php delete mode 100644 lib/phplib/DB_Sql.class.php create mode 100644 lib/phplib/DB_Sql.php delete mode 100644 lib/phplib/Seminar_Auth.class.php create mode 100644 lib/phplib/Seminar_Auth.php delete mode 100644 lib/phplib/Seminar_Default_Auth.class.php create mode 100644 lib/phplib/Seminar_Default_Auth.php delete mode 100644 lib/phplib/Seminar_Perm.class.php create mode 100644 lib/phplib/Seminar_Perm.php delete mode 100644 lib/phplib/Seminar_Register_Auth.class.php create mode 100644 lib/phplib/Seminar_Register_Auth.php delete mode 100644 lib/phplib/Seminar_Session.class.php create mode 100644 lib/phplib/Seminar_Session.php delete mode 100644 lib/phplib/Seminar_User.class.php create mode 100644 lib/phplib/Seminar_User.php delete mode 100644 lib/phplib/email_validation.class.php create mode 100644 lib/phplib/email_validation.php delete mode 100644 lib/plugins/core/AdminCourseAction.class.php create mode 100644 lib/plugins/core/AdminCourseAction.php delete mode 100644 lib/plugins/core/AdminCourseContents.class.php create mode 100644 lib/plugins/core/AdminCourseContents.php delete mode 100644 lib/plugins/core/AdminCourseWidgetPlugin.class.php create mode 100644 lib/plugins/core/AdminCourseWidgetPlugin.php delete mode 100644 lib/plugins/core/AdministrationPlugin.class.php create mode 100644 lib/plugins/core/AdministrationPlugin.php delete mode 100644 lib/plugins/core/DetailspagePlugin.class.php create mode 100644 lib/plugins/core/DetailspagePlugin.php delete mode 100644 lib/plugins/core/FileUploadHook.class.php create mode 100644 lib/plugins/core/FileUploadHook.php delete mode 100644 lib/plugins/core/FilesystemPlugin.class.php create mode 100644 lib/plugins/core/FilesystemPlugin.php delete mode 100644 lib/plugins/core/ForumModule.class.php create mode 100644 lib/plugins/core/ForumModule.php delete mode 100644 lib/plugins/core/HomepagePlugin.class.php create mode 100644 lib/plugins/core/HomepagePlugin.php delete mode 100644 lib/plugins/core/LibraryPlugin.class.php create mode 100644 lib/plugins/core/LibraryPlugin.php delete mode 100644 lib/plugins/core/MetricsPlugin.class.php create mode 100644 lib/plugins/core/MetricsPlugin.php delete mode 100644 lib/plugins/core/PortalPlugin.class.php create mode 100644 lib/plugins/core/PortalPlugin.php delete mode 100644 lib/plugins/core/PrivacyPlugin.class.php create mode 100644 lib/plugins/core/PrivacyPlugin.php delete mode 100644 lib/plugins/core/QuestionnaireAssignmentPlugin.class.php create mode 100644 lib/plugins/core/QuestionnaireAssignmentPlugin.php delete mode 100644 lib/plugins/core/RESTAPIPlugin.class.php create mode 100644 lib/plugins/core/RESTAPIPlugin.php delete mode 100644 lib/plugins/core/Role.class.php create mode 100644 lib/plugins/core/Role.php delete mode 100644 lib/plugins/core/ScorePlugin.class.php create mode 100644 lib/plugins/core/ScorePlugin.php delete mode 100644 lib/plugins/core/StandardPlugin.class.php create mode 100644 lib/plugins/core/StandardPlugin.php delete mode 100644 lib/plugins/core/StudIPPlugin.class.php create mode 100644 lib/plugins/core/StudIPPlugin.php delete mode 100644 lib/plugins/core/SystemPlugin.class.php create mode 100644 lib/plugins/core/SystemPlugin.php delete mode 100644 lib/plugins/core/WebServicePlugin.class.php create mode 100644 lib/plugins/core/WebServicePlugin.php delete mode 100644 lib/plugins/db/RolePersistence.class.php create mode 100644 lib/plugins/db/RolePersistence.php delete mode 100644 lib/plugins/engine/PluginEngine.class.php create mode 100644 lib/plugins/engine/PluginEngine.php delete mode 100644 lib/plugins/engine/PluginManager.class.php create mode 100644 lib/plugins/engine/PluginManager.php delete mode 100644 lib/plugins/engine/PluginRepository.class.php create mode 100644 lib/plugins/engine/PluginRepository.php delete mode 100644 lib/raumzeit/CycleData.class.php create mode 100644 lib/raumzeit/CycleData.php delete mode 100644 lib/raumzeit/CycleDataDB.class.php create mode 100644 lib/raumzeit/CycleDataDB.php delete mode 100644 lib/raumzeit/Issue.class.php create mode 100644 lib/raumzeit/Issue.php delete mode 100644 lib/raumzeit/IssueDB.class.php create mode 100644 lib/raumzeit/IssueDB.php delete mode 100644 lib/raumzeit/MetaDate.class.php create mode 100644 lib/raumzeit/MetaDate.php delete mode 100644 lib/raumzeit/MetaDateDB.class.php create mode 100644 lib/raumzeit/MetaDateDB.php delete mode 100644 lib/raumzeit/SeminarDB.class.php create mode 100644 lib/raumzeit/SeminarDB.php delete mode 100644 lib/raumzeit/SingleDate.class.php create mode 100644 lib/raumzeit/SingleDate.php delete mode 100644 lib/raumzeit/SingleDateDB.class.php create mode 100644 lib/raumzeit/SingleDateDB.php delete mode 100644 lib/resources/ResourceManager.class.php create mode 100644 lib/resources/ResourceManager.php delete mode 100644 lib/resources/RoomManager.class.php create mode 100644 lib/resources/RoomManager.php delete mode 100644 lib/soap/StudipSoapClient.class.php create mode 100644 lib/soap/StudipSoapClient.php delete mode 100644 lib/soap/StudipSoapClient_PHP5.class.php create mode 100644 lib/soap/StudipSoapClient_PHP5.php delete mode 100644 vendor/oauth-php/library/session/OAuthSessionAbstract.class.php create mode 100644 vendor/oauth-php/library/session/OAuthSessionAbstract.php delete mode 100644 vendor/oauth-php/library/signature_method/OAuthSignatureMethod.class.php create mode 100644 vendor/oauth-php/library/signature_method/OAuthSignatureMethod.php delete mode 100644 vendor/oauth-php/library/store/OAuthStoreAbstract.class.php create mode 100644 vendor/oauth-php/library/store/OAuthStoreAbstract.php diff --git a/app/controllers/questionnaire.php b/app/controllers/questionnaire.php index 4016b9d..94e4d04 100644 --- a/app/controllers/questionnaire.php +++ b/app/controllers/questionnaire.php @@ -1,7 +1,5 @@ = 7.1", - "psr/http-message": "^1.0", - "symfony/polyfill-mbstring": "^1.0" + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.1" }, "require-dev": { "ext-zip": "*", - "guzzlehttp/guzzle": ">= 6.3", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": ">= 7.5" + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^10.0", + "vimeo/psalm": "^5.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" }, "type": "library", "autoload": { @@ -1514,7 +1520,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.1.0" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.0" }, "funding": [ { @@ -1526,7 +1532,7 @@ "type": "open_collective" } ], - "time": "2020-05-30T13:11:16+00:00" + "time": "2023-06-21T14:59:35+00:00" }, { "name": "markbaker/complex", @@ -1637,16 +1643,16 @@ }, { "name": "monolog/monolog", - "version": "2.9.1", + "version": "2.9.3", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1" + "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a30bfe2e142720dfa990d0a7e573997f5d884215", + "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215", "shasum": "" }, "require": { @@ -1667,8 +1673,8 @@ "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5.14", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", "predis/predis": "^1.1 || ^2.0", "rollbar/rollbar": "^1.3 || ^2 || ^3", "ruflin/elastica": "^7", @@ -1723,7 +1729,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.9.1" + "source": "https://github.com/Seldaek/monolog/tree/2.9.3" }, "funding": [ { @@ -1735,94 +1741,33 @@ "type": "tidelift" } ], - "time": "2023-02-06T13:44:46+00:00" - }, - { - "name": "myclabs/php-enum", - "version": "1.7.7", - "source": { - "type": "git", - "url": "https://github.com/myclabs/php-enum.git", - "reference": "d178027d1e679832db9f38248fcc7200647dc2b7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/php-enum/zipball/d178027d1e679832db9f38248fcc7200647dc2b7", - "reference": "d178027d1e679832db9f38248fcc7200647dc2b7", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7", - "squizlabs/php_codesniffer": "1.*", - "vimeo/psalm": "^3.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "MyCLabs\\Enum\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP Enum contributors", - "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" - } - ], - "description": "PHP Enum implementation", - "homepage": "http://github.com/myclabs/php-enum", - "keywords": [ - "enum" - ], - "support": { - "issues": "https://github.com/myclabs/php-enum/issues", - "source": "https://github.com/myclabs/php-enum/tree/1.7.7" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", - "type": "tidelift" - } - ], - "time": "2020-11-14T18:14:52+00:00" + "time": "2024-04-12T20:52:51+00:00" }, { "name": "neomerx/cors-psr7", - "version": "v2.0.2", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/neomerx/cors-psr7.git", - "reference": "454e923aaf9ba4aa162f7aca2a514e41708b6ba5" + "reference": "515d7fdb60b9d475da70029d4e5662beaae1875f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/neomerx/cors-psr7/zipball/454e923aaf9ba4aa162f7aca2a514e41708b6ba5", - "reference": "454e923aaf9ba4aa162f7aca2a514e41708b6ba5", + "url": "https://api.github.com/repos/neomerx/cors-psr7/zipball/515d7fdb60b9d475da70029d4e5662beaae1875f", + "reference": "515d7fdb60b9d475da70029d4e5662beaae1875f", "shasum": "" }, "require": { - "php": ">=7.1.0", + "php": ">=8.0.0", "psr/http-message": "^1.0", - "psr/log": "^1.0" + "psr/log": "^3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", + "friendsofphp/php-cs-fixer": "^3.11", "mockery/mockery": "^1.0", - "phpmd/phpmd": "^2.6", - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^9.2", "scrutinizer/ocular": "^1.4", - "squizlabs/php_codesniffer": "^2.9" + "squizlabs/php_codesniffer": "^3.0" }, "type": "library", "autoload": { @@ -1854,9 +1799,9 @@ ], "support": { "issues": "https://github.com/neomerx/cors-psr7/issues", - "source": "https://github.com/neomerx/cors-psr7/tree/develop" + "source": "https://github.com/neomerx/cors-psr7/tree/3.0.2" }, - "time": "2019-02-14T10:35:50+00:00" + "time": "2022-11-28T03:29:06+00:00" }, { "name": "nikic/fast-route", @@ -2218,16 +2163,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.6.3", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "58c3f47f650c94ec05a151692652a868995d2938" + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", - "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", "shasum": "" }, "require": { @@ -2281,7 +2226,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-06-14T06:56:20+00:00" + "time": "2024-05-08T12:18:48+00:00" }, { "name": "paragonie/random_compat", @@ -2438,24 +2383,26 @@ }, { "name": "php-di/invoker", - "version": "2.0.0", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/PHP-DI/Invoker.git", - "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a" + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/540c27c86f663e20fe39a24cd72fa76cdb21d41a", - "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", + "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", "shasum": "" }, "require": { - "psr/container": "~1.0" + "php": ">=7.3", + "psr/container": "^1.0|^2.0" }, "require-dev": { "athletic/athletic": "~0.1.8", - "phpunit/phpunit": "~4.5" + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" }, "type": "library", "autoload": { @@ -2479,9 +2426,15 @@ ], "support": { "issues": "https://github.com/PHP-DI/Invoker/issues", - "source": "https://github.com/PHP-DI/Invoker/tree/master" + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" }, - "time": "2017-03-20T19:28:22+00:00" + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2023-09-08T09:24:21+00:00" }, { "name": "php-di/php-di", @@ -2899,21 +2852,21 @@ }, { "name": "phpxmlrpc/extras", - "version": "1.0.0-beta2", + "version": "1.0.0-beta4", "source": { "type": "git", "url": "https://github.com/gggeek/phpxmlrpc-extras.git", - "reference": "4bf48852955f7b4698b37ae91cce83c49f658b4c" + "reference": "7aedeb5100b0bca4085692a2c626fafb726c73b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/gggeek/phpxmlrpc-extras/zipball/4bf48852955f7b4698b37ae91cce83c49f658b4c", - "reference": "4bf48852955f7b4698b37ae91cce83c49f658b4c", + "url": "https://api.github.com/repos/gggeek/phpxmlrpc-extras/zipball/7aedeb5100b0bca4085692a2c626fafb726c73b6", + "reference": "7aedeb5100b0bca4085692a2c626fafb726c73b6", "shasum": "" }, "require": { - "php": "^5.3.0 || ^7.0 || ^8.0", - "phpxmlrpc/phpxmlrpc": "^4.6.0" + "php": "^5.4.0 || ^7.0 || ^8.0", + "phpxmlrpc/phpxmlrpc": "^4.10.1" }, "require-dev": { "ext-curl": "*", @@ -2945,22 +2898,22 @@ ], "support": { "issues": "https://github.com/gggeek/phpxmlrpc-extras/issues", - "source": "https://github.com/gggeek/phpxmlrpc-extras/tree/1.0.0-beta2" + "source": "https://github.com/gggeek/phpxmlrpc-extras/tree/1.0.0-beta4" }, - "time": "2023-01-19T14:20:38+00:00" + "time": "2024-04-16T15:48:39+00:00" }, { "name": "phpxmlrpc/phpxmlrpc", - "version": "4.10.0", + "version": "4.10.3", "source": { "type": "git", "url": "https://github.com/gggeek/phpxmlrpc.git", - "reference": "7103864975ca0b930574cabc8b4593093ee4432e" + "reference": "6725f215cd7d7c7d36c1d773aa89902aef2da67c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/gggeek/phpxmlrpc/zipball/7103864975ca0b930574cabc8b4593093ee4432e", - "reference": "7103864975ca0b930574cabc8b4593093ee4432e", + "url": "https://api.github.com/repos/gggeek/phpxmlrpc/zipball/6725f215cd7d7c7d36c1d773aa89902aef2da67c", + "reference": "6725f215cd7d7c7d36c1d773aa89902aef2da67c", "shasum": "" }, "require": { @@ -2968,7 +2921,8 @@ "php": "^5.4.0 || ^7.0 || ^8.0" }, "conflict": { - "phpxmlrpc/extras": "<= 0.6.3" + "phpxmlrpc/extras": "<= 1.0.0-beta2", + "phpxmlrpc/jsonrpc": "<= 1.0.0-beta1" }, "require-dev": { "ext-curl": "*", @@ -3004,9 +2958,9 @@ ], "support": { "issues": "https://github.com/gggeek/phpxmlrpc/issues", - "source": "https://github.com/gggeek/phpxmlrpc/tree/4.10.0" + "source": "https://github.com/gggeek/phpxmlrpc/tree/4.10.3" }, - "time": "2023-02-11T17:10:30+00:00" + "time": "2024-04-23T10:50:06+00:00" }, { "name": "psr/cache", @@ -3107,22 +3061,27 @@ }, { "name": "psr/container", - "version": "1.1.2", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -3149,27 +3108,27 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-11-05T16:50:12+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -3189,7 +3148,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -3201,26 +3160,26 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -3244,7 +3203,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -3256,9 +3215,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -3315,21 +3274,21 @@ }, { "name": "psr/http-server-handler", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", "shasum": "" }, "require": { "php": ">=7.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -3349,7 +3308,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP server-side request handler", @@ -3365,28 +3324,27 @@ "server" ], "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" }, - "time": "2018-10-30T16:46:14+00:00" + "time": "2023-04-10T20:06:20+00:00" }, { "name": "psr/http-server-middleware", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", "shasum": "" }, "require": { "php": ">=7.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0 || ^2.0", "psr/http-server-handler": "^1.0" }, "type": "library", @@ -3407,7 +3365,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP server-side middleware", @@ -3423,36 +3381,36 @@ ], "support": { "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" }, - "time": "2018-10-30T17:12:04+00:00" + "time": "2023-04-11T06:14:47+00:00" }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3473,31 +3431,31 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:46:02+00:00" }, { "name": "psr/simple-cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -3512,7 +3470,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for simple caching", @@ -3524,9 +3482,9 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/master" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2017-10-23T01:57:42+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { "name": "psy/psysh", @@ -4706,16 +4664,16 @@ }, { "name": "symfony/string", - "version": "v6.4.7", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ffeb9591c61f65a68d47f77d12b83fa530227a69" + "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ffeb9591c61f65a68d47f77d12b83fa530227a69", - "reference": "ffeb9591c61f65a68d47f77d12b83fa530227a69", + "url": "https://api.github.com/repos/symfony/string/zipball/a147c0f826c4a1f3afb763ab8e009e37c877a44d", + "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d", "shasum": "" }, "require": { @@ -4772,7 +4730,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.7" + "source": "https://github.com/symfony/string/tree/v6.4.8" }, "funding": [ { @@ -4788,7 +4746,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:22:46+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/var-dumper", @@ -5651,6 +5609,52 @@ "time": "2022-02-16T19:48:08+00:00" }, { + "name": "codeception/specify", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Specify.git", + "reference": "b718b4b9bb722be4756aa7c9aa7e89b6babc2cf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Specify/zipball/b718b4b9bb722be4756aa7c9aa7e89b6babc2cf6", + "reference": "b718b4b9bb722be4756aa7c9aa7e89b6babc2cf6", + "shasum": "" + }, + "require": { + "myclabs/deep-copy": "^1.10", + "php": ">=7.4.0", + "phpunit/phpunit": "^8.0|^9.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@codeception.com" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "BDD code blocks for PHPUnit and Codeception", + "support": { + "issues": "https://github.com/Codeception/Specify/issues", + "source": "https://github.com/Codeception/Specify/tree/2.0.0" + }, + "time": "2021-11-22T00:16:11+00:00" + }, + { "name": "codeception/stub", "version": "4.1.3", "source": { @@ -5692,6 +5696,76 @@ "time": "2024-02-02T19:21:00+00:00" }, { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { "name": "maximebf/debugbar", "version": "v1.22.3", "source": { @@ -5761,16 +5835,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -5778,11 +5852,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -5808,7 +5883,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -5816,7 +5891,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "overtrue/phplint", @@ -6034,34 +6109,29 @@ }, { "name": "php-http/httplug", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "f640739f80dfa1152533976e3c112477f69274eb" + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/f640739f80dfa1152533976e3c112477f69274eb", - "reference": "f640739f80dfa1152533976e3c112477f69274eb", + "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", "php-http/promise": "^1.1", "psr/http-client": "^1.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.1", - "phpspec/phpspec": "^5.1 || ^6.0" + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "psr-4": { "Http\\Client\\": "src/" @@ -6090,37 +6160,32 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.3.0" + "source": "https://github.com/php-http/httplug/tree/2.4.0" }, - "time": "2022-02-21T09:52:22+00:00" + "time": "2023-04-14T15:10:03+00:00" }, { "name": "php-http/promise", - "version": "1.1.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/php-http/promise.git", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", - "phpspec/phpspec": "^5.1.2 || ^6.2" + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, "autoload": { "psr-4": { "Http\\Promise\\": "src/" @@ -6147,9 +6212,9 @@ ], "support": { "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.1.0" + "source": "https://github.com/php-http/promise/tree/1.3.1" }, - "time": "2020-07-07T09:29:14+00:00" + "time": "2024-03-15T13:55:21+00:00" }, { "name": "phpstan/phpstan", @@ -6211,16 +6276,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.14", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", - "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { @@ -6228,18 +6293,18 @@ "ext-libxml": "*", "ext-xmlwriter": "*", "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -6248,7 +6313,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -6277,7 +6342,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" }, "funding": [ { @@ -6285,32 +6350,32 @@ "type": "github" } ], - "time": "2024-03-12T15:33:41+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -6337,8 +6402,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -6346,28 +6410,28 @@ "type": "github" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", - "version": "4.0.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-pcntl": "*" @@ -6375,7 +6439,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -6401,7 +6465,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" }, "funding": [ { @@ -6409,32 +6473,32 @@ "type": "github" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2020-09-28T05:58:55+00:00" }, { "name": "phpunit/php-text-template", - "version": "3.0.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -6460,8 +6524,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" }, "funding": [ { @@ -6469,32 +6532,32 @@ "type": "github" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2020-10-26T05:33:50+00:00" }, { "name": "phpunit/php-timer", - "version": "6.0.0", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -6520,7 +6583,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" }, "funding": [ { @@ -6528,23 +6591,24 @@ "type": "github" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2020-10-26T13:16:10+00:00" }, { "name": "phpunit/phpunit", - "version": "10.5.20", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/547d314dc24ec1e177720d45c6263fb226cc2ae3", - "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -6554,26 +6618,27 @@ "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.5", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-invoker": "^4.0", - "phpunit/php-text-template": "^3.0", - "phpunit/php-timer": "^6.0", - "sebastian/cli-parser": "^2.0", - "sebastian/code-unit": "^2.0", - "sebastian/comparator": "^5.0", - "sebastian/diff": "^5.0", - "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.1", - "sebastian/global-state": "^6.0.1", - "sebastian/object-enumerator": "^5.0", - "sebastian/recursion-context": "^5.0", - "sebastian/type": "^4.0", - "sebastian/version": "^4.0" + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -6581,7 +6646,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -6613,7 +6678,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -6629,7 +6694,7 @@ "type": "tidelift" } ], - "time": "2024-04-24T06:32:35+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "psr/event-dispatcher", @@ -6683,28 +6748,28 @@ }, { "name": "sebastian/cli-parser", - "version": "2.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -6727,8 +6792,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -6736,32 +6800,32 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", - "version": "2.0.0", + "version": "1.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -6784,7 +6848,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" }, "funding": [ { @@ -6792,32 +6856,32 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -6839,7 +6903,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" }, "funding": [ { @@ -6847,36 +6911,34 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -6915,8 +6977,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -6924,33 +6985,33 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", - "version": "3.2.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -6973,8 +7034,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -6982,33 +7042,33 @@ "type": "github" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "5.1.1", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -7040,8 +7100,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -7049,27 +7108,27 @@ "type": "github" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "6.1.0", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-posix": "*" @@ -7077,7 +7136,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -7096,7 +7155,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", + "homepage": "http://www.github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -7104,8 +7163,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -7113,34 +7171,34 @@ "type": "github" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.2", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -7182,8 +7240,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -7191,35 +7248,38 @@ "type": "github" } ], - "time": "2024-03-02T07:17:12+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.2", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -7238,14 +7298,13 @@ } ], "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -7253,33 +7312,33 @@ "type": "github" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.2", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -7302,8 +7361,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -7311,34 +7369,34 @@ "type": "github" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", - "version": "5.0.0", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -7360,7 +7418,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" }, "funding": [ { @@ -7368,32 +7426,32 @@ "type": "github" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { "name": "sebastian/object-reflector", - "version": "3.0.0", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -7415,7 +7473,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, "funding": [ { @@ -7423,32 +7481,32 @@ "type": "github" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -7478,7 +7536,7 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -7486,32 +7544,86 @@ "type": "github" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "4.0.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -7534,7 +7646,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -7542,29 +7654,29 @@ "type": "github" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", - "version": "4.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -7587,7 +7699,7 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" }, "funding": [ { @@ -7595,20 +7707,20 @@ "type": "github" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2020-09-28T06:39:44+00:00" }, { "name": "symfony/cache", - "version": "v6.4.7", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "b9e9b93c9817ec6c789c7943f5e54b57a041c16a" + "reference": "287142df5579ce223c485b3872df3efae8390984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/b9e9b93c9817ec6c789c7943f5e54b57a041c16a", - "reference": "b9e9b93c9817ec6c789c7943f5e54b57a041c16a", + "url": "https://api.github.com/repos/symfony/cache/zipball/287142df5579ce223c485b3872df3efae8390984", + "reference": "287142df5579ce223c485b3872df3efae8390984", "shasum": "" }, "require": { @@ -7675,7 +7787,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.7" + "source": "https://github.com/symfony/cache/tree/v6.4.8" }, "funding": [ { @@ -7691,7 +7803,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:22:46+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/cache-contracts", @@ -7771,16 +7883,16 @@ }, { "name": "symfony/css-selector", - "version": "v6.4.7", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "1c5d5c2103c3762aff27a27e1e2409e30a79083b" + "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c5d5c2103c3762aff27a27e1e2409e30a79083b", - "reference": "1c5d5c2103c3762aff27a27e1e2409e30a79083b", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4b61b02fe15db48e3687ce1c45ea385d1780fe08", + "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08", "shasum": "" }, "require": { @@ -7816,7 +7928,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.7" + "source": "https://github.com/symfony/css-selector/tree/v6.4.8" }, "funding": [ { @@ -7832,20 +7944,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:22:46+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.7", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "d84384f3f67de3cb650db64d685d70395dacfc3f" + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d84384f3f67de3cb650db64d685d70395dacfc3f", - "reference": "d84384f3f67de3cb650db64d685d70395dacfc3f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", "shasum": "" }, "require": { @@ -7896,7 +8008,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.7" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" }, "funding": [ { @@ -7912,7 +8024,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:22:46+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -7992,16 +8104,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.7", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "511c48990be17358c23bf45c5d71ab85d40fb764" + "reference": "3ef977a43883215d560a2cecb82ec8e62131471c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/511c48990be17358c23bf45c5d71ab85d40fb764", - "reference": "511c48990be17358c23bf45c5d71ab85d40fb764", + "url": "https://api.github.com/repos/symfony/finder/zipball/3ef977a43883215d560a2cecb82ec8e62131471c", + "reference": "3ef977a43883215d560a2cecb82ec8e62131471c", "shasum": "" }, "require": { @@ -8036,7 +8148,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.7" + "source": "https://github.com/symfony/finder/tree/v6.4.8" }, "funding": [ { @@ -8052,20 +8164,20 @@ "type": "tidelift" } ], - "time": "2024-04-23T10:36:43+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/options-resolver", - "version": "v6.4.7", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "9a3c92b490716ba6771f5beced13c6eda7183eed" + "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9a3c92b490716ba6771f5beced13c6eda7183eed", - "reference": "9a3c92b490716ba6771f5beced13c6eda7183eed", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22ab9e9101ab18de37839074f8a1197f55590c1b", + "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b", "shasum": "" }, "require": { @@ -8103,7 +8215,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.4.7" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.8" }, "funding": [ { @@ -8119,20 +8231,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:22:46+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.7", + "version": "v6.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "825f9b00c37bbe1c1691cc1aff9b5451fc9b4405" + "reference": "792ca836f99b340f2e9ca9497c7953948c49a504" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/825f9b00c37bbe1c1691cc1aff9b5451fc9b4405", - "reference": "825f9b00c37bbe1c1691cc1aff9b5451fc9b4405", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/792ca836f99b340f2e9ca9497c7953948c49a504", + "reference": "792ca836f99b340f2e9ca9497c7953948c49a504", "shasum": "" }, "require": { @@ -8180,7 +8292,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.7" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.8" }, "funding": [ { @@ -8196,7 +8308,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:22:46+00:00" + "time": "2024-05-31T14:49:08+00:00" }, { "name": "theseer/tokenizer", diff --git a/db/migrations/1.2_step_102_datenfeldtypen.php b/db/migrations/1.2_step_102_datenfeldtypen.php index 1e7b552..b639132 100644 --- a/db/migrations/1.2_step_102_datenfeldtypen.php +++ b/db/migrations/1.2_step_102_datenfeldtypen.php @@ -63,7 +63,7 @@ class Step102Datenfeldtypen extends Migration { return; } - require_once 'lib/classes/DataFieldStructure.class.php'; + require_once 'lib/classes/DataFieldStructure.php'; $ids = array_keys(DataFieldStructure::getDataFieldStructures()); diff --git a/db/migrations/1.6_step_25_raumzeit_db_conversion.php b/db/migrations/1.6_step_25_raumzeit_db_conversion.php index 1dd8edf..9a8ffd7 100644 --- a/db/migrations/1.6_step_25_raumzeit_db_conversion.php +++ b/db/migrations/1.6_step_25_raumzeit_db_conversion.php @@ -55,8 +55,8 @@ class Step25RaumzeitDbConversion extends Migration // include business logic - require_once('lib/classes/Seminar.class.php'); - require_once('lib/resources/lib/VeranstaltungResourcesAssign.class.php'); + require_once('lib/classes/Seminar.php'); + require_once('lib/resources/lib/VeranstaltungResourcesAssign.php'); diff --git a/lib/admissionrules/conditionaladmission/ConditionalAdmission.class.php b/lib/admissionrules/conditionaladmission/ConditionalAdmission.class.php deleted file mode 100644 index 7fdbc00..0000000 --- a/lib/admissionrules/conditionaladmission/ConditionalAdmission.class.php +++ /dev/null @@ -1,563 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class ConditionalAdmission extends AdmissionRule -{ - // --- ATTRIBUTES --- - - /** - * All conditions that must be fulfilled for successful admission. - */ - public $conditions = []; - - /** - * Grouped conditions that must be fulfilled for successful admission. - */ - public $conditiongroups = []; - - /** - * Conditions that must be fulfilled for successful admission. - */ - public $ungrouped_conditions = []; - - /** - * Quota for grouped conditions - */ - public $quota = []; - - /** - * Are condition groups allowed? - */ - public $conditiongroups_allowed = null; - - /** - * courseset siblings of this rule - */ - public $siblings = []; - - // --- OPERATIONS --- - - /** - * Standard constructor. - * - * @param String $ruleId If this rule has been saved previously, it - * will be loaded from database. - * @return ConditionalAdmission the current object (this). - */ - public function __construct($ruleId='', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - $this->default_message = _('Sie erfüllen nicht die Bedingung: %s'); - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('conditionaladmissions'); - } - return $this; - } - - /** - * Adds a new UserFilter to this rule. - * - * @param UserFilter $condition - * @param String $group - * @param Int $quota - * @return ConditionalAdmission - */ - public function addCondition($condition, $group = '', $quota = 0) - { - if ($group) { - $this->conditiongroups[$group][$condition->getId()] = $condition; - $this->quota[$group] = $quota; - } else { - $this->ungrouped_conditions[$condition->getId()] = $condition; - } - return $this; - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() - { - parent::delete(); - // Delete rule data. - $stmt = DBManager::get()->prepare("DELETE FROM `conditionaladmissions` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - // Delete all associated conditions... - foreach ($this->ungrouped_conditions as $condition) { - $condition->delete(); - } - foreach ($this->conditiongroups as $conditions) { - foreach ($conditions as $condition) { - $condition->delete(); - } - } - // ... and their connection to this rule. - $stmt = DBManager::get()->prepare("DELETE `admission_condition`, `admission_conditiongroup` FROM `admission_condition` - LEFT JOIN `admission_conditiongroup` USING( `conditiongroup_id`) - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Gets all users that are matched by thís rule. - * - * @return Array An array containing IDs of users who are matched by - * this rule. - */ - public function getAffectedUsers() - { - $users = []; - foreach ($this->ungrouped_conditions as $condition) { - $users = array_intersect($users, $condition->getAffectedUsers()); - } - foreach ($this->conditiongroups as $conditions) { - foreach ($conditions as $condition) { - $users = array_intersect($users, $condition->getAffectedUsers()); - } - } - return $users; - } - - /** - * Gets all defined conditions. - * - * @return Array - */ - public function getConditions() - { - return $this->conditions; - } - - /** - * Gets all grouped conditiongroups. - * - * @return Array - */ - public function getConditionGroups() - { - return $this->conditiongroups; - } - - /** - * Gets all grouped conditiongroups. - * - * @return Array - */ - public function getUngroupedConditions() - { - return $this->ungrouped_conditions; - } - - /** - * Gets quota for given conditiongroup. - * - * @return Array - */ - public function getQuota($group_id) - { - return $this->quota[$group_id]; - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() - { - return _('Über eine Menge von Bedingungen kann festgelegt werden, '. - 'wer zur Anmeldung zu den Veranstaltungen des Anmeldesets '. - 'zugelassen ist. Es muss nur eine der Bedingungen erfüllt sein, '. - 'innerhalb einer Bedingung müssen aber alle Datenfelder '. - 'zutreffen.'); - } - - /** - * Return this rule's name. - */ - public static function getName() - { - return _('Bedingte Anmeldung'); - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() - { - // Open generic admission rule template. - $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); - $tpl->set_attribute('rule', $this); - $factory = new Flexi\Factory(__DIR__ . '/templates/'); - // Now open specific template for this rule and insert base template. - $tpl2 = $factory->open('configure'); - $tpl2->set_attribute('rule', $this); - $tpl2->set_attribute('tpl', $tpl->render()); - return $tpl2->render(); - } - - /** - * Helper function for loading data from DB. Generic AdmissionRule data is - * loaded with the parent load() method. - */ - public function load() - { - // Load basic data. - $stmt = DBManager::get()->prepare("SELECT * - FROM `conditionaladmissions` WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->message = $current['message']; - $this->startTime = $current['start_time']; - $this->endTime = $current['end_time']; - // Retrieve conditions. - $stmt = DBManager::get()->prepare("SELECT * - FROM `admission_condition` LEFT JOIN `admission_conditiongroup` USING (`conditiongroup_id`) WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - $conditions = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($conditions as $condition) { - $currentCondition = new UserFilter($condition['filter_id']); - if ($condition['conditiongroup_id']) { - $this->conditiongroups[$condition['conditiongroup_id']][$condition['filter_id']] = $currentCondition; - $this->quota[$condition['conditiongroup_id']] = $condition['quota']; - } else { - $this->ungrouped_conditions[$condition['filter_id']] = $currentCondition; - } - } - } - } - - /** - * Checks if condition groups are allowed. - * - * @return Boolean - */ - public function conditiongroupsAllowed() - { - if ($this->conditiongroups_allowed === null) { - foreach ($this->getSiblings() as $rule) { - if (get_class($rule) == 'ParticipantRestrictedAdmission') { - if ($rule->getDistributionTime() > time()) { - $this->conditiongroups_allowed = true; - } - } - } - } - return $this->conditiongroups_allowed; - } - - /** - * Removes condition groups and sets all conditions as ungrouped. - * - */ - public function removeConditionGroups() - { - foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { - foreach ($conditions as $condition_id => $condition) { - $this->ungrouped_conditions[$condition_id] = $condition; - } - } - $this->conditiongroups = []; - } - - /** - * Removes the condition with the given ID from the rule. - * - * @param String $conditionId - * @return ConditionalAdmission - */ - public function removeCondition($conditionId) - { - if (isset($this->ungrouped_conditions[$conditionId])) { - $this->ungrouped_conditions[$conditionId]->delete(); - unset($this->ungrouped_conditions[$conditionId]); - } else { - foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { - if (isset($conditions[$conditionId])) { - $this->conditiongroups[$conditiongroup_id]->conditions[$conditionId]->delete(); - unset($this->conditiongroups[$conditiongroup_id]->conditions[$conditionId]); - } - } - } - return $this; - } - - /** - * Checks whether the given user fulfills the configured - * admission conditions. Only one of the conditions needs to be - * fulfilled (logical operator OR). The fields in a condition are - * in conjunction (logical operator AND). - * - * @param String $userId - * @param String $courseId - * @return Array Array with conditions that have failed. If array - * is empty, everything's all right. - */ - public function ruleApplies($userId, $courseId) - { - $failed = []; - // Check for rule validity time frame. - if ($this->checkTimeFrame()) { - // Check all configured conditions. - foreach ($this->ungrouped_conditions as $condition) { - if (!$condition->isFulfilled($userId)) { - $failed[] = $this->getMessage($condition->toString()); - } else { - $failed = []; - break; - } - } - foreach ($this->conditiongroups as $conditions) { - foreach ($conditions as $condition) { - if (!$condition->isFulfilled($userId)) { - $failed[] = $this->getMessage($condition->toString()); - } else { - $failed = []; - break 2; - } - } - } - } - return $failed; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) - { - UserFilterField::getAvailableFilterFields(); - parent::setAllData($data); - $this->conditions = []; - $this->ungrouped_conditions = []; - $this->conditiongroups = []; - $this->quota = []; - foreach ($data['conditions'] as $ser_con) { - $condition = ObjectBuilder::build($ser_con, 'UserFilter'); - $this->addCondition($condition, $data['conditiongroup_'.$condition->getId()], $data['quota_'.$data['conditiongroup_'.$condition->getId()]] ?? 0); - } - foreach ($this->getConditiongroups() as $conditiongroup_id => $conditions) { - if (mb_strlen($conditiongroup_id) < 32) { - $group = md5(uniqid('conditiongroups' . microtime(), true)); - - $this->conditiongroups[$group] = $this->conditiongroups[$conditiongroup_id]; - unset($this->conditiongroups[$conditiongroup_id]); - - $this->quota[$group] = $this->quota[$conditiongroup_id]; - unset($this->quota[$conditiongroup_id]); - } - } - if (count($this->getConditiongroups()) && $data['conditiongroups_allowed']) { - $this->conditiongroups_allowed = true; - } - - return $this; - } - - /** - * Helper function for storing data to DB. - */ - public function store() - { - // Store rule data. - $stmt = DBManager::get()->prepare("INSERT INTO `conditionaladmissions` - (`rule_id`, `message`, `start_time`, `end_time`, `mkdate`, `chdate`) - VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `message`=VALUES(`message`), - `start_time`=VALUES(`start_time`), - `end_time`=VALUES(`end_time`), - `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->message, (int)$this->startTime, - (int)$this->endTime, time(), time()]); - // prepare condition data. - $keys = array_keys($this->ungrouped_conditions); - foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { - $keys = array_merge($keys, array_keys($conditions)); - } - - // Delete removed conditions from DB. - $stmt = DBManager::get()->prepare("SELECT `filter_id`, `conditiongroup_id` FROM - `admission_condition` WHERE `rule_id`=? AND `filter_id` NOT IN ('". - implode("', '", $keys)."')"); - $stmt->execute([$this->id]); - $groups = []; - foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $entry) { - $current = new UserFilter($entry['filter_id']); - $current->delete(); - $groups[] = $entry['conditiongroup_id']; - } - DBManager::get()->exec("DELETE FROM `admission_condition` - WHERE `rule_id`='".$this->id."' AND `filter_id` NOT IN ('". - implode("', '", $keys)."')"); - // Store all conditions. - $queries = []; - $parameters = []; - $groupqueries = []; - $groupparameters = []; - foreach ($this->ungrouped_conditions as $condition) { - // Store each ungrouped condition... - $condition->store(); - $queries[] = "(?, ?, ?, ?)"; - $parameters[] = $this->id; - $parameters[] = $condition->getId(); - $parameters[] = ''; - $parameters[] = time(); - } - - foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { - $groupqueries[] = "(?, ?)"; - $groupparameters[] = $conditiongroup_id; - $groupparameters[] = $this->quota[$conditiongroup_id]; - foreach ($conditions as $condition) { - // Store each group of conditions... - $condition->store(); - $queries[] = "(?, ?, ?, ?)"; - $parameters[] = $this->id; - $parameters[] = $condition->getId(); - $parameters[] = $conditiongroup_id; - $parameters[] = time(); - } - } - - // Store all assignments between rule and condition. - if (count($queries) > 0) { - $stmt = DBManager::get()->prepare("INSERT INTO `admission_condition` - (`rule_id`, `filter_id`, `conditiongroup_id`, `mkdate`) - VALUES " . implode(',', $queries) . " ON DUPLICATE KEY UPDATE - `filter_id`=VALUES(`filter_id`), `conditiongroup_id`=VALUES(`conditiongroup_id`)"); - $stmt->execute($parameters); - } - - // Store all assignments between condition and conditiongroup. - if (count($groupqueries) > 0) { - $stmt = DBManager::get()->prepare("INSERT INTO `admission_conditiongroup` - (`conditiongroup_id`, `quota`) - VALUES " . implode(',', $groupqueries) . " ON DUPLICATE KEY UPDATE - `quota`=VALUES(`quota`)"); - $stmt->execute($groupparameters); - } - - return $this; - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() - { - $factory = new Flexi\Factory(__DIR__ . '/templates/'); - $tpl = $factory->open('info'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array $data Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = parent::validate($data); - if (!$data['conditions'] || !is_array($data['conditions'])) { - $errors[] = _('Es muss mindestens eine Auswahlbedingung angegeben werden.'); - return $errors; - } - $quota = []; - $quota_total = 0; - $grouped = 0; - $ungrouped = 0; - $no_quota = 0; - foreach ($data['conditions'] as $ser_con) { - $condition = ObjectBuilder::build($ser_con, 'UserFilter'); - if ($data['conditiongroup_'.$condition->getId()]) { - $grouped++; - } else { - $ungrouped++; - } - $quota[$data['conditiongroup_' . $condition->getId()]] = $data['quota_' . $data['conditiongroup_' . $condition->getId()]] ?? 0; - if (!$quota[$data['conditiongroup_' . $condition->getId()]]) { - $no_quota++; - } - } - foreach ($quota as $part) { - $quota_total += $part; - } - if ($grouped && $ungrouped) { - $errors[] = sprintf(_('Es müssen entweder alle Bedingungen Teil einer Gruppe sein, oder keine. %s Bedingungen sind keiner Gruppe zugeordnet.'), $ungrouped); - } elseif ($grouped && $quota_total !== 100) { - $errors[] = _('Die Gesamtsumme der Kontingente muss 100 Prozent betragen.'); - } elseif ($grouped && $no_quota) { - $errors[] = sprintf(_('Für %s Gruppen muss noch ein Kontingent festgelegt werden.'), $no_quota); - } - return $errors; - } - - public function getMessage($condition = null) - { - $message = parent::getMessage(); - if ($condition) { - return sprintf($message, $condition); - } else { - return $message; - } - } - - public function __clone() - { - $this->id = md5(uniqid(get_class($this))); - $this->courseSetId = null; - $cloned_conditions = []; - foreach ($this->ungrouped_conditions as $condition) { - $dolly = clone $condition; - $cloned_conditions[$dolly->id] = $dolly; - } - $this->ungrouped_conditions = $cloned_conditions; - $cloned_conditiongroups = []; - $cloned_quota = []; - foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { - $cloned_conditiongroup_id = md5(uniqid($conditiongroup_id)); - $cloned_quota[$cloned_conditiongroup_id] = $this->quota[$conditiongroup_id]; - foreach ($conditions as $condition) { - $dolly = clone $condition; - $cloned_conditiongroups[$cloned_conditiongroup_id][$dolly->id] = $dolly; - } - } - $this->conditiongroups = $cloned_conditiongroups; - $this->quota = $cloned_quota; - } - - public function setSiblings($siblings = []) - { - parent::setSiblings($siblings); - $this->conditiongroups_allowed = null; - } -} diff --git a/lib/admissionrules/conditionaladmission/ConditionalAdmission.php b/lib/admissionrules/conditionaladmission/ConditionalAdmission.php new file mode 100644 index 0000000..7fdbc00 --- /dev/null +++ b/lib/admissionrules/conditionaladmission/ConditionalAdmission.php @@ -0,0 +1,563 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class ConditionalAdmission extends AdmissionRule +{ + // --- ATTRIBUTES --- + + /** + * All conditions that must be fulfilled for successful admission. + */ + public $conditions = []; + + /** + * Grouped conditions that must be fulfilled for successful admission. + */ + public $conditiongroups = []; + + /** + * Conditions that must be fulfilled for successful admission. + */ + public $ungrouped_conditions = []; + + /** + * Quota for grouped conditions + */ + public $quota = []; + + /** + * Are condition groups allowed? + */ + public $conditiongroups_allowed = null; + + /** + * courseset siblings of this rule + */ + public $siblings = []; + + // --- OPERATIONS --- + + /** + * Standard constructor. + * + * @param String $ruleId If this rule has been saved previously, it + * will be loaded from database. + * @return ConditionalAdmission the current object (this). + */ + public function __construct($ruleId='', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + $this->default_message = _('Sie erfüllen nicht die Bedingung: %s'); + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('conditionaladmissions'); + } + return $this; + } + + /** + * Adds a new UserFilter to this rule. + * + * @param UserFilter $condition + * @param String $group + * @param Int $quota + * @return ConditionalAdmission + */ + public function addCondition($condition, $group = '', $quota = 0) + { + if ($group) { + $this->conditiongroups[$group][$condition->getId()] = $condition; + $this->quota[$group] = $quota; + } else { + $this->ungrouped_conditions[$condition->getId()] = $condition; + } + return $this; + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() + { + parent::delete(); + // Delete rule data. + $stmt = DBManager::get()->prepare("DELETE FROM `conditionaladmissions` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + // Delete all associated conditions... + foreach ($this->ungrouped_conditions as $condition) { + $condition->delete(); + } + foreach ($this->conditiongroups as $conditions) { + foreach ($conditions as $condition) { + $condition->delete(); + } + } + // ... and their connection to this rule. + $stmt = DBManager::get()->prepare("DELETE `admission_condition`, `admission_conditiongroup` FROM `admission_condition` + LEFT JOIN `admission_conditiongroup` USING( `conditiongroup_id`) + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Gets all users that are matched by thís rule. + * + * @return Array An array containing IDs of users who are matched by + * this rule. + */ + public function getAffectedUsers() + { + $users = []; + foreach ($this->ungrouped_conditions as $condition) { + $users = array_intersect($users, $condition->getAffectedUsers()); + } + foreach ($this->conditiongroups as $conditions) { + foreach ($conditions as $condition) { + $users = array_intersect($users, $condition->getAffectedUsers()); + } + } + return $users; + } + + /** + * Gets all defined conditions. + * + * @return Array + */ + public function getConditions() + { + return $this->conditions; + } + + /** + * Gets all grouped conditiongroups. + * + * @return Array + */ + public function getConditionGroups() + { + return $this->conditiongroups; + } + + /** + * Gets all grouped conditiongroups. + * + * @return Array + */ + public function getUngroupedConditions() + { + return $this->ungrouped_conditions; + } + + /** + * Gets quota for given conditiongroup. + * + * @return Array + */ + public function getQuota($group_id) + { + return $this->quota[$group_id]; + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() + { + return _('Über eine Menge von Bedingungen kann festgelegt werden, '. + 'wer zur Anmeldung zu den Veranstaltungen des Anmeldesets '. + 'zugelassen ist. Es muss nur eine der Bedingungen erfüllt sein, '. + 'innerhalb einer Bedingung müssen aber alle Datenfelder '. + 'zutreffen.'); + } + + /** + * Return this rule's name. + */ + public static function getName() + { + return _('Bedingte Anmeldung'); + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() + { + // Open generic admission rule template. + $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); + $tpl->set_attribute('rule', $this); + $factory = new Flexi\Factory(__DIR__ . '/templates/'); + // Now open specific template for this rule and insert base template. + $tpl2 = $factory->open('configure'); + $tpl2->set_attribute('rule', $this); + $tpl2->set_attribute('tpl', $tpl->render()); + return $tpl2->render(); + } + + /** + * Helper function for loading data from DB. Generic AdmissionRule data is + * loaded with the parent load() method. + */ + public function load() + { + // Load basic data. + $stmt = DBManager::get()->prepare("SELECT * + FROM `conditionaladmissions` WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->message = $current['message']; + $this->startTime = $current['start_time']; + $this->endTime = $current['end_time']; + // Retrieve conditions. + $stmt = DBManager::get()->prepare("SELECT * + FROM `admission_condition` LEFT JOIN `admission_conditiongroup` USING (`conditiongroup_id`) WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + $conditions = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($conditions as $condition) { + $currentCondition = new UserFilter($condition['filter_id']); + if ($condition['conditiongroup_id']) { + $this->conditiongroups[$condition['conditiongroup_id']][$condition['filter_id']] = $currentCondition; + $this->quota[$condition['conditiongroup_id']] = $condition['quota']; + } else { + $this->ungrouped_conditions[$condition['filter_id']] = $currentCondition; + } + } + } + } + + /** + * Checks if condition groups are allowed. + * + * @return Boolean + */ + public function conditiongroupsAllowed() + { + if ($this->conditiongroups_allowed === null) { + foreach ($this->getSiblings() as $rule) { + if (get_class($rule) == 'ParticipantRestrictedAdmission') { + if ($rule->getDistributionTime() > time()) { + $this->conditiongroups_allowed = true; + } + } + } + } + return $this->conditiongroups_allowed; + } + + /** + * Removes condition groups and sets all conditions as ungrouped. + * + */ + public function removeConditionGroups() + { + foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { + foreach ($conditions as $condition_id => $condition) { + $this->ungrouped_conditions[$condition_id] = $condition; + } + } + $this->conditiongroups = []; + } + + /** + * Removes the condition with the given ID from the rule. + * + * @param String $conditionId + * @return ConditionalAdmission + */ + public function removeCondition($conditionId) + { + if (isset($this->ungrouped_conditions[$conditionId])) { + $this->ungrouped_conditions[$conditionId]->delete(); + unset($this->ungrouped_conditions[$conditionId]); + } else { + foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { + if (isset($conditions[$conditionId])) { + $this->conditiongroups[$conditiongroup_id]->conditions[$conditionId]->delete(); + unset($this->conditiongroups[$conditiongroup_id]->conditions[$conditionId]); + } + } + } + return $this; + } + + /** + * Checks whether the given user fulfills the configured + * admission conditions. Only one of the conditions needs to be + * fulfilled (logical operator OR). The fields in a condition are + * in conjunction (logical operator AND). + * + * @param String $userId + * @param String $courseId + * @return Array Array with conditions that have failed. If array + * is empty, everything's all right. + */ + public function ruleApplies($userId, $courseId) + { + $failed = []; + // Check for rule validity time frame. + if ($this->checkTimeFrame()) { + // Check all configured conditions. + foreach ($this->ungrouped_conditions as $condition) { + if (!$condition->isFulfilled($userId)) { + $failed[] = $this->getMessage($condition->toString()); + } else { + $failed = []; + break; + } + } + foreach ($this->conditiongroups as $conditions) { + foreach ($conditions as $condition) { + if (!$condition->isFulfilled($userId)) { + $failed[] = $this->getMessage($condition->toString()); + } else { + $failed = []; + break 2; + } + } + } + } + return $failed; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) + { + UserFilterField::getAvailableFilterFields(); + parent::setAllData($data); + $this->conditions = []; + $this->ungrouped_conditions = []; + $this->conditiongroups = []; + $this->quota = []; + foreach ($data['conditions'] as $ser_con) { + $condition = ObjectBuilder::build($ser_con, 'UserFilter'); + $this->addCondition($condition, $data['conditiongroup_'.$condition->getId()], $data['quota_'.$data['conditiongroup_'.$condition->getId()]] ?? 0); + } + foreach ($this->getConditiongroups() as $conditiongroup_id => $conditions) { + if (mb_strlen($conditiongroup_id) < 32) { + $group = md5(uniqid('conditiongroups' . microtime(), true)); + + $this->conditiongroups[$group] = $this->conditiongroups[$conditiongroup_id]; + unset($this->conditiongroups[$conditiongroup_id]); + + $this->quota[$group] = $this->quota[$conditiongroup_id]; + unset($this->quota[$conditiongroup_id]); + } + } + if (count($this->getConditiongroups()) && $data['conditiongroups_allowed']) { + $this->conditiongroups_allowed = true; + } + + return $this; + } + + /** + * Helper function for storing data to DB. + */ + public function store() + { + // Store rule data. + $stmt = DBManager::get()->prepare("INSERT INTO `conditionaladmissions` + (`rule_id`, `message`, `start_time`, `end_time`, `mkdate`, `chdate`) + VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `message`=VALUES(`message`), + `start_time`=VALUES(`start_time`), + `end_time`=VALUES(`end_time`), + `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->message, (int)$this->startTime, + (int)$this->endTime, time(), time()]); + // prepare condition data. + $keys = array_keys($this->ungrouped_conditions); + foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { + $keys = array_merge($keys, array_keys($conditions)); + } + + // Delete removed conditions from DB. + $stmt = DBManager::get()->prepare("SELECT `filter_id`, `conditiongroup_id` FROM + `admission_condition` WHERE `rule_id`=? AND `filter_id` NOT IN ('". + implode("', '", $keys)."')"); + $stmt->execute([$this->id]); + $groups = []; + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $entry) { + $current = new UserFilter($entry['filter_id']); + $current->delete(); + $groups[] = $entry['conditiongroup_id']; + } + DBManager::get()->exec("DELETE FROM `admission_condition` + WHERE `rule_id`='".$this->id."' AND `filter_id` NOT IN ('". + implode("', '", $keys)."')"); + // Store all conditions. + $queries = []; + $parameters = []; + $groupqueries = []; + $groupparameters = []; + foreach ($this->ungrouped_conditions as $condition) { + // Store each ungrouped condition... + $condition->store(); + $queries[] = "(?, ?, ?, ?)"; + $parameters[] = $this->id; + $parameters[] = $condition->getId(); + $parameters[] = ''; + $parameters[] = time(); + } + + foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { + $groupqueries[] = "(?, ?)"; + $groupparameters[] = $conditiongroup_id; + $groupparameters[] = $this->quota[$conditiongroup_id]; + foreach ($conditions as $condition) { + // Store each group of conditions... + $condition->store(); + $queries[] = "(?, ?, ?, ?)"; + $parameters[] = $this->id; + $parameters[] = $condition->getId(); + $parameters[] = $conditiongroup_id; + $parameters[] = time(); + } + } + + // Store all assignments between rule and condition. + if (count($queries) > 0) { + $stmt = DBManager::get()->prepare("INSERT INTO `admission_condition` + (`rule_id`, `filter_id`, `conditiongroup_id`, `mkdate`) + VALUES " . implode(',', $queries) . " ON DUPLICATE KEY UPDATE + `filter_id`=VALUES(`filter_id`), `conditiongroup_id`=VALUES(`conditiongroup_id`)"); + $stmt->execute($parameters); + } + + // Store all assignments between condition and conditiongroup. + if (count($groupqueries) > 0) { + $stmt = DBManager::get()->prepare("INSERT INTO `admission_conditiongroup` + (`conditiongroup_id`, `quota`) + VALUES " . implode(',', $groupqueries) . " ON DUPLICATE KEY UPDATE + `quota`=VALUES(`quota`)"); + $stmt->execute($groupparameters); + } + + return $this; + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() + { + $factory = new Flexi\Factory(__DIR__ . '/templates/'); + $tpl = $factory->open('info'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array $data Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = parent::validate($data); + if (!$data['conditions'] || !is_array($data['conditions'])) { + $errors[] = _('Es muss mindestens eine Auswahlbedingung angegeben werden.'); + return $errors; + } + $quota = []; + $quota_total = 0; + $grouped = 0; + $ungrouped = 0; + $no_quota = 0; + foreach ($data['conditions'] as $ser_con) { + $condition = ObjectBuilder::build($ser_con, 'UserFilter'); + if ($data['conditiongroup_'.$condition->getId()]) { + $grouped++; + } else { + $ungrouped++; + } + $quota[$data['conditiongroup_' . $condition->getId()]] = $data['quota_' . $data['conditiongroup_' . $condition->getId()]] ?? 0; + if (!$quota[$data['conditiongroup_' . $condition->getId()]]) { + $no_quota++; + } + } + foreach ($quota as $part) { + $quota_total += $part; + } + if ($grouped && $ungrouped) { + $errors[] = sprintf(_('Es müssen entweder alle Bedingungen Teil einer Gruppe sein, oder keine. %s Bedingungen sind keiner Gruppe zugeordnet.'), $ungrouped); + } elseif ($grouped && $quota_total !== 100) { + $errors[] = _('Die Gesamtsumme der Kontingente muss 100 Prozent betragen.'); + } elseif ($grouped && $no_quota) { + $errors[] = sprintf(_('Für %s Gruppen muss noch ein Kontingent festgelegt werden.'), $no_quota); + } + return $errors; + } + + public function getMessage($condition = null) + { + $message = parent::getMessage(); + if ($condition) { + return sprintf($message, $condition); + } else { + return $message; + } + } + + public function __clone() + { + $this->id = md5(uniqid(get_class($this))); + $this->courseSetId = null; + $cloned_conditions = []; + foreach ($this->ungrouped_conditions as $condition) { + $dolly = clone $condition; + $cloned_conditions[$dolly->id] = $dolly; + } + $this->ungrouped_conditions = $cloned_conditions; + $cloned_conditiongroups = []; + $cloned_quota = []; + foreach ($this->conditiongroups as $conditiongroup_id => $conditions) { + $cloned_conditiongroup_id = md5(uniqid($conditiongroup_id)); + $cloned_quota[$cloned_conditiongroup_id] = $this->quota[$conditiongroup_id]; + foreach ($conditions as $condition) { + $dolly = clone $condition; + $cloned_conditiongroups[$cloned_conditiongroup_id][$dolly->id] = $dolly; + } + } + $this->conditiongroups = $cloned_conditiongroups; + $this->quota = $cloned_quota; + } + + public function setSiblings($siblings = []) + { + parent::setSiblings($siblings); + $this->conditiongroups_allowed = null; + } +} diff --git a/lib/admissionrules/coursememberadmission/CourseMemberAdmission.class.php b/lib/admissionrules/coursememberadmission/CourseMemberAdmission.class.php deleted file mode 100644 index a3309e8..0000000 --- a/lib/admissionrules/coursememberadmission/CourseMemberAdmission.class.php +++ /dev/null @@ -1,260 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class CourseMemberAdmission extends AdmissionRule -{ - const MODE_MUST_BE_IN_COURSES = 0; - const MODE_MAY_NOT_BE_IN_COURSES = 1; - // --- ATTRIBUTES --- - - /** - * End of course admission. - */ - public $courses_to_add = '[]'; - public $modus = ''; - - // --- OPERATIONS --- - - /** - * Standard constructor - * - * @param String $ruleId - * @param String e - */ - public function __construct($ruleId = '', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('coursememberadmissions'); - } - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() - { - parent::delete(); - - // Delete rule data. - DBManager::get()->execute( - "DELETE FROM `coursememberadmissions` WHERE `rule_id` = ?", - [$this->id] - ); - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() - { - return _("Anmelderegeln dieses Typs legen eine Veranstaltung fest, in der die Nutzer bereits eingetragen sein müssen, oder in der sie nicht eingetragen sein dürfen, um sich zu Veranstaltungen des Anmeldesets anmelden zu können."); - } - - /** - * Return this rule's name. - */ - public static function getName() - { - return _("Veranstaltungsbezogene Anmeldung"); - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() - { - // Open generic admission rule template. - $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); - $tpl->set_attribute('rule', $this); - - return $this->getTemplateFactory()->render('configure', [ - 'rule' => $this, - 'tpl' => $tpl->render(), - 'courses' => $this->getDecodedCourses(), - ]); - } - - /** - * Helper function for loading rule definition from database. - */ - public function load() - { - // Load data. - $stmt = DBManager::get()->prepare("SELECT * - FROM `coursememberadmissions` WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetchOne()) { - $this->message = $current['message']; - $this->startTime = $current['start_time']; - $this->endTime = $current['end_time']; - $this->courses_to_add = $current['courses']; - $this->modus = (int) $current['modus']; - } - } - - /** - * Is admission allowed according to the defined time frame? - * - * @param String $userId - * @param String $courseId - * @return Array - */ - public function ruleApplies($userId, $courseId) - { - $errors = []; - if ($this->checkTimeFrame()) { - $courses = $this->getDecodedCourses(); - foreach ($courses as $course) { - $is_member = CourseMember::exists([$course->id, $userId]); - - if (($this->modus == self::MODE_MUST_BE_IN_COURSES && !$is_member) - || ($this->modus == self::MODE_MAY_NOT_BE_IN_COURSES && $is_member) - ) { - $errors[] = $this->getMessage($course); - } - } - - // mode: "Mitgliedschaft ist in mindestens einer dieser Veranstaltungen notwendig" - if ($this->modus == self::MODE_MUST_BE_IN_COURSES && count($errors) < count($courses)) { - $errors = []; - } - } - - return $errors; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) - { - parent::setAllData($data); - - $this->modus = (int) $data['modus']; - $this->courses_to_add = json_encode(array_keys($data['courses_to_add'])); - return $this; - } - - /** - * Store rule definition to database. - */ - public function store() - { - // Store data. - $stmt = DBManager::get()->prepare("INSERT INTO `coursememberadmissions` - (`rule_id`, `message`, `courses`, `modus`, `start_time`, - `end_time`, `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE `start_time`=VALUES(`start_time`), - `end_time`=VALUES(`end_time`),message=VALUES(message),courses=VALUES(courses),modus=VALUES(modus), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->message, $this->courses_to_add, (int)$this->modus, (int)$this->startTime, - (int)$this->endTime, time(), time()]); - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() - { - return $this->getTemplateFactory()->render('info', [ - 'courses' => $this->getDecodedCourses(), - 'rule' => $this, - 'modus' => $this->modus, - ]); - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array $data Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = parent::validate($data); - if (!$data['courses_to_add']) { - $errors[] = _('Bitte wählen Sie eine Veranstaltung aus.'); - } - return $errors; - } - - public function getMessage($course = null) - { - $message = parent::getMessage(); - - if ($course) { - return sprintf($message, $course->getFullName('number-name')); - } else { - return $message; - } - } - - private function getDecodedCourses() - { - $decoded_courses = json_decode($this->courses_to_add, true); - if (!$decoded_courses) { - return []; - } - return Course::findMany($decoded_courses); - } - - public function getValidityPeriod(): string - { - if ($this->getStartTime() && $this->getEndTime()) { - return sprintf( - _('Diese Regel gilt von %s bis %s.'), - strftime('%d.%m.%Y %H:%M', $this->getStartTime()), - strftime('%d.%m.%Y %H:%M', $this->getEndTime()) - ); - } - - if ($this->getStartTime() && !$this->getEndTime()) { - return sprintf( - _('Diese Regel gilt ab %s.'), - strftime('%d.%m.%Y %H:%M', $this->getStartTime()) - ); - } - - if (!$this->getStartTime() && $this->getEndTime()) { - return sprintf( - _('Diese Regel gilt bis %s.'), - strftime('%d.%m.%Y %H:%M', $this->getEndTime()) - ); - } - - return ''; - } - - private function getTemplateFactory(): Flexi\Factory - { - return new Flexi\Factory(__DIR__ . '/templates/'); - } -} diff --git a/lib/admissionrules/coursememberadmission/CourseMemberAdmission.php b/lib/admissionrules/coursememberadmission/CourseMemberAdmission.php new file mode 100644 index 0000000..a3309e8 --- /dev/null +++ b/lib/admissionrules/coursememberadmission/CourseMemberAdmission.php @@ -0,0 +1,260 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class CourseMemberAdmission extends AdmissionRule +{ + const MODE_MUST_BE_IN_COURSES = 0; + const MODE_MAY_NOT_BE_IN_COURSES = 1; + // --- ATTRIBUTES --- + + /** + * End of course admission. + */ + public $courses_to_add = '[]'; + public $modus = ''; + + // --- OPERATIONS --- + + /** + * Standard constructor + * + * @param String $ruleId + * @param String e + */ + public function __construct($ruleId = '', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('coursememberadmissions'); + } + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() + { + parent::delete(); + + // Delete rule data. + DBManager::get()->execute( + "DELETE FROM `coursememberadmissions` WHERE `rule_id` = ?", + [$this->id] + ); + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() + { + return _("Anmelderegeln dieses Typs legen eine Veranstaltung fest, in der die Nutzer bereits eingetragen sein müssen, oder in der sie nicht eingetragen sein dürfen, um sich zu Veranstaltungen des Anmeldesets anmelden zu können."); + } + + /** + * Return this rule's name. + */ + public static function getName() + { + return _("Veranstaltungsbezogene Anmeldung"); + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() + { + // Open generic admission rule template. + $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); + $tpl->set_attribute('rule', $this); + + return $this->getTemplateFactory()->render('configure', [ + 'rule' => $this, + 'tpl' => $tpl->render(), + 'courses' => $this->getDecodedCourses(), + ]); + } + + /** + * Helper function for loading rule definition from database. + */ + public function load() + { + // Load data. + $stmt = DBManager::get()->prepare("SELECT * + FROM `coursememberadmissions` WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetchOne()) { + $this->message = $current['message']; + $this->startTime = $current['start_time']; + $this->endTime = $current['end_time']; + $this->courses_to_add = $current['courses']; + $this->modus = (int) $current['modus']; + } + } + + /** + * Is admission allowed according to the defined time frame? + * + * @param String $userId + * @param String $courseId + * @return Array + */ + public function ruleApplies($userId, $courseId) + { + $errors = []; + if ($this->checkTimeFrame()) { + $courses = $this->getDecodedCourses(); + foreach ($courses as $course) { + $is_member = CourseMember::exists([$course->id, $userId]); + + if (($this->modus == self::MODE_MUST_BE_IN_COURSES && !$is_member) + || ($this->modus == self::MODE_MAY_NOT_BE_IN_COURSES && $is_member) + ) { + $errors[] = $this->getMessage($course); + } + } + + // mode: "Mitgliedschaft ist in mindestens einer dieser Veranstaltungen notwendig" + if ($this->modus == self::MODE_MUST_BE_IN_COURSES && count($errors) < count($courses)) { + $errors = []; + } + } + + return $errors; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) + { + parent::setAllData($data); + + $this->modus = (int) $data['modus']; + $this->courses_to_add = json_encode(array_keys($data['courses_to_add'])); + return $this; + } + + /** + * Store rule definition to database. + */ + public function store() + { + // Store data. + $stmt = DBManager::get()->prepare("INSERT INTO `coursememberadmissions` + (`rule_id`, `message`, `courses`, `modus`, `start_time`, + `end_time`, `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE `start_time`=VALUES(`start_time`), + `end_time`=VALUES(`end_time`),message=VALUES(message),courses=VALUES(courses),modus=VALUES(modus), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->message, $this->courses_to_add, (int)$this->modus, (int)$this->startTime, + (int)$this->endTime, time(), time()]); + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() + { + return $this->getTemplateFactory()->render('info', [ + 'courses' => $this->getDecodedCourses(), + 'rule' => $this, + 'modus' => $this->modus, + ]); + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array $data Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = parent::validate($data); + if (!$data['courses_to_add']) { + $errors[] = _('Bitte wählen Sie eine Veranstaltung aus.'); + } + return $errors; + } + + public function getMessage($course = null) + { + $message = parent::getMessage(); + + if ($course) { + return sprintf($message, $course->getFullName('number-name')); + } else { + return $message; + } + } + + private function getDecodedCourses() + { + $decoded_courses = json_decode($this->courses_to_add, true); + if (!$decoded_courses) { + return []; + } + return Course::findMany($decoded_courses); + } + + public function getValidityPeriod(): string + { + if ($this->getStartTime() && $this->getEndTime()) { + return sprintf( + _('Diese Regel gilt von %s bis %s.'), + strftime('%d.%m.%Y %H:%M', $this->getStartTime()), + strftime('%d.%m.%Y %H:%M', $this->getEndTime()) + ); + } + + if ($this->getStartTime() && !$this->getEndTime()) { + return sprintf( + _('Diese Regel gilt ab %s.'), + strftime('%d.%m.%Y %H:%M', $this->getStartTime()) + ); + } + + if (!$this->getStartTime() && $this->getEndTime()) { + return sprintf( + _('Diese Regel gilt bis %s.'), + strftime('%d.%m.%Y %H:%M', $this->getEndTime()) + ); + } + + return ''; + } + + private function getTemplateFactory(): Flexi\Factory + { + return new Flexi\Factory(__DIR__ . '/templates/'); + } +} diff --git a/lib/admissionrules/limitedadmission/LimitedAdmission.class.php b/lib/admissionrules/limitedadmission/LimitedAdmission.class.php deleted file mode 100644 index a193bda..0000000 --- a/lib/admissionrules/limitedadmission/LimitedAdmission.class.php +++ /dev/null @@ -1,284 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class LimitedAdmission extends AdmissionRule -{ - // --- ATTRIBUTES --- - - - /** - * Maximal number of courses that a user can register for. - */ - public $maxNumber = 1; - - // --- OPERATIONS --- - - /** - * Standard constructor. - * - * @param String ruleId - * @return LimitedAdmission - */ - public function __construct($ruleId='', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - $this->default_message = _('Sie haben sich bereits zur maximalen Anzahl von %s Veranstaltungen angemeldet.'); - - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('limitedadmissions'); - } - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() { - parent::delete(); - // Delete rule data. - $stmt = DBManager::get()->prepare("DELETE FROM `limitedadmissions` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - // Delete all custom max numbers. - $stmt = DBManager::get()->prepare("DELETE FROM `userlimits` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Users can specify their own maximal number of courses they want - * to be registered for. This method gets the specified value for the - * given user or the max number that has been specified by the rule if no - * custom number was set. - * - * @param userId - * @return Integer - */ - public function getCustomMaxNumber($userId) - { - // Initially we use the number given per admission rule. - $maxNumber = $this->maxNumber; - $stmt = DBManager::get()->prepare("SELECT `maxnumber` - FROM `userlimits` WHERE rule_id=? AND user_id=?"); - $stmt->execute([$this->id, $userId]); - // The user has given some custom number. - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - // Custom number must be smaller than rule max number. - $maxNumber = min($maxNumber, $current['maxnumber']); - } - return $maxNumber; - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() { - return _("Diese Art von Anmelderegel legt eine Maximalzahl von ". - "Veranstaltungen fest, an denen Nutzer im aktuellen ". - "Anmeldeset teilnehmen können."); - } - - /** - * Gets the maximal number of courses that users can be registered for. - * - * @return Integer - */ - public function getMaxNumber() - { - return (int)$this->maxNumber; - } - - public function getMaxNumberForUser($userId) - { - return min($this->maxNumber, $this->getCustomMaxNumber($userId)); - } - - - /** - * Return this rule's name. - */ - public static function getName() { - return _("Anmeldung zu maximal n Veranstaltungen"); - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() { - // Open generic admission rule template. - $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); - $tpl->set_attribute('rule', $this); - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - // Now open specific template for this rule and insert base template. - $tpl2 = $factory->open('configure'); - $tpl2->set_attribute('rule', $this); - $tpl2->set_attribute('tpl', $tpl->render()); - return $tpl2->render(); - } - - /** - * Internal helper function for loading rule definition from database. - */ - public function load() { - $stmt = DBManager::get()->prepare("SELECT * - FROM `limitedadmissions` WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->message = $current['message']; - $this->startTime = $current['start_time']; - $this->endTime = $current['end_time']; - $this->maxNumber = $current['maxnumber']; - } - } - - /** - * Does the current rule allow the given user to register as participant - * in the given course? That only happens when the user has no more than - * the given number of registrations at the other courses in the course set. - * - * @param String userId - * @param String courseId - * @return Array Any errors that occurred on admission. - */ - public function ruleApplies($userId, $courseId) - { - $errors = []; - // Check for rule validity time frame. - if ($this->checkTimeFrame()) { - // How many courses from this set has the user already registered for? - $db = DBManager::get(); - $number = $db->fetchColumn("SELECT COUNT(*) - FROM `seminar_user` WHERE `user_id`=? AND `status` IN ('user', 'autor') AND `Seminar_id` IN ( - SELECT `Seminar_id` FROM `seminar_courseset` WHERE `set_id`=?)", [$userId, $this->courseSetId]); - $number += $db->fetchColumn("SELECT COUNT(*) - FROM `admission_seminar_user` WHERE `user_id`=? AND `Seminar_id` IN ( - SELECT `Seminar_id` FROM `seminar_courseset` WHERE `set_id`=?)", [$userId, $this->courseSetId]); - // Check if the number is smaller than admission rule limit - if (!($number < - $this->getMaxNumber())) { - $errors[] = $this->getMessage($this->getMaxNumber()); - } - } - return $errors; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) { - parent::setAllData($data); - $this->maxNumber = intval($data['maxnumber']); - return $this; - } - - /** - * Sets a new maximal number of courses that the given user can - * register for. - * - * @param String userId - * @param Integer maxNumber - * @return LimitedAdmission - */ - public function setCustomMaxNumber($userId, $maxNumber) - { - $stmt = DBManager::get()->prepare("INSERT INTO `userlimits` - (`rule_id`, `user_id`, `maxnumber`, `mkdate`, `chdate`) - VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `maxnumber`=VALUES(`maxnumber`), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $userId, - min($this->maxNumber, $maxNumber), time(), time()]); - return $this; - } - - /** - * Sets a new maximal number of courses for registration of the same user. - * - * @param Integer newMaxNumber - * @return LimitedAdmission - */ - public function setMaxNumber($newMaxNumber) - { - $this->maxNumber = $newMaxNumber; - return $this; - } - - /** - * Helper function for storing data to DB. - */ - public function store() { - // Store data. - $stmt = DBManager::get()->prepare("INSERT INTO `limitedadmissions` - (`rule_id`, `message`, `start_time`, `end_time`, `maxnumber`, - `mkdate`, `chdate`) - VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `message`=VALUES(`message`), `start_time`=VALUES(`start_time`), - `end_time`=VALUES(`end_time`), `maxnumber`=VALUES(`maxnumber`), - `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->message, (int)$this->startTime, - (int)$this->endTime, $this->maxNumber, time(), time()]); - return $this; - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - $tpl = $factory->open('info'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = parent::validate($data); - if (!$data['maxnumber']) { - $errors[] = _('Bitte geben Sie die maximale Anzahl erlaubter Anmeldungen an.'); - } - return $errors; - } - - public function getMessage($max_number = null) - { - $message = parent::getMessage(); - if (isset($max_number)) { - return sprintf($message, $max_number); - } else { - return $message; - } - } -} /* end of class LimitedAdmission */ - -?> diff --git a/lib/admissionrules/limitedadmission/LimitedAdmission.php b/lib/admissionrules/limitedadmission/LimitedAdmission.php new file mode 100644 index 0000000..a193bda --- /dev/null +++ b/lib/admissionrules/limitedadmission/LimitedAdmission.php @@ -0,0 +1,284 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class LimitedAdmission extends AdmissionRule +{ + // --- ATTRIBUTES --- + + + /** + * Maximal number of courses that a user can register for. + */ + public $maxNumber = 1; + + // --- OPERATIONS --- + + /** + * Standard constructor. + * + * @param String ruleId + * @return LimitedAdmission + */ + public function __construct($ruleId='', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + $this->default_message = _('Sie haben sich bereits zur maximalen Anzahl von %s Veranstaltungen angemeldet.'); + + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('limitedadmissions'); + } + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() { + parent::delete(); + // Delete rule data. + $stmt = DBManager::get()->prepare("DELETE FROM `limitedadmissions` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + // Delete all custom max numbers. + $stmt = DBManager::get()->prepare("DELETE FROM `userlimits` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Users can specify their own maximal number of courses they want + * to be registered for. This method gets the specified value for the + * given user or the max number that has been specified by the rule if no + * custom number was set. + * + * @param userId + * @return Integer + */ + public function getCustomMaxNumber($userId) + { + // Initially we use the number given per admission rule. + $maxNumber = $this->maxNumber; + $stmt = DBManager::get()->prepare("SELECT `maxnumber` + FROM `userlimits` WHERE rule_id=? AND user_id=?"); + $stmt->execute([$this->id, $userId]); + // The user has given some custom number. + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + // Custom number must be smaller than rule max number. + $maxNumber = min($maxNumber, $current['maxnumber']); + } + return $maxNumber; + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() { + return _("Diese Art von Anmelderegel legt eine Maximalzahl von ". + "Veranstaltungen fest, an denen Nutzer im aktuellen ". + "Anmeldeset teilnehmen können."); + } + + /** + * Gets the maximal number of courses that users can be registered for. + * + * @return Integer + */ + public function getMaxNumber() + { + return (int)$this->maxNumber; + } + + public function getMaxNumberForUser($userId) + { + return min($this->maxNumber, $this->getCustomMaxNumber($userId)); + } + + + /** + * Return this rule's name. + */ + public static function getName() { + return _("Anmeldung zu maximal n Veranstaltungen"); + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() { + // Open generic admission rule template. + $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); + $tpl->set_attribute('rule', $this); + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + // Now open specific template for this rule and insert base template. + $tpl2 = $factory->open('configure'); + $tpl2->set_attribute('rule', $this); + $tpl2->set_attribute('tpl', $tpl->render()); + return $tpl2->render(); + } + + /** + * Internal helper function for loading rule definition from database. + */ + public function load() { + $stmt = DBManager::get()->prepare("SELECT * + FROM `limitedadmissions` WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->message = $current['message']; + $this->startTime = $current['start_time']; + $this->endTime = $current['end_time']; + $this->maxNumber = $current['maxnumber']; + } + } + + /** + * Does the current rule allow the given user to register as participant + * in the given course? That only happens when the user has no more than + * the given number of registrations at the other courses in the course set. + * + * @param String userId + * @param String courseId + * @return Array Any errors that occurred on admission. + */ + public function ruleApplies($userId, $courseId) + { + $errors = []; + // Check for rule validity time frame. + if ($this->checkTimeFrame()) { + // How many courses from this set has the user already registered for? + $db = DBManager::get(); + $number = $db->fetchColumn("SELECT COUNT(*) + FROM `seminar_user` WHERE `user_id`=? AND `status` IN ('user', 'autor') AND `Seminar_id` IN ( + SELECT `Seminar_id` FROM `seminar_courseset` WHERE `set_id`=?)", [$userId, $this->courseSetId]); + $number += $db->fetchColumn("SELECT COUNT(*) + FROM `admission_seminar_user` WHERE `user_id`=? AND `Seminar_id` IN ( + SELECT `Seminar_id` FROM `seminar_courseset` WHERE `set_id`=?)", [$userId, $this->courseSetId]); + // Check if the number is smaller than admission rule limit + if (!($number < + $this->getMaxNumber())) { + $errors[] = $this->getMessage($this->getMaxNumber()); + } + } + return $errors; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) { + parent::setAllData($data); + $this->maxNumber = intval($data['maxnumber']); + return $this; + } + + /** + * Sets a new maximal number of courses that the given user can + * register for. + * + * @param String userId + * @param Integer maxNumber + * @return LimitedAdmission + */ + public function setCustomMaxNumber($userId, $maxNumber) + { + $stmt = DBManager::get()->prepare("INSERT INTO `userlimits` + (`rule_id`, `user_id`, `maxnumber`, `mkdate`, `chdate`) + VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `maxnumber`=VALUES(`maxnumber`), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $userId, + min($this->maxNumber, $maxNumber), time(), time()]); + return $this; + } + + /** + * Sets a new maximal number of courses for registration of the same user. + * + * @param Integer newMaxNumber + * @return LimitedAdmission + */ + public function setMaxNumber($newMaxNumber) + { + $this->maxNumber = $newMaxNumber; + return $this; + } + + /** + * Helper function for storing data to DB. + */ + public function store() { + // Store data. + $stmt = DBManager::get()->prepare("INSERT INTO `limitedadmissions` + (`rule_id`, `message`, `start_time`, `end_time`, `maxnumber`, + `mkdate`, `chdate`) + VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `message`=VALUES(`message`), `start_time`=VALUES(`start_time`), + `end_time`=VALUES(`end_time`), `maxnumber`=VALUES(`maxnumber`), + `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->message, (int)$this->startTime, + (int)$this->endTime, $this->maxNumber, time(), time()]); + return $this; + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + $tpl = $factory->open('info'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = parent::validate($data); + if (!$data['maxnumber']) { + $errors[] = _('Bitte geben Sie die maximale Anzahl erlaubter Anmeldungen an.'); + } + return $errors; + } + + public function getMessage($max_number = null) + { + $message = parent::getMessage(); + if (isset($max_number)) { + return sprintf($message, $max_number); + } else { + return $message; + } + } +} /* end of class LimitedAdmission */ + +?> diff --git a/lib/admissionrules/lockedadmission/LockedAdmission.class.php b/lib/admissionrules/lockedadmission/LockedAdmission.class.php deleted file mode 100644 index 0ca324b..0000000 --- a/lib/admissionrules/lockedadmission/LockedAdmission.class.php +++ /dev/null @@ -1,136 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class LockedAdmission extends AdmissionRule -{ - // --- ATTRIBUTES --- - - // --- OPERATIONS --- - - /** - * Standard constructor. - * - * @param String ruleId - */ - public function __construct($ruleId='', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - $this->default_message = _('Die Anmeldung ist gesperrt.'); - - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('lockedadmissions'); - } - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() { - parent::delete(); - // Delete rule data. - $stmt = DBManager::get()->prepare("DELETE FROM `lockedadmissions` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() { - return _("Diese Art von Anmelderegel sperrt die Anmeldung ". - "zu allen zugehörigen Veranstaltungen, sodass sich niemand ". - "eintragen kann."); - } - - /** - * Return this rule's name. - */ - public static function getName() { - return _("Anmeldung gesperrt"); - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - // Now open specific template for this rule and insert base template. - $tpl = $factory->open('configure'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Internal helper function for loading rule definition from database. - */ - public function load() { - $stmt = DBManager::get()->prepare("SELECT * FROM `lockedadmissions` - WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->message = $current['message']; - } - } - - /** - * Does the current rule allow the given user to register as participant - * in the given course? Never happens here as admission is completely - * locked. - * - * @param String userId - * @param String courseId - * @return Array Any errors that occurred on admission. - */ - public function ruleApplies($userId, $courseId) - { - // YOU CANNOT PASS! - return [$this->getMessage()]; - } - - /** - * Helper function for storing data to DB. - */ - public function store() { - // Store data. - $stmt = DBManager::get()->prepare("INSERT INTO `lockedadmissions` - (`rule_id`, `message`, `mkdate`, `chdate`) - VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `message`=VALUES(`message`), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->message, time(), time()]); - return $this; - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - $tpl = $factory->open('info'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - -} /* end of class LockedAdmission */ - -?> diff --git a/lib/admissionrules/lockedadmission/LockedAdmission.php b/lib/admissionrules/lockedadmission/LockedAdmission.php new file mode 100644 index 0000000..0ca324b --- /dev/null +++ b/lib/admissionrules/lockedadmission/LockedAdmission.php @@ -0,0 +1,136 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class LockedAdmission extends AdmissionRule +{ + // --- ATTRIBUTES --- + + // --- OPERATIONS --- + + /** + * Standard constructor. + * + * @param String ruleId + */ + public function __construct($ruleId='', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + $this->default_message = _('Die Anmeldung ist gesperrt.'); + + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('lockedadmissions'); + } + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() { + parent::delete(); + // Delete rule data. + $stmt = DBManager::get()->prepare("DELETE FROM `lockedadmissions` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() { + return _("Diese Art von Anmelderegel sperrt die Anmeldung ". + "zu allen zugehörigen Veranstaltungen, sodass sich niemand ". + "eintragen kann."); + } + + /** + * Return this rule's name. + */ + public static function getName() { + return _("Anmeldung gesperrt"); + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + // Now open specific template for this rule and insert base template. + $tpl = $factory->open('configure'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Internal helper function for loading rule definition from database. + */ + public function load() { + $stmt = DBManager::get()->prepare("SELECT * FROM `lockedadmissions` + WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->message = $current['message']; + } + } + + /** + * Does the current rule allow the given user to register as participant + * in the given course? Never happens here as admission is completely + * locked. + * + * @param String userId + * @param String courseId + * @return Array Any errors that occurred on admission. + */ + public function ruleApplies($userId, $courseId) + { + // YOU CANNOT PASS! + return [$this->getMessage()]; + } + + /** + * Helper function for storing data to DB. + */ + public function store() { + // Store data. + $stmt = DBManager::get()->prepare("INSERT INTO `lockedadmissions` + (`rule_id`, `message`, `mkdate`, `chdate`) + VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `message`=VALUES(`message`), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->message, time(), time()]); + return $this; + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + $tpl = $factory->open('info'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + +} /* end of class LockedAdmission */ + +?> diff --git a/lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.class.php b/lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.class.php deleted file mode 100644 index 28a910d..0000000 --- a/lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.class.php +++ /dev/null @@ -1,230 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class ParticipantRestrictedAdmission extends AdmissionRule -{ - // --- ATTRIBUTES --- - - /** - * Timestamp for execution of seat distribution algorithm - */ - public $distributionTime = null; - - public $first_come_first_served_allowed = false; - - public $minimum_timespan_to_distribution_time = 120; - - public $prio_exists = false; - - - // --- OPERATIONS --- - - /** - * Standard constructor - * - * @param String $ruleId - * @param String $courseSetId - */ - public function __construct($ruleId = '', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - $this->first_come_first_served_allowed = (bool)Config::get()->ENABLE_COURSESET_FCFS; - $this->default_message = _('Es stehen keine weiteren Plätze zur Verfügung.'); - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('participantrestrictedadmissions'); - } - } - - public function isFCFSallowed() - { - return $this->first_come_first_served_allowed; - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() - { - if ($this->prio_exists) { - $set_id = DBManager::get()->fetchColumn("SELECT set_id FROM courseset_rule WHERE rule_id = ? LIMIT 1", [$this->id]); - //Delete priorities - AdmissionPriority::unsetAllPriorities($set_id); - } - parent::delete(); - // Delete rule data. - $stmt = DBManager::get()->prepare("DELETE FROM `participantrestrictedadmissions` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() - { - return _("Anmelderegeln dieses Typs legen fest, ob die zugeordneten Veranstaltungen eine maximale Teilnehmendenanzahl haben. Die Platzverteilung erfolgt automatisiert."); - } - - /** - * Gets the time for seat distribution algorithm. - * - * @return int - */ - public function getDistributionTime() - { - return $this->distributionTime; - } - - /** - * Return this rule's name. - */ - public static function getName() - { - return _("Beschränkte Teilnehmendenanzahl"); - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() - { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - // Open specific template for this rule and insert base template. - $tpl = $factory->open('configure'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Helper function for loading rule definition from database. - */ - public function load() - { - // Load data. - $stmt = DBManager::get()->prepare("SELECT * - FROM `participantrestrictedadmissions` WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->message = $current['message']; - $this->distributionTime = $current['distribution_time']; - if ($current['distribution_time'] > 0) { - $this->prio_exists = DBManager::get()->fetchColumn("SELECT 1 FROM courseset_rule INNER JOIN priorities USING(set_id) WHERE rule_id = ? LIMIT 1", [$this->id]); - } - } - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) - { - parent::setAllData($data); - if (!empty($data['distributiondate'])) { - if (!$data['distributiontime']) { - $data['distributiontime'] = '23:59'; - } - $ddate = strtotime($data['distributiondate'] . ' ' . $data['distributiontime']); - $this->setDistributionTime($ddate); - } - if (!empty($data['enable_FCFS'])) { - $this->setDistributionTime(0); - } - if (!empty($data['startdate'])) { - $starttime = strtotime($data['startdate'] . ' ' . $data['starttime']); - if ($starttime > time()) { - $this->minimum_timespan_to_distribution_time = $this->minimum_timespan_to_distribution_time + (($starttime - time()) / 60); - } - } - - return $this; - } - - /** - * Sets a new timestamp for seat distribution algorithm execution. - * - * @param int $newDistributionTime - * @return ParticipantRestrictedAdmission - */ - public function setDistributionTime($newDistributionTime) - { - $this->distributionTime = $newDistributionTime; - return $this; - } - - - /** - * Store rule definition to database. - */ - public function store() - { - // Store data. - $stmt = DBManager::get()->prepare("INSERT INTO `participantrestrictedadmissions` - (`rule_id`, `message`, `distribution_time`, - `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - `distribution_time`=VALUES(`distribution_time`), - message=VALUES(message), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, (string)$this->message, - (int)$this->distributionTime, time(), time()]); - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() - { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - $tpl = $factory->open('info'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array $data Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = parent::validate($data); - if (!$data['distributiontime']) { - $data['distributiontime'] = '23:59'; - } - $ddate = strtotime($data['distributiondate'] . ' ' . $data['distributiontime']); - if (empty($data['enable_FCFS']) && (empty($data['distributiondate']) || $ddate < (time() + $this->minimum_timespan_to_distribution_time * 60))) { - $errors[] = sprintf(_('Bitte geben Sie für die Platzverteilung ein Datum an, das weiter in der Zukunft liegt. Das frühestmögliche Datum ist %s.'), strftime('%x %R', time() + $this->minimum_timespan_to_distribution_time*60)); - } - if (!empty($data['enable_FCFS']) && $data['distributiondate']) { - $errors[] = _('Sie können kein Datum für die automatische Platzverteilung einstellen und gleichzeitig die automatische Platzverteilung ausschalten.'); - } - return $errors; - } -} diff --git a/lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.php b/lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.php new file mode 100644 index 0000000..28a910d --- /dev/null +++ b/lib/admissionrules/participantrestrictedadmission/ParticipantRestrictedAdmission.php @@ -0,0 +1,230 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class ParticipantRestrictedAdmission extends AdmissionRule +{ + // --- ATTRIBUTES --- + + /** + * Timestamp for execution of seat distribution algorithm + */ + public $distributionTime = null; + + public $first_come_first_served_allowed = false; + + public $minimum_timespan_to_distribution_time = 120; + + public $prio_exists = false; + + + // --- OPERATIONS --- + + /** + * Standard constructor + * + * @param String $ruleId + * @param String $courseSetId + */ + public function __construct($ruleId = '', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + $this->first_come_first_served_allowed = (bool)Config::get()->ENABLE_COURSESET_FCFS; + $this->default_message = _('Es stehen keine weiteren Plätze zur Verfügung.'); + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('participantrestrictedadmissions'); + } + } + + public function isFCFSallowed() + { + return $this->first_come_first_served_allowed; + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() + { + if ($this->prio_exists) { + $set_id = DBManager::get()->fetchColumn("SELECT set_id FROM courseset_rule WHERE rule_id = ? LIMIT 1", [$this->id]); + //Delete priorities + AdmissionPriority::unsetAllPriorities($set_id); + } + parent::delete(); + // Delete rule data. + $stmt = DBManager::get()->prepare("DELETE FROM `participantrestrictedadmissions` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() + { + return _("Anmelderegeln dieses Typs legen fest, ob die zugeordneten Veranstaltungen eine maximale Teilnehmendenanzahl haben. Die Platzverteilung erfolgt automatisiert."); + } + + /** + * Gets the time for seat distribution algorithm. + * + * @return int + */ + public function getDistributionTime() + { + return $this->distributionTime; + } + + /** + * Return this rule's name. + */ + public static function getName() + { + return _("Beschränkte Teilnehmendenanzahl"); + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() + { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + // Open specific template for this rule and insert base template. + $tpl = $factory->open('configure'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Helper function for loading rule definition from database. + */ + public function load() + { + // Load data. + $stmt = DBManager::get()->prepare("SELECT * + FROM `participantrestrictedadmissions` WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->message = $current['message']; + $this->distributionTime = $current['distribution_time']; + if ($current['distribution_time'] > 0) { + $this->prio_exists = DBManager::get()->fetchColumn("SELECT 1 FROM courseset_rule INNER JOIN priorities USING(set_id) WHERE rule_id = ? LIMIT 1", [$this->id]); + } + } + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) + { + parent::setAllData($data); + if (!empty($data['distributiondate'])) { + if (!$data['distributiontime']) { + $data['distributiontime'] = '23:59'; + } + $ddate = strtotime($data['distributiondate'] . ' ' . $data['distributiontime']); + $this->setDistributionTime($ddate); + } + if (!empty($data['enable_FCFS'])) { + $this->setDistributionTime(0); + } + if (!empty($data['startdate'])) { + $starttime = strtotime($data['startdate'] . ' ' . $data['starttime']); + if ($starttime > time()) { + $this->minimum_timespan_to_distribution_time = $this->minimum_timespan_to_distribution_time + (($starttime - time()) / 60); + } + } + + return $this; + } + + /** + * Sets a new timestamp for seat distribution algorithm execution. + * + * @param int $newDistributionTime + * @return ParticipantRestrictedAdmission + */ + public function setDistributionTime($newDistributionTime) + { + $this->distributionTime = $newDistributionTime; + return $this; + } + + + /** + * Store rule definition to database. + */ + public function store() + { + // Store data. + $stmt = DBManager::get()->prepare("INSERT INTO `participantrestrictedadmissions` + (`rule_id`, `message`, `distribution_time`, + `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + `distribution_time`=VALUES(`distribution_time`), + message=VALUES(message), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, (string)$this->message, + (int)$this->distributionTime, time(), time()]); + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() + { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + $tpl = $factory->open('info'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array $data Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = parent::validate($data); + if (!$data['distributiontime']) { + $data['distributiontime'] = '23:59'; + } + $ddate = strtotime($data['distributiondate'] . ' ' . $data['distributiontime']); + if (empty($data['enable_FCFS']) && (empty($data['distributiondate']) || $ddate < (time() + $this->minimum_timespan_to_distribution_time * 60))) { + $errors[] = sprintf(_('Bitte geben Sie für die Platzverteilung ein Datum an, das weiter in der Zukunft liegt. Das frühestmögliche Datum ist %s.'), strftime('%x %R', time() + $this->minimum_timespan_to_distribution_time*60)); + } + if (!empty($data['enable_FCFS']) && $data['distributiondate']) { + $errors[] = _('Sie können kein Datum für die automatische Platzverteilung einstellen und gleichzeitig die automatische Platzverteilung ausschalten.'); + } + return $errors; + } +} diff --git a/lib/admissionrules/passwordadmission/PasswordAdmission.class.php b/lib/admissionrules/passwordadmission/PasswordAdmission.class.php deleted file mode 100644 index 0650630..0000000 --- a/lib/admissionrules/passwordadmission/PasswordAdmission.class.php +++ /dev/null @@ -1,241 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once 'vendor/phpass/PasswordHash.php'; - -class PasswordAdmission extends AdmissionRule -{ - // --- ATTRIBUTES --- - /* - * Password hasher (phpass library) - */ - public $hasher = null; - - /* - * Crypted password. - */ - public $password = ''; - - // --- OPERATIONS --- - - public $new = false; - /** - * Standard constructor. - * - * @param String ruleId - */ - public function __construct($ruleId='', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - $this->default_message = _('Das eingegebene Passwort ist falsch.'); - // Create a new bcrypt password hasher (exclude weaker algorithms). - $this->hasher = new PasswordHash(8, false); - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('passwordadmissions'); - $this->new = true; - } - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() { - parent::delete(); - // Delete rule data. - $stmt = DBManager::get()->prepare("DELETE FROM `passwordadmissions` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() { - return _("Mit dieser Anmelderegel können Sie ein Passwort für Zugang ". - "zu den zugeordneten Veranstaltungen vergeben. Die Anmeldung ist ". - "dann nur für Personen möglich, die dieses Passwort kennen."); - } - - /** - * Shows an input form where the user can enter a password and try to get - * past the holy gates. - * - * @return String A template-based input form. - */ - public function getInput() { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - $tpl = $factory->open('input'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Return this rule's name. - */ - public static function getName() { - return _("Anmeldung mit Passwort"); - } - - /** - * Gets the bcrypted hash of the current password. - * - * @return String - */ - public function getPassword() { - return $this->password; - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() { - // Open generic admission rule template. - $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); - $tpl->set_attribute('rule', $this); - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - // Now open specific template for this rule and insert base template. - $tpl2 = $factory->open('configure'); - $tpl2->set_attribute('rule', $this); - $tpl2->set_attribute('tpl', $tpl->render()); - return $tpl2->render(); - } - - /** - * Internal helper function for loading rule definition from database. - */ - public function load() { - $stmt = DBManager::get()->prepare("SELECT * FROM `passwordadmissions` - WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->message = $current['message']; - $this->startTime = $current['start_time']; - $this->endTime = $current['end_time']; - $this->password = $current['password']; - } - } - - /** - * Does the current rule allow the given user to register as participant - * in the given course? Here, a given password (via the getInput method) is - * compared to the stored encrypted one. - * - * @param String userId - * @param String courseId - * @return array - */ - public function ruleApplies($userId, $courseId) - { - $errors = []; - if ($this->checkTimeFrame()) { - if (Request::get('pwarule_password') === null) { - $errors[] = _('Die Eingabe eines Passwortes ist erforderlich.'); - } else { - CSRFProtection::verifyUnsafeRequest(); - $pwcheck = $this->hasher->CheckPassword(Request::get('pwarule_password'), - $this->getPassword()); - //migrated passwords - $pwcheck_m = $this->hasher->CheckPassword(md5(Request::get('pwarule_password')), - $this->getPassword()); - if (!($pwcheck || $pwcheck_m)) { - $errors[] = $this->getMessage(); - } - } - } - return $errors; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) { - parent::setAllData($data); - if ($this->new || $data['password1'] !== '') { - $this->setPassword($data['password1']); - } - return $this; - } - - /** - * Sets the password by bcrypting the given clear text password. - * - * @param String $clearText The clear text password to be set. - * @return PasswordAdmission - */ - public function setPassword($clearText) { - $this->password = $this->hasher->HashPassword($clearText); - return $this; - } - - /** - * Helper function for storing data to DB. - */ - public function store() { - // Store data. - $stmt = DBManager::get()->prepare("INSERT INTO `passwordadmissions` - (`rule_id`, `message`, `start_time`, `end_time`, `password`, - `mkdate`, `chdate`) - VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `message`=VALUES(`message`), `start_time`=VALUES(`start_time`), - `end_time`=VALUES(`end_time`), `password`=VALUES(`password`), - `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->message, (int)$this->startTime, (int)$this->endTime, $this->password, - time(), time()]); - return $this; - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - $tpl = $factory->open('info'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = parent::validate($data); - if ($data['password1'] === '' && $this->new) { - $errors[] = _('Das Passwort darf nicht leer sein.'); - } - if ($data['password1'] !== $data['password2']) { - $errors[] = _('Das Passwort stimmt nicht mit der Wiederholung überein.'); - } - return $errors; - } -} diff --git a/lib/admissionrules/passwordadmission/PasswordAdmission.php b/lib/admissionrules/passwordadmission/PasswordAdmission.php new file mode 100644 index 0000000..0650630 --- /dev/null +++ b/lib/admissionrules/passwordadmission/PasswordAdmission.php @@ -0,0 +1,241 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +require_once 'vendor/phpass/PasswordHash.php'; + +class PasswordAdmission extends AdmissionRule +{ + // --- ATTRIBUTES --- + /* + * Password hasher (phpass library) + */ + public $hasher = null; + + /* + * Crypted password. + */ + public $password = ''; + + // --- OPERATIONS --- + + public $new = false; + /** + * Standard constructor. + * + * @param String ruleId + */ + public function __construct($ruleId='', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + $this->default_message = _('Das eingegebene Passwort ist falsch.'); + // Create a new bcrypt password hasher (exclude weaker algorithms). + $this->hasher = new PasswordHash(8, false); + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('passwordadmissions'); + $this->new = true; + } + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() { + parent::delete(); + // Delete rule data. + $stmt = DBManager::get()->prepare("DELETE FROM `passwordadmissions` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() { + return _("Mit dieser Anmelderegel können Sie ein Passwort für Zugang ". + "zu den zugeordneten Veranstaltungen vergeben. Die Anmeldung ist ". + "dann nur für Personen möglich, die dieses Passwort kennen."); + } + + /** + * Shows an input form where the user can enter a password and try to get + * past the holy gates. + * + * @return String A template-based input form. + */ + public function getInput() { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + $tpl = $factory->open('input'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Return this rule's name. + */ + public static function getName() { + return _("Anmeldung mit Passwort"); + } + + /** + * Gets the bcrypted hash of the current password. + * + * @return String + */ + public function getPassword() { + return $this->password; + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() { + // Open generic admission rule template. + $tpl = $GLOBALS['template_factory']->open('admission/rules/configure'); + $tpl->set_attribute('rule', $this); + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + // Now open specific template for this rule and insert base template. + $tpl2 = $factory->open('configure'); + $tpl2->set_attribute('rule', $this); + $tpl2->set_attribute('tpl', $tpl->render()); + return $tpl2->render(); + } + + /** + * Internal helper function for loading rule definition from database. + */ + public function load() { + $stmt = DBManager::get()->prepare("SELECT * FROM `passwordadmissions` + WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->message = $current['message']; + $this->startTime = $current['start_time']; + $this->endTime = $current['end_time']; + $this->password = $current['password']; + } + } + + /** + * Does the current rule allow the given user to register as participant + * in the given course? Here, a given password (via the getInput method) is + * compared to the stored encrypted one. + * + * @param String userId + * @param String courseId + * @return array + */ + public function ruleApplies($userId, $courseId) + { + $errors = []; + if ($this->checkTimeFrame()) { + if (Request::get('pwarule_password') === null) { + $errors[] = _('Die Eingabe eines Passwortes ist erforderlich.'); + } else { + CSRFProtection::verifyUnsafeRequest(); + $pwcheck = $this->hasher->CheckPassword(Request::get('pwarule_password'), + $this->getPassword()); + //migrated passwords + $pwcheck_m = $this->hasher->CheckPassword(md5(Request::get('pwarule_password')), + $this->getPassword()); + if (!($pwcheck || $pwcheck_m)) { + $errors[] = $this->getMessage(); + } + } + } + return $errors; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) { + parent::setAllData($data); + if ($this->new || $data['password1'] !== '') { + $this->setPassword($data['password1']); + } + return $this; + } + + /** + * Sets the password by bcrypting the given clear text password. + * + * @param String $clearText The clear text password to be set. + * @return PasswordAdmission + */ + public function setPassword($clearText) { + $this->password = $this->hasher->HashPassword($clearText); + return $this; + } + + /** + * Helper function for storing data to DB. + */ + public function store() { + // Store data. + $stmt = DBManager::get()->prepare("INSERT INTO `passwordadmissions` + (`rule_id`, `message`, `start_time`, `end_time`, `password`, + `mkdate`, `chdate`) + VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `message`=VALUES(`message`), `start_time`=VALUES(`start_time`), + `end_time`=VALUES(`end_time`), `password`=VALUES(`password`), + `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->message, (int)$this->startTime, (int)$this->endTime, $this->password, + time(), time()]); + return $this; + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + $tpl = $factory->open('info'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = parent::validate($data); + if ($data['password1'] === '' && $this->new) { + $errors[] = _('Das Passwort darf nicht leer sein.'); + } + if ($data['password1'] !== $data['password2']) { + $errors[] = _('Das Passwort stimmt nicht mit der Wiederholung überein.'); + } + return $errors; + } +} diff --git a/lib/admissionrules/preferentialadmission/PreferentialAdmission.class.php b/lib/admissionrules/preferentialadmission/PreferentialAdmission.class.php deleted file mode 100644 index bee3e19..0000000 --- a/lib/admissionrules/preferentialadmission/PreferentialAdmission.class.php +++ /dev/null @@ -1,540 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once('lib/classes/admission/AdmissionRule.class.php'); -require_once('lib/classes/admission/UserFilter.class.php'); - -class PreferentialAdmission extends AdmissionRule -{ - // --- ATTRIBUTES --- - - /** - * Stores IDs of userlists generated for representing the selected - * conditions. These lists are created on seat distribution in the course - * set and are deleted immediately after. - */ - public $userlists = []; - - /** - * Conditions for selecting the favored people in seat distribution. - */ - public $conditions = []; - - /** - * Should higher semesters of study be favored? - */ - public $favorSemester = false; - - /** - * If semesters are favored, which bonus difference shall be set between - * each semester of study? - */ - public $bonus_difference = 100; - - /** - * The courseset this rule belongs to. - */ - public $courseset = null; - - // --- OPERATIONS --- - - /** - * Standard constructor. - * - * @param String ruleId If this rule has been saved previously, it - * will be loaded from database. - * @return AdmissionRule the current object (this). - */ - public function __construct($ruleId='') - { - $this->id = $ruleId; - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('prefadmissions'); - } - } - - /** - * Adds a new UserFilter to this rule. - * - * @param UserFilter condition - * @return PreferentialAdmission - */ - public function addCondition($condition) - { - $this->conditions[$condition->getId()] = $condition; - return $this; - } - - /** - * Hook that can be called after the seat distribution on the courseset - * has completed. User lists that were generated before are removed now. - */ - public function afterSeatDistribution($courseset) - { - foreach ($this->userlists as $id) { - $current = new AdmissionUserList($id); - $courseset->removeUserList($id); - $current->delete(); - } - } - - /** - * Hook that can be called when the seat distribution on the courseset - * starts. This type of admission rule gets all users that fulfill the - * specified conditions and generates user lists with modified chances - * in seat distribution. - * - * @param CourseSet The courseset this rule belongs to. - */ - public function beforeSeatDistribution($courseset) - { - $this->courseset = $courseset; - /* - * First, we need to calculate the maximum of persons applying - * for a single course as that number will influence the numbers - * to set for preferation. - */ - $this->bonus_difference = DBManager::get()->fetchColumn("SELECT MAX(users) FROM ( - SELECT `priority`, COUNT(DISTINCT `user_id`) AS users - FROM `priorities` - WHERE `set_id` = ? - GROUP BY `priority` - ) t", [$courseset->getId()]); - $users = $this->getAffectedUsers(); - - // No study semester variation, just put all users together. - if (!$this->favorSemester) { - - $userlist = new AdmissionUserList(); - $userlist->setUsers($users)->setFactor($this->bonus_difference + 1)->store(); - $this->userlists[] = $userlist->getId(); - $courseset->addUserList($userlist->getId()); - - // Study semesters need to be considered for differentiation... - } else { - /* - * Build data grouped by semester of study for users affected - * by given conditions. - */ - if ($this->conditions) { - $grouped = $this->getSemesterGroups($users, true); - - /* - * Build data grouped by semester of study for all users - * (excluding all users affected by given conditions). - */ - $rest = $this->getSemesterGroups( - array_keys(AdmissionPriority::getPriorities($courseset->getId())), - false, $users); - - /* - * Now set bonus factors to higher semesters. We are processing - * users not affected by conditions first so that we get the - * maximum bonus these users get and can build on top of that - * for users affected by conditions. - */ - $maxbonus = $this->setSemesterBonus($courseset, $rest); - - /* - * Finally, set bonuses for the users affected by conditions. - */ - $endbonus = $this->setSemesterBonus($courseset, $grouped, $maxbonus + 1); - /* - * No conditions given, just group all users - * by their semester of study. - */ - } else { - // Build list of users by semester of study. - $grouped = $this->getSemesterGroups( - array_keys(AdmissionPriority::getPriorities($courseset->getId())), - false); - - // Assign corresponding bonus to users. - $maxbonus = $this->setSemesterBonus($courseset, $grouped); - } - } - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() - { - parent::delete(); - // Delete rule data. - $stmt = DBManager::get()->prepare("DELETE FROM `prefadmissions` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - // Delete all associated conditions... - foreach ($this->conditions as $condition) { - $condition->delete(); - } - // ... and their connection to this rule. - $stmt = DBManager::get()->prepare("DELETE FROM `prefadmission_condition` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Gets all users that are matched by thís rule. - * - * @return Array An array containing IDs of users who are matched by - * this rule. - */ - public function getAffectedUsers() - { - $users = []; - if ($this->conditions) { - // Get users from all specified conditions. - foreach ($this->conditions as $condition) { - $users = array_unique(array_merge($users, $condition->getUsers())); - } - } else { - $users = array_keys(AdmissionPriority::getPriorities($this->courseset->getId())); - } - return $users; - } - - /** - * Gets all defined conditions. - * - * @return Array - */ - public function getConditions() - { - return $this->conditions; - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() - { - return _('Sie können hier festlegen, dass bestimmte Studiengänge, '. - 'Fachsemester etc. bei der Platzverteilung zu Veranstaltungen '. - 'bevorzugt behandelt werden sollen.'); - } - - /** - * Returns whether higher semesters of study should be favored. - * - * @return bool - */ - public function getFavorSemester() - { - return $this->favorSemester; - } - - /** - * Return this rule's name. - */ - public static function getName() - { - return _('Bevorzugte Anmeldung'); - } - - /** - * Gets the semesters of study for the given users. If conditions are - * set and should be considered, only the semesters of study belonging - * to the given conditions are set. - * - * @param $users user IDs to process - * @param $considerConditions should only the semesters of study belonging - * to given conditions be considered? - * @param array $exclude user IDs to exclude - * @return array Users with their maximal semester of study. - */ - public function getSemesterGroups($users, $considerConditions, $exclude = []) - { - /* - * Get all selected condition values so that the study semester - * can be matched against that data; we don't want some "general" - * value for a user's study semester, but the one that is assigned - * to a given subject and degree. - */ - $queryParts = []; - $values = [$users]; - if ($exclude) { - $values[] = $exclude; - } - if ($considerConditions) { - foreach ($this->conditions as $condition) { - $queryPart = ""; - // Search for subject and degree entries. - foreach ($condition->getFields() as $field) { - switch (get_class($field)) { - case 'DegreeCondition': - if ($queryPart) { - $queryPart .= " AND "; - } - $queryPart .= "`abschluss_id`".$field->getCompareOperator()."?"; - $values[] = $field->getValue() ?: ''; - break; - case 'SubjectCondition': - if ($queryPart) { - $queryPart .= " AND "; - } - $queryPart .= "`fach_id`".$field->getCompareOperator()."?"; - $values[] = $field->getValue() ?: ''; - break; - case 'SemesterOfStudyCondition': - if ($queryPart) { - $queryPart .= " AND "; - } - $queryPart .= "`semester`".$field->getCompareOperator()."?"; - $values[] = $field->getValue() ?: ''; - break; - default: - break; - } - } - if ($queryPart) { - $queryParts[] = $queryPart; - } - } - } - // Build SQL query with affected users and selected subjects and degrees. - $query = "SELECT `user_id`, MAX(`semester`) AS semester - FROM `user_studiengang` - WHERE `user_id` IN (?)"; - if ($exclude) { - $query .= " AND `user_id` NOT IN (?)"; - } - if ($queryParts) { - $query .= " AND ((".implode(") OR (", $queryParts)."))"; - } - $query .= " GROUP BY `user_id` ORDER BY `semester`, `user_id`"; - $groups = []; - foreach (DBManager::get()->fetchAll($query, $values) as $entry) { - if (intval($entry['semester'])) { - $groups[intval($entry['semester'])][] = $entry['user_id']; - } - } - ksort($groups); - return $groups; - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() - { - $factory = new Flexi\Factory(__DIR__.'/templates/'); - // Now open specific template for this rule and insert base template. - $tpl = $factory->open('configure'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Helper function for loading data from DB. Generic AdmissionRule data is - * loaded with the parent load() method. - */ - public function load() - { - // Load basic data. - $stmt = DBManager::get()->prepare("SELECT * - FROM `prefadmissions` WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->favorSemester = $current['favor_semester']; - // Retrieve conditions. - $stmt = DBManager::get()->prepare("SELECT * - FROM `prefadmission_condition` WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - $conditions = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($conditions as $condition) { - $currentCondition = new UserFilter($condition['condition_id']); - $this->conditions[$condition['condition_id']] = $currentCondition; - } - } - } - - /** - * Removes the condition with the given ID from the rule. - * - * @param String conditionId - * @return PreferentialAdmission - */ - public function removeCondition($conditionId) - { - $this->conditions[$conditionId]->delete(); - unset($this->conditions[$conditionId]); - return $this; - } - - /** - * Admission is open for everyone. On seat distribution, the rule conditions - * will be used to generate user lists with the specified chance. - * - * @param String $userId - * @param String $courseId - * @return Array Is the user allowed to register or are there any errors? - */ - public function ruleApplies($userId, $courseId) - { - return []; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) - { - UserFilterField::getAvailableFilterFields(); - parent::setAllData($data); - $this->favorSemester = (bool) $data['favor_semester']; - $this->conditions = []; - if ($data['conditions']) { - foreach ($data['conditions'] as $condition) { - $this->addCondition(ObjectBuilder::build($condition, 'UserFilter')); - } - } - return $this; - } - - /** - * New setting for favoring higher semesters of study. - * - * @param bool $newFavorSemester - * @return PreferentialAdmission - */ - public function setFavorSemester($newFavorSemester) { - $this->favorSemester = $newFavorSemester; - return $this; - } - - /** - * Create user lists and set bonus corresponding to - * the maximal available semester of study for given users. - * - * @param $courseset CourseSet to add user lists to - * @param $grouped associative array of users in the form - * => array(, $members) { - $userlist = new AdmissionUserList(); - $userlist->setUsers($members); - $userlist->setFactor($bonus); - $userlist->store(); - $bonus = $bonus + ($this->bonus_difference + 1); - $courseset->addUserList($userlist->getId()); - $this->userlists[] = $userlist->getId(); - } - return $bonus; - } - - /** - * Helper function for storing data to DB. - */ - public function store() - { - // Store rule data. - $stmt = DBManager::get()->prepare("INSERT INTO `prefadmissions` - (`rule_id`, `favor_semester`, `mkdate`, `chdate`) - VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `favor_semester`=VALUES(`favor_semester`), - `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->favorSemester, time(), time()]); - // Delete removed conditions from DB. - $entries = DBManager::get()->fetchAll("SELECT `condition_id` FROM - `prefadmission_condition` WHERE `rule_id`=? AND `condition_id` NOT IN (?)", - [$this->id, array_keys($this->conditions)]); - foreach ($entries as $entry) { - $current = new UserFilter($entry['condition_id']); - $current->delete(); - } - DBManager::get()->execute("DELETE FROM `prefadmission_condition` - WHERE `rule_id`=? AND `condition_id` NOT IN (?)", [$this->id, array_keys($this->conditions)]); - // Store all conditions. - $queries = []; - $parameters = []; - if ($this->conditions) { - foreach ($this->conditions as $condition) { - // Store each condition... - $condition->store(); - $queries[] = "(?, ?, ?)"; - $parameters[] = $this->id; - $parameters[] = $condition->getId(); - $parameters[] = time(); - } - // Store all assignments between rule and condition. - $stmt = DBManager::get()->execute("INSERT INTO `prefadmission_condition` - (`rule_id`, `condition_id`, `mkdate`) - VALUES ".implode(",", $queries)." ON DUPLICATE KEY UPDATE - `condition_id`=VALUES(`condition_id`)", $parameters); - } - return $this; - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() - { - $factory = new Flexi\Factory(__DIR__.'/templates/'); - $tpl = $factory->open('info'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = parent::validate($data); - if (!$data['conditions'] && !$data['favor_semester']) { - $errors[] = _('Es muss mindestens eine Auswahlbedingung angegeben werden.'); - } - return $errors; - } - - public function __clone() - { - $this->id = md5(uniqid(get_class($this))); - $this->courseSetId = null; - $cloned_conditions = []; - foreach ($this->conditions as $condition) { - $dolly = clone $condition; - $cloned_conditions[$dolly->id] = $dolly; - } - $this->conditions = $cloned_conditions; - } - -} /* end of class PreferentialAdmission */ diff --git a/lib/admissionrules/preferentialadmission/PreferentialAdmission.php b/lib/admissionrules/preferentialadmission/PreferentialAdmission.php new file mode 100644 index 0000000..bee3e19 --- /dev/null +++ b/lib/admissionrules/preferentialadmission/PreferentialAdmission.php @@ -0,0 +1,540 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +require_once('lib/classes/admission/AdmissionRule.class.php'); +require_once('lib/classes/admission/UserFilter.class.php'); + +class PreferentialAdmission extends AdmissionRule +{ + // --- ATTRIBUTES --- + + /** + * Stores IDs of userlists generated for representing the selected + * conditions. These lists are created on seat distribution in the course + * set and are deleted immediately after. + */ + public $userlists = []; + + /** + * Conditions for selecting the favored people in seat distribution. + */ + public $conditions = []; + + /** + * Should higher semesters of study be favored? + */ + public $favorSemester = false; + + /** + * If semesters are favored, which bonus difference shall be set between + * each semester of study? + */ + public $bonus_difference = 100; + + /** + * The courseset this rule belongs to. + */ + public $courseset = null; + + // --- OPERATIONS --- + + /** + * Standard constructor. + * + * @param String ruleId If this rule has been saved previously, it + * will be loaded from database. + * @return AdmissionRule the current object (this). + */ + public function __construct($ruleId='') + { + $this->id = $ruleId; + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('prefadmissions'); + } + } + + /** + * Adds a new UserFilter to this rule. + * + * @param UserFilter condition + * @return PreferentialAdmission + */ + public function addCondition($condition) + { + $this->conditions[$condition->getId()] = $condition; + return $this; + } + + /** + * Hook that can be called after the seat distribution on the courseset + * has completed. User lists that were generated before are removed now. + */ + public function afterSeatDistribution($courseset) + { + foreach ($this->userlists as $id) { + $current = new AdmissionUserList($id); + $courseset->removeUserList($id); + $current->delete(); + } + } + + /** + * Hook that can be called when the seat distribution on the courseset + * starts. This type of admission rule gets all users that fulfill the + * specified conditions and generates user lists with modified chances + * in seat distribution. + * + * @param CourseSet The courseset this rule belongs to. + */ + public function beforeSeatDistribution($courseset) + { + $this->courseset = $courseset; + /* + * First, we need to calculate the maximum of persons applying + * for a single course as that number will influence the numbers + * to set for preferation. + */ + $this->bonus_difference = DBManager::get()->fetchColumn("SELECT MAX(users) FROM ( + SELECT `priority`, COUNT(DISTINCT `user_id`) AS users + FROM `priorities` + WHERE `set_id` = ? + GROUP BY `priority` + ) t", [$courseset->getId()]); + $users = $this->getAffectedUsers(); + + // No study semester variation, just put all users together. + if (!$this->favorSemester) { + + $userlist = new AdmissionUserList(); + $userlist->setUsers($users)->setFactor($this->bonus_difference + 1)->store(); + $this->userlists[] = $userlist->getId(); + $courseset->addUserList($userlist->getId()); + + // Study semesters need to be considered for differentiation... + } else { + /* + * Build data grouped by semester of study for users affected + * by given conditions. + */ + if ($this->conditions) { + $grouped = $this->getSemesterGroups($users, true); + + /* + * Build data grouped by semester of study for all users + * (excluding all users affected by given conditions). + */ + $rest = $this->getSemesterGroups( + array_keys(AdmissionPriority::getPriorities($courseset->getId())), + false, $users); + + /* + * Now set bonus factors to higher semesters. We are processing + * users not affected by conditions first so that we get the + * maximum bonus these users get and can build on top of that + * for users affected by conditions. + */ + $maxbonus = $this->setSemesterBonus($courseset, $rest); + + /* + * Finally, set bonuses for the users affected by conditions. + */ + $endbonus = $this->setSemesterBonus($courseset, $grouped, $maxbonus + 1); + /* + * No conditions given, just group all users + * by their semester of study. + */ + } else { + // Build list of users by semester of study. + $grouped = $this->getSemesterGroups( + array_keys(AdmissionPriority::getPriorities($courseset->getId())), + false); + + // Assign corresponding bonus to users. + $maxbonus = $this->setSemesterBonus($courseset, $grouped); + } + } + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() + { + parent::delete(); + // Delete rule data. + $stmt = DBManager::get()->prepare("DELETE FROM `prefadmissions` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + // Delete all associated conditions... + foreach ($this->conditions as $condition) { + $condition->delete(); + } + // ... and their connection to this rule. + $stmt = DBManager::get()->prepare("DELETE FROM `prefadmission_condition` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Gets all users that are matched by thís rule. + * + * @return Array An array containing IDs of users who are matched by + * this rule. + */ + public function getAffectedUsers() + { + $users = []; + if ($this->conditions) { + // Get users from all specified conditions. + foreach ($this->conditions as $condition) { + $users = array_unique(array_merge($users, $condition->getUsers())); + } + } else { + $users = array_keys(AdmissionPriority::getPriorities($this->courseset->getId())); + } + return $users; + } + + /** + * Gets all defined conditions. + * + * @return Array + */ + public function getConditions() + { + return $this->conditions; + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() + { + return _('Sie können hier festlegen, dass bestimmte Studiengänge, '. + 'Fachsemester etc. bei der Platzverteilung zu Veranstaltungen '. + 'bevorzugt behandelt werden sollen.'); + } + + /** + * Returns whether higher semesters of study should be favored. + * + * @return bool + */ + public function getFavorSemester() + { + return $this->favorSemester; + } + + /** + * Return this rule's name. + */ + public static function getName() + { + return _('Bevorzugte Anmeldung'); + } + + /** + * Gets the semesters of study for the given users. If conditions are + * set and should be considered, only the semesters of study belonging + * to the given conditions are set. + * + * @param $users user IDs to process + * @param $considerConditions should only the semesters of study belonging + * to given conditions be considered? + * @param array $exclude user IDs to exclude + * @return array Users with their maximal semester of study. + */ + public function getSemesterGroups($users, $considerConditions, $exclude = []) + { + /* + * Get all selected condition values so that the study semester + * can be matched against that data; we don't want some "general" + * value for a user's study semester, but the one that is assigned + * to a given subject and degree. + */ + $queryParts = []; + $values = [$users]; + if ($exclude) { + $values[] = $exclude; + } + if ($considerConditions) { + foreach ($this->conditions as $condition) { + $queryPart = ""; + // Search for subject and degree entries. + foreach ($condition->getFields() as $field) { + switch (get_class($field)) { + case 'DegreeCondition': + if ($queryPart) { + $queryPart .= " AND "; + } + $queryPart .= "`abschluss_id`".$field->getCompareOperator()."?"; + $values[] = $field->getValue() ?: ''; + break; + case 'SubjectCondition': + if ($queryPart) { + $queryPart .= " AND "; + } + $queryPart .= "`fach_id`".$field->getCompareOperator()."?"; + $values[] = $field->getValue() ?: ''; + break; + case 'SemesterOfStudyCondition': + if ($queryPart) { + $queryPart .= " AND "; + } + $queryPart .= "`semester`".$field->getCompareOperator()."?"; + $values[] = $field->getValue() ?: ''; + break; + default: + break; + } + } + if ($queryPart) { + $queryParts[] = $queryPart; + } + } + } + // Build SQL query with affected users and selected subjects and degrees. + $query = "SELECT `user_id`, MAX(`semester`) AS semester + FROM `user_studiengang` + WHERE `user_id` IN (?)"; + if ($exclude) { + $query .= " AND `user_id` NOT IN (?)"; + } + if ($queryParts) { + $query .= " AND ((".implode(") OR (", $queryParts)."))"; + } + $query .= " GROUP BY `user_id` ORDER BY `semester`, `user_id`"; + $groups = []; + foreach (DBManager::get()->fetchAll($query, $values) as $entry) { + if (intval($entry['semester'])) { + $groups[intval($entry['semester'])][] = $entry['user_id']; + } + } + ksort($groups); + return $groups; + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() + { + $factory = new Flexi\Factory(__DIR__.'/templates/'); + // Now open specific template for this rule and insert base template. + $tpl = $factory->open('configure'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Helper function for loading data from DB. Generic AdmissionRule data is + * loaded with the parent load() method. + */ + public function load() + { + // Load basic data. + $stmt = DBManager::get()->prepare("SELECT * + FROM `prefadmissions` WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->favorSemester = $current['favor_semester']; + // Retrieve conditions. + $stmt = DBManager::get()->prepare("SELECT * + FROM `prefadmission_condition` WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + $conditions = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($conditions as $condition) { + $currentCondition = new UserFilter($condition['condition_id']); + $this->conditions[$condition['condition_id']] = $currentCondition; + } + } + } + + /** + * Removes the condition with the given ID from the rule. + * + * @param String conditionId + * @return PreferentialAdmission + */ + public function removeCondition($conditionId) + { + $this->conditions[$conditionId]->delete(); + unset($this->conditions[$conditionId]); + return $this; + } + + /** + * Admission is open for everyone. On seat distribution, the rule conditions + * will be used to generate user lists with the specified chance. + * + * @param String $userId + * @param String $courseId + * @return Array Is the user allowed to register or are there any errors? + */ + public function ruleApplies($userId, $courseId) + { + return []; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) + { + UserFilterField::getAvailableFilterFields(); + parent::setAllData($data); + $this->favorSemester = (bool) $data['favor_semester']; + $this->conditions = []; + if ($data['conditions']) { + foreach ($data['conditions'] as $condition) { + $this->addCondition(ObjectBuilder::build($condition, 'UserFilter')); + } + } + return $this; + } + + /** + * New setting for favoring higher semesters of study. + * + * @param bool $newFavorSemester + * @return PreferentialAdmission + */ + public function setFavorSemester($newFavorSemester) { + $this->favorSemester = $newFavorSemester; + return $this; + } + + /** + * Create user lists and set bonus corresponding to + * the maximal available semester of study for given users. + * + * @param $courseset CourseSet to add user lists to + * @param $grouped associative array of users in the form + * => array(, $members) { + $userlist = new AdmissionUserList(); + $userlist->setUsers($members); + $userlist->setFactor($bonus); + $userlist->store(); + $bonus = $bonus + ($this->bonus_difference + 1); + $courseset->addUserList($userlist->getId()); + $this->userlists[] = $userlist->getId(); + } + return $bonus; + } + + /** + * Helper function for storing data to DB. + */ + public function store() + { + // Store rule data. + $stmt = DBManager::get()->prepare("INSERT INTO `prefadmissions` + (`rule_id`, `favor_semester`, `mkdate`, `chdate`) + VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `favor_semester`=VALUES(`favor_semester`), + `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->favorSemester, time(), time()]); + // Delete removed conditions from DB. + $entries = DBManager::get()->fetchAll("SELECT `condition_id` FROM + `prefadmission_condition` WHERE `rule_id`=? AND `condition_id` NOT IN (?)", + [$this->id, array_keys($this->conditions)]); + foreach ($entries as $entry) { + $current = new UserFilter($entry['condition_id']); + $current->delete(); + } + DBManager::get()->execute("DELETE FROM `prefadmission_condition` + WHERE `rule_id`=? AND `condition_id` NOT IN (?)", [$this->id, array_keys($this->conditions)]); + // Store all conditions. + $queries = []; + $parameters = []; + if ($this->conditions) { + foreach ($this->conditions as $condition) { + // Store each condition... + $condition->store(); + $queries[] = "(?, ?, ?)"; + $parameters[] = $this->id; + $parameters[] = $condition->getId(); + $parameters[] = time(); + } + // Store all assignments between rule and condition. + $stmt = DBManager::get()->execute("INSERT INTO `prefadmission_condition` + (`rule_id`, `condition_id`, `mkdate`) + VALUES ".implode(",", $queries)." ON DUPLICATE KEY UPDATE + `condition_id`=VALUES(`condition_id`)", $parameters); + } + return $this; + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() + { + $factory = new Flexi\Factory(__DIR__.'/templates/'); + $tpl = $factory->open('info'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = parent::validate($data); + if (!$data['conditions'] && !$data['favor_semester']) { + $errors[] = _('Es muss mindestens eine Auswahlbedingung angegeben werden.'); + } + return $errors; + } + + public function __clone() + { + $this->id = md5(uniqid(get_class($this))); + $this->courseSetId = null; + $cloned_conditions = []; + foreach ($this->conditions as $condition) { + $dolly = clone $condition; + $cloned_conditions[$dolly->id] = $dolly; + } + $this->conditions = $cloned_conditions; + } + +} /* end of class PreferentialAdmission */ diff --git a/lib/admissionrules/termsadmission/TermsAdmission.class.php b/lib/admissionrules/termsadmission/TermsAdmission.class.php deleted file mode 100644 index 50c8712..0000000 --- a/lib/admissionrules/termsadmission/TermsAdmission.class.php +++ /dev/null @@ -1,171 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once 'lib/classes/admission/AdmissionRule.class.php'; - -class TermsAdmission extends AdmissionRule -{ - // Terms of admission - public $terms; - - /** - * Standard constructor. - * - * @param String ruleId - */ - public function __construct($ruleId = '', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('termsadmissions'); - } - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() - { - parent::delete(); - - $stmt = DBManager::get()->prepare('DELETE FROM termsadmissions WHERE rule_id = ?'); - $stmt->execute([$this->getId()]); - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() - { - return _('Mit dieser Anmelderegel können Sie einen Kurs mit spezifischen Teilnahmebedingungen realisieren. ' - . 'Die Anmeldung ist erst möglich, nachdem diese akzeptiert wurden.'); - } - - /** - * Shows an input form - * - * @return string A template-based input form. - * @throws Flexi\TemplateNotFoundException - */ - public function getInput() - { - $factory = new Flexi\Factory(__DIR__ . '/templates'); - $template = $factory->open('input'); - $template->rule = $this; - - return (string) MessageBox::info($template->render())->hideClose(); - } - - /** - * Return this rule's name. - */ - public static function getName() { - return _('Kurs mit Teilnahmebedingungen'); - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - * @throws Flexi\TemplateNotFoundException - */ - public function getTemplate() - { - $factory = new Flexi\Factory(__DIR__ . '/templates'); - $template = $factory->open('configure'); - $template->rule = $this; - - return $template->render(); - } - - /** - * Does the current rule allow the given user to register as participant - * in the given course? - * - * @param String userId - * @param String courseId - * @return Array - */ - public function ruleApplies($userId, $courseId) - { - $errors = []; - - // check if the user has accepted the terms - if (Request::int('terms_accepted')) { - $_SESSION['terms_accepted_' . $this->getId()] = true; - } - if (!$_SESSION['terms_accepted_' . $this->getId()]) { - $errors[] = _('Um sich anzumelden, müssen Sie die Teilnahmebedingungen akzeptieren.'); - } - - return $errors; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) - { - parent::setAllData($data); - $this->terms = trim($data['terms']); - return $this; - } - - /** - * Internal helper function for loading rule definition from database. - */ - public function load() - { - $rule = DBManager::get()->fetchOne('SELECT * FROM termsadmissions WHERE rule_id = ?', [$this->getId()]); - $this->terms = $rule['terms']; - return $this; - } - - /** - * Store rule definition to database. - */ - public function store() - { - // Store data. - $stmt = DBManager::get()->prepare('INSERT INTO termsadmissions (rule_id, terms, mkdate, chdate) VALUES (?, ?, ?, ?) - ON DUPLICATE KEY UPDATE terms = VALUES(terms), chdate = VALUES(chdate)'); - $stmt->execute([$this->id, $this->terms, time(), time()]); - } - - /** - * A textual description of the current rule. - * - * @return String - * @throws Flexi\TemplateNotFoundException - */ - public function toString() - { - $factory = new Flexi\Factory(__DIR__ . '/templates/'); - $template = $factory->open('info'); - $template->rule = $this; - - return $template->render(); - } -} diff --git a/lib/admissionrules/termsadmission/TermsAdmission.php b/lib/admissionrules/termsadmission/TermsAdmission.php new file mode 100644 index 0000000..50c8712 --- /dev/null +++ b/lib/admissionrules/termsadmission/TermsAdmission.php @@ -0,0 +1,171 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +require_once 'lib/classes/admission/AdmissionRule.class.php'; + +class TermsAdmission extends AdmissionRule +{ + // Terms of admission + public $terms; + + /** + * Standard constructor. + * + * @param String ruleId + */ + public function __construct($ruleId = '', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('termsadmissions'); + } + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() + { + parent::delete(); + + $stmt = DBManager::get()->prepare('DELETE FROM termsadmissions WHERE rule_id = ?'); + $stmt->execute([$this->getId()]); + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() + { + return _('Mit dieser Anmelderegel können Sie einen Kurs mit spezifischen Teilnahmebedingungen realisieren. ' + . 'Die Anmeldung ist erst möglich, nachdem diese akzeptiert wurden.'); + } + + /** + * Shows an input form + * + * @return string A template-based input form. + * @throws Flexi\TemplateNotFoundException + */ + public function getInput() + { + $factory = new Flexi\Factory(__DIR__ . '/templates'); + $template = $factory->open('input'); + $template->rule = $this; + + return (string) MessageBox::info($template->render())->hideClose(); + } + + /** + * Return this rule's name. + */ + public static function getName() { + return _('Kurs mit Teilnahmebedingungen'); + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + * @throws Flexi\TemplateNotFoundException + */ + public function getTemplate() + { + $factory = new Flexi\Factory(__DIR__ . '/templates'); + $template = $factory->open('configure'); + $template->rule = $this; + + return $template->render(); + } + + /** + * Does the current rule allow the given user to register as participant + * in the given course? + * + * @param String userId + * @param String courseId + * @return Array + */ + public function ruleApplies($userId, $courseId) + { + $errors = []; + + // check if the user has accepted the terms + if (Request::int('terms_accepted')) { + $_SESSION['terms_accepted_' . $this->getId()] = true; + } + if (!$_SESSION['terms_accepted_' . $this->getId()]) { + $errors[] = _('Um sich anzumelden, müssen Sie die Teilnahmebedingungen akzeptieren.'); + } + + return $errors; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) + { + parent::setAllData($data); + $this->terms = trim($data['terms']); + return $this; + } + + /** + * Internal helper function for loading rule definition from database. + */ + public function load() + { + $rule = DBManager::get()->fetchOne('SELECT * FROM termsadmissions WHERE rule_id = ?', [$this->getId()]); + $this->terms = $rule['terms']; + return $this; + } + + /** + * Store rule definition to database. + */ + public function store() + { + // Store data. + $stmt = DBManager::get()->prepare('INSERT INTO termsadmissions (rule_id, terms, mkdate, chdate) VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE terms = VALUES(terms), chdate = VALUES(chdate)'); + $stmt->execute([$this->id, $this->terms, time(), time()]); + } + + /** + * A textual description of the current rule. + * + * @return String + * @throws Flexi\TemplateNotFoundException + */ + public function toString() + { + $factory = new Flexi\Factory(__DIR__ . '/templates/'); + $template = $factory->open('info'); + $template->rule = $this; + + return $template->render(); + } +} diff --git a/lib/admissionrules/timedadmission/TimedAdmission.class.php b/lib/admissionrules/timedadmission/TimedAdmission.class.php deleted file mode 100644 index e167bd8..0000000 --- a/lib/admissionrules/timedadmission/TimedAdmission.class.php +++ /dev/null @@ -1,243 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class TimedAdmission extends AdmissionRule -{ - // --- ATTRIBUTES --- - - /** - * End of course admission. - */ - public $endTime = 0; - - /** - * Start of course admission. - */ - public $startTime = 0; - - // --- OPERATIONS --- - - /** - * Standard constructor - * - * @param String ruleId - */ - public function __construct($ruleId='', $courseSetId = '') - { - parent::__construct($ruleId, $courseSetId); - $this->default_message = _('Sie befinden sich nicht innerhalb des Anmeldezeitraums.'); - if ($ruleId) { - $this->load(); - } else { - $this->id = $this->generateId('timedadmissions'); - } - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() { - parent::delete(); - // Delete rule data. - $stmt = DBManager::get()->prepare("DELETE FROM `timedadmissions` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() { - return _("Anmelderegeln dieses Typs legen ein Zeitfenster fest, in ". - "dem die Anmeldung zu Veranstaltungen möglich ist. Es kann auch ". - "nur ein Start- oder Endzeitpunkt angegeben werden."); - } - - /** - * Gets the end of course admission. - * - * @return int - */ - public function getEndTime() - { - return $this->endTime; - } - - /** - * Return this rule's name. - */ - public static function getName() { - return _("Zeitgesteuerte Anmeldung"); - } - - /** - * Gets the start of course admission. - * - * @return int - */ - public function getStartTime() - { - return $this->startTime; - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - // Open specific template for this rule and insert base template. - $tpl = $factory->open('configure'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Helper function for loading rule definition from database. - */ - public function load() { - // Load data. - $stmt = DBManager::get()->prepare("SELECT * - FROM `timedadmissions` WHERE `rule_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->message = $current['message']; - $this->startTime = $current['start_time']; - $this->endTime = $current['end_time']; - } - } - - /** - * Is admission allowed according to the defined time frame? - * - * @param String userId - * @param String courseId - * @return Array - */ - public function ruleApplies($userId, $courseId) { - $errors = []; - if (!$this->checkTimeFrame()) { - $errors[] = $this->getMessage(); - } - return $errors; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) { - parent::setAllData($data); - if ($data['startdate']) { - $sdate = $data['startdate']; - $stime = $data['starttime']; - $parsed = date_parse($sdate.' '.$stime); - $timestamp = mktime($parsed['hour'], $parsed['minute'], 0, $parsed['month'], $parsed['day'], $parsed['year']); - $this->setStartTime($timestamp); - } - if ($data['enddate']) { - $edate = $data['enddate']; - $etime = $data['endtime']; - if (!$etime) { - $etime = '23:59'; - } - $parsed = date_parse($edate.' '.$etime); - $timestamp = mktime($parsed['hour'], $parsed['minute'], 0, $parsed['month'], $parsed['day'], $parsed['year']); - $this->setEndTime($timestamp); - } - return $this; - } - - /** - * Sets a new end timestamp for course admission. - * - * @param int newEndTime - * @return TimedAdmission - */ - public function setEndTime($newEndTime) - { - $this->endTime = $newEndTime; - return $this; - } - - /** - * Sets a new start timestamp for course admission. - * - * @param int newStartTime - * @return TimedAdmission - */ - public function setStartTime($newStartTime) - { - $this->startTime = $newStartTime; - return $this; - } - - /** - * Store rule definition to database. - */ - public function store() { - // Store data. - $stmt = DBManager::get()->prepare("INSERT INTO `timedadmissions` - (`rule_id`, `message`, `start_time`, - `end_time`, `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE `start_time`=VALUES(`start_time`), - `end_time`=VALUES(`end_time`),message=VALUES(message), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->message, (int)$this->startTime, - (int)$this->endTime, time(), time()]); - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() - { - $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); - $tpl = $factory->open('info'); - $tpl->set_attribute('rule', $this); - return $tpl->render(); - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = parent::validate($data); - if (!$data['startdate'] && !$data['enddate']) { - $errors[] = _('Bitte geben Sie entweder ein Start- oder Enddatum an.'); - } - if ($data['startdate'] && $data['enddate'] && strtotime($data['enddate'] . ' ' . $data['endtime']) < strtotime($data['startdate']. ' ' . $data['starttime'])) { - $errors[] = _('Das Enddatum darf nicht vor dem Startdatum liegen.'); - } - return $errors; - } - -} /* end of class TimedAdmission */ - -?> diff --git a/lib/admissionrules/timedadmission/TimedAdmission.php b/lib/admissionrules/timedadmission/TimedAdmission.php new file mode 100644 index 0000000..e167bd8 --- /dev/null +++ b/lib/admissionrules/timedadmission/TimedAdmission.php @@ -0,0 +1,243 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class TimedAdmission extends AdmissionRule +{ + // --- ATTRIBUTES --- + + /** + * End of course admission. + */ + public $endTime = 0; + + /** + * Start of course admission. + */ + public $startTime = 0; + + // --- OPERATIONS --- + + /** + * Standard constructor + * + * @param String ruleId + */ + public function __construct($ruleId='', $courseSetId = '') + { + parent::__construct($ruleId, $courseSetId); + $this->default_message = _('Sie befinden sich nicht innerhalb des Anmeldezeitraums.'); + if ($ruleId) { + $this->load(); + } else { + $this->id = $this->generateId('timedadmissions'); + } + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() { + parent::delete(); + // Delete rule data. + $stmt = DBManager::get()->prepare("DELETE FROM `timedadmissions` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() { + return _("Anmelderegeln dieses Typs legen ein Zeitfenster fest, in ". + "dem die Anmeldung zu Veranstaltungen möglich ist. Es kann auch ". + "nur ein Start- oder Endzeitpunkt angegeben werden."); + } + + /** + * Gets the end of course admission. + * + * @return int + */ + public function getEndTime() + { + return $this->endTime; + } + + /** + * Return this rule's name. + */ + public static function getName() { + return _("Zeitgesteuerte Anmeldung"); + } + + /** + * Gets the start of course admission. + * + * @return int + */ + public function getStartTime() + { + return $this->startTime; + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + // Open specific template for this rule and insert base template. + $tpl = $factory->open('configure'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Helper function for loading rule definition from database. + */ + public function load() { + // Load data. + $stmt = DBManager::get()->prepare("SELECT * + FROM `timedadmissions` WHERE `rule_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->message = $current['message']; + $this->startTime = $current['start_time']; + $this->endTime = $current['end_time']; + } + } + + /** + * Is admission allowed according to the defined time frame? + * + * @param String userId + * @param String courseId + * @return Array + */ + public function ruleApplies($userId, $courseId) { + $errors = []; + if (!$this->checkTimeFrame()) { + $errors[] = $this->getMessage(); + } + return $errors; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) { + parent::setAllData($data); + if ($data['startdate']) { + $sdate = $data['startdate']; + $stime = $data['starttime']; + $parsed = date_parse($sdate.' '.$stime); + $timestamp = mktime($parsed['hour'], $parsed['minute'], 0, $parsed['month'], $parsed['day'], $parsed['year']); + $this->setStartTime($timestamp); + } + if ($data['enddate']) { + $edate = $data['enddate']; + $etime = $data['endtime']; + if (!$etime) { + $etime = '23:59'; + } + $parsed = date_parse($edate.' '.$etime); + $timestamp = mktime($parsed['hour'], $parsed['minute'], 0, $parsed['month'], $parsed['day'], $parsed['year']); + $this->setEndTime($timestamp); + } + return $this; + } + + /** + * Sets a new end timestamp for course admission. + * + * @param int newEndTime + * @return TimedAdmission + */ + public function setEndTime($newEndTime) + { + $this->endTime = $newEndTime; + return $this; + } + + /** + * Sets a new start timestamp for course admission. + * + * @param int newStartTime + * @return TimedAdmission + */ + public function setStartTime($newStartTime) + { + $this->startTime = $newStartTime; + return $this; + } + + /** + * Store rule definition to database. + */ + public function store() { + // Store data. + $stmt = DBManager::get()->prepare("INSERT INTO `timedadmissions` + (`rule_id`, `message`, `start_time`, + `end_time`, `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE `start_time`=VALUES(`start_time`), + `end_time`=VALUES(`end_time`),message=VALUES(message), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->message, (int)$this->startTime, + (int)$this->endTime, time(), time()]); + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() + { + $factory = new Flexi\Factory(dirname(__FILE__).'/templates/'); + $tpl = $factory->open('info'); + $tpl->set_attribute('rule', $this); + return $tpl->render(); + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = parent::validate($data); + if (!$data['startdate'] && !$data['enddate']) { + $errors[] = _('Bitte geben Sie entweder ein Start- oder Enddatum an.'); + } + if ($data['startdate'] && $data['enddate'] && strtotime($data['enddate'] . ' ' . $data['endtime']) < strtotime($data['startdate']. ' ' . $data['starttime'])) { + $errors[] = _('Das Enddatum darf nicht vor dem Startdatum liegen.'); + } + return $errors; + } + +} /* end of class TimedAdmission */ + +?> diff --git a/lib/bootstrap-autoload.php b/lib/bootstrap-autoload.php index e2554c7..7d99f86 100644 --- a/lib/bootstrap-autoload.php +++ b/lib/bootstrap-autoload.php @@ -3,102 +3,21 @@ require 'lib/classes/StudipAutoloader.php'; StudipAutoloader::register(); -// General classes folders -StudipAutoloader::addAutoloadPath('lib/models'); -StudipAutoloader::addAutoloadPath('lib/models/calendar'); -StudipAutoloader::addAutoloadPath('lib/models/resources'); -StudipAutoloader::addAutoloadPath('lib/classes'); -StudipAutoloader::addAutoloadPath('lib/classes', 'Studip'); - -// Plugins -StudipAutoloader::addAutoloadPath('lib/plugins/core'); -StudipAutoloader::addAutoloadPath('lib/plugins/db'); -StudipAutoloader::addAutoloadPath('lib/plugins/engine'); - -// Specialized folders -StudipAutoloader::addAutoloadPath('lib/classes/admission'); -StudipAutoloader::addAutoloadPath('lib/classes/admission/userfilter'); -StudipAutoloader::addAutoloadPath('lib/classes/auth_plugins'); -StudipAutoloader::addAutoloadPath('lib/classes/calendar'); - -StudipAutoloader::addAutoloadPath('lib/classes/cache', 'Studip\\Cache'); class_alias(\Studip\Cache\Factory::class, 'StudipCacheFactory'); class_alias(\Studip\Cache\Cache::class, 'StudipCache'); - -StudipAutoloader::addAutoloadPath('lib/classes/exportdocument'); -StudipAutoloader::addAutoloadPath('lib/classes/forms'); -StudipAutoloader::addAutoloadPath('lib/classes/globalsearch'); -StudipAutoloader::addAutoloadPath('lib/classes/helpbar'); -StudipAutoloader::addAutoloadPath('lib/classes/librarysearch/resultparsers'); -StudipAutoloader::addAutoloadPath('lib/classes/librarysearch/searchmodules'); -StudipAutoloader::addAutoloadPath('lib/classes/librarysearch'); -StudipAutoloader::addAutoloadPath('lib/classes/searchtypes'); -StudipAutoloader::addAutoloadPath('lib/classes/sidebar'); -StudipAutoloader::addAutoloadPath('lib/classes/visibility'); -StudipAutoloader::addAutoloadPath('lib/classes/coursewizardsteps'); -StudipAutoloader::addAutoloadPath('lib/classes/wiki'); - -StudipAutoloader::addAutoloadPath('lib/calendar'); -StudipAutoloader::addAutoloadPath('lib/calendar', 'Studip\\Calendar'); -StudipAutoloader::addAutoloadPath('lib/exceptions'); -StudipAutoloader::addAutoloadPath('lib/exceptions/resources'); -StudipAutoloader::addAutoloadPath('lib/exTpl', 'exTpl'); -StudipAutoloader::addAutoloadPath('lib/filesystem'); -StudipAutoloader::addAutoloadPath('lib/migrations'); -StudipAutoloader::addAutoloadPath('lib/modules'); -StudipAutoloader::addAutoloadPath('lib/navigation'); -StudipAutoloader::addAutoloadPath('lib/phplib'); -StudipAutoloader::addAutoloadPath('lib/raumzeit'); -StudipAutoloader::addAutoloadPath('lib/resources'); -StudipAutoloader::addAutoloadPath('lib/activities', 'Studip\\Activity'); - -StudipAutoloader::addAutoloadPath('lib/calendar/lib'); -StudipAutoloader::addAutoloadPath('lib/elearning'); -StudipAutoloader::addAutoloadPath('lib/extern'); -StudipAutoloader::addAutoloadPath('lib/ilias_interface'); - -// Flexi -StudipAutoloader::addAutoloadPath('lib/flexi', 'Flexi'); class_alias(Flexi\PhpTemplate::class, 'Flexi_PhpTemplate'); class_alias(Flexi\Template::class, 'Flexi_Template'); class_alias(Flexi\Factory::class, 'Flexi_TemplateFactory'); class_alias(Flexi\TemplateNotFoundException::class, 'Flexi_TemplateNotFoundException'); - -// Trails -StudipAutoloader::addAutoloadPath('lib/trails', 'Trails'); class_alias(Trails\Controller::class, 'Trails_Controller'); class_alias(Trails\Dispatcher::class, 'Trails_Dispatcher'); class_alias(Trails\Exception::class, 'Trails_Exception'); class_alias(Trails\Flash::class, 'Trails_Flash'); class_alias(Trails\Inflector::class, 'Trails_Inflector'); class_alias(Trails\Response::class, 'Trails_Response'); - class_alias(Trails\Exceptions\DoubleRenderError::class, 'Trails_DoubleRenderError'); class_alias(Trails\Exceptions\MissingFile::class, 'Trails_MissingFile'); class_alias(Trails\Exceptions\RoutingError::class, 'Trails_RoutingError'); class_alias(Trails\Exceptions\SessionRequiredException::class, 'Trails_SessionRequiredException'); class_alias(Trails\Exceptions\UnknownAction::class, 'Trails_UnknownAction'); class_alias(Trails\Exceptions\UnknownController::class, 'Trails_UnknownController'); - -// Messy file names -StudipAutoloader::addClassLookups([ - 'email_validation_class' => 'lib/phplib/email_validation.class.php', - 'messaging' => 'lib/messaging.inc.php', - 'StudipPlugin' => 'lib/plugins/core/StudIPPlugin.class.php', - 'MVVController' => 'app/controllers/module/mvv_controller.php' -]); - -// Vendor -StudipAutoloader::addClassLookups([ - 'PasswordHash' => 'vendor/phpass/PasswordHash.php', -]); - -// XMLRpc -StudipAutoloader::addClassLookup( - ['xmlrpcval', 'xmlrpcmsg', 'xmlrpcresp', 'xmlrpc_client'], - 'composer/phpxmlrpc/phpxmlrpc/lib/xmlrpc.inc' -); -StudipAutoloader::addClassLookup( - ['xmlrpc_server'], - 'composer/phpxmlrpc/phpxmlrpc/lib/xmlrpcs.inc' -); diff --git a/lib/bootstrap.php b/lib/bootstrap.php index 8c618ff..478ca0e 100644 --- a/lib/bootstrap.php +++ b/lib/bootstrap.php @@ -224,7 +224,7 @@ if (Config::get()->CALENDAR_ENABLE) { } if (Config::get()->SOAP_ENABLE) { - require_once 'lib/soap/StudipSoapClient' . (Config::get()->SOAP_USE_PHP5 ? '_PHP5' : '' ) . '.class.php'; + require_once 'lib/soap/StudipSoapClient' . (Config::get()->SOAP_USE_PHP5 ? '_PHP5' : '' ) . '.php'; } if (Config::Get()->ILIAS_INTERFACE_ENABLE) { diff --git a/lib/calendar/CalendarColumn.class.php b/lib/calendar/CalendarColumn.class.php deleted file mode 100644 index cc3abea..0000000 --- a/lib/calendar/CalendarColumn.class.php +++ /dev/null @@ -1,371 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @deprecated since Stud.IP 5.5 - */ - -class CalendarColumn -{ - protected static $number = 0; - protected $title = ""; - protected $id = ""; - public $entries = []; - protected $url = ""; - protected $grouped = false; - protected $sorted_entries = null; - - /** - * creates instance of type CalendarColumn - * - * @param string $id necessary if you want JavaScript enabled for this calendar - * @return CalendarColumn - */ - static public function create($id = null) { - $column = new CalendarColumn($id); - return $column; - } - - /** - * constructor - * - * @param string $id necessary if you want JavaScript enabled for this column - */ - public function __construct($id = null) { - $id !== null || $id = md5(uniqid("CalendarColumn_".self::$number++)); - $this->setId($id); - } - - /** - * returns the id of the column - * - * @return string - */ - public function getId() { - return $this->id; - } - - /** - * sets the id for this column, which is only necessary if you want - * Javascript to be enabled for this calendar - * - * @param string $id new id for this column - * @return CalendarColumn - */ - public function setId($id) { - $this->id = $id; - return $this; - } - - /** - * sets a title like "monday" for this column, which will be displayed in the calendar - * - * @param string $new_title new title - * @return CalendarColumn - */ - public function setTitle($new_title) { - $this->title = $new_title; - return $this; - } - - /** - * returns the title of this column like "monday" - * - * @return string title of column - */ - public function getTitle() { - return $this->title; - } - - /** - * sets the url to be directed to when clicking on the title of the column. - * Usually this is a single-day-view of the calendar. - * - * @param string $new_url an url - * @return CalendarColumn - */ - public function setURL($new_url) { - $this->url = $new_url; - return $this; - } - - /** - * returns the URL of the column (see setURL) - * - * @return string an url - */ - public function getURL() { - return $this->url; - } - - /** - * adds a new entry in the column. The entry needs to be an associative array - * with parameters as follows: - * - * @param array $entry_array associative array for an entry in the column like - * array ( - * 'color' => the color in hex (css-like, without the #) - * 'start' => the (start hour * 100) + (start minute) - * 'end' => the (end hour * 100) + (end minute) - * 'title' => the entry`s title - * 'content' => whatever shall be the content of the entry as a string - * ) - */ - public function addEntry($entry_array) { - if (!isset($entry_array['start']) || !isset($entry_array['end']) - || !isset($entry_array['title']) ) { - throw new InvalidArgumentException('The entry '. print_r($entry_array, true) .' does not follow the specifications!'); - } else { - $this->entries[] = $entry_array; - } - return $this; - } - - /** - * adds many entries to the column. For the syntax of an entry see addEntry() - * - * @param array $entries_array - * @return CalendarColumn - */ - public function addEntries($entries_array = []) { - foreach ($entries_array as $entry_array) { - $this->addEntry($entry_array); - } - return $this; - } - - /** - * returns all entries of this column - * - * @return array of arrays like - * array ( - * 'color' => the color in hex (css-like, without the #) - * 'start' => the (start hour * 100) + (start minute) - * 'end' => the (end hour * 100) + (end minute) - * 'title' => the entry`s title - * 'content' => whatever shall be the content of the entry as a string - * ) - */ - public function getEntries() { - return $this->entries; - } - - /** - * deletes all entries of this column. So the only way to edit an entry is - * getting all entries with getEntries, edit this entry, eraseEntries() and - * addEntries(). Not very short, but at least it works. - * - * @return CalendarColumn - */ - public function eraseEntries() { - $this->entries = []; - return $this; - } - - /** - * Returns an array of calendar-entries, grouped by day and additionally grouped by same start and end - * if groupEntries(true) has been called. - * - * @return mixed the (double-)grouped entries - */ - public function getGroupedEntries() - { - if (empty($this->sorted_entries)) { - if ($this->isGrouped()) { - $this->sorted_entries = $this->sortAndGroupEntries(); - } else { - $this->sorted_entries = $this->sortEntries(); - } - } - - return $this->sorted_entries; - } - - /** - * sorts and groups entries and returns them - * only used by columns with grouped entries like instituteschedules - * - * @return array - */ - public function sortAndGroupEntries() - { - $day = $this->getTitle(); - - $entries_for_column = $this->getEntries(); - $result = []; - $new_entries = []; - - // 1st step - group all entries with the same duration - foreach ($entries_for_column as $entry_id => $entry) { - $new_entries[$entry['start'] .'_'. $entry['end']][] = $entry; - } - - $column = 0; - - // 2nd step - optimize the groups - while (sizeof($new_entries) > 0) { - $lstart = 2399; $lend = 0; - - foreach ($new_entries as $time => $grouped_entries) { - list($start, $end) = explode('_', $time); - if ($start < $lstart /*&& ($end - $start) >= ($lend - $lstart)*/ ) { - $lstart = $start; - $lend = $end; - } - } - - $result['col_'. $column][] = $new_entries[$lstart .'_'. $lend]; - unset($new_entries[$lstart .'_'. $lend]); - - $hit = true; - - while ($hit) { - $hit = false; - $hstart = 2399; $hend = 2399; - - // check, if there is something, that can be placed after - foreach ($new_entries as $time => $grouped_entries) { - list($start, $end) = explode('_', $time); - - if ( ($start >= $lend) && ($start < $hstart) ) { - $hstart = $start; - $hend = $end; - $hit = true; - } - } - - if ($hit) { - $lend = $hend; - $result['col_'. $column][] = $new_entries[$hstart .'_'. $hend]; - unset($new_entries[$hstart .'_'. $hend]); - } - } - - $column++; - } // 2nd step - - return $result; - - } - - /** - * sorts entries and returns them - * - * @return array - */ - public function sortEntries() - { - $entries_for_column = $this->getEntries(); - - $result = []; - $column = 0; - - // 2nd step - optimize the groups - while (sizeof($entries_for_column) > 0) { - $lstart = 2399; $lend = 0; $lkey = null; - - foreach ($entries_for_column as $entry_key => $entry) { - if ($entry['start'] < $lstart /*&& ($end - $start) >= ($lend - $lstart)*/ ) { - $lstart = $entry['start']; - $lend = $entry['end']; - $lkey = $entry_key; - } - } - - $result['col_'. $column][] = $entries_for_column[$lkey]; - unset($entries_for_column[$lkey]); - - $hit = true; - - while ($hit) { - $hit = false; - $hstart = 2399; $hend = 2399; $hkey = null; - - // check, if there is something, that can be placed after - foreach ($entries_for_column as $entry_key => $entry) { - if ( ($entry['start'] >= $lend) && ($entry['start'] < $hstart) ) { - // && (($end - $start) > ($hend - $hstart)) ) { - $hstart = $entry['start']; - $hend = $entry['end']; - $hkey = $entry_key; - $hit = true; - } - } - - if ($hit) { - $lend = $hend; - $result['col_'. $column][] = $entries_for_column[$hkey]; - unset($entries_for_column[$hkey]); - } - } - - $column++; - } // 2nd step - return $result; - - } - - /** - * returns a matrix that tells the number of entries for a given timeslot - * - * @return array - */ - public function getMatrix() { - $group_matrix = []; - foreach ($this->getGroupedEntries() as $groups) { - foreach ($groups as $group) { - if (isset($group[0]) && is_array($group[0])) { - $data = $group[0]; - } else { - $data = $group; - } - - for ($i = floor($data['start'] / 100); $i <= floor($data['end'] / 100); $i++) { - for ($j = 0; $j < 60; $j++) { - if (($i * 100) + $j >= $data['start'] && ($i * 100) + $j < $data['end']) { - if (!isset($group_matrix[$i * 100 + $j])) { - $group_matrix[$i * 100 + $j] = 0; - } - $group_matrix[$i * 100 + $j]++; - } - } - } - } - } - return $group_matrix; - } - - /** - * check, if a grouped view of the entries is requested - * - * @return bool true if grouped, false otherwise - */ - public function isGrouped() - { - return $this->grouped; - } - - /** - * Call this function th enable/disable the grouping of entries with the same start and end. - * - * @param bool $group optional, defaults to true - * @return void - */ - public function groupEntries($grouped = true) - { - $this->grouped = $grouped; - } - -} diff --git a/lib/calendar/CalendarColumn.php b/lib/calendar/CalendarColumn.php new file mode 100644 index 0000000..cc3abea --- /dev/null +++ b/lib/calendar/CalendarColumn.php @@ -0,0 +1,371 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @deprecated since Stud.IP 5.5 + */ + +class CalendarColumn +{ + protected static $number = 0; + protected $title = ""; + protected $id = ""; + public $entries = []; + protected $url = ""; + protected $grouped = false; + protected $sorted_entries = null; + + /** + * creates instance of type CalendarColumn + * + * @param string $id necessary if you want JavaScript enabled for this calendar + * @return CalendarColumn + */ + static public function create($id = null) { + $column = new CalendarColumn($id); + return $column; + } + + /** + * constructor + * + * @param string $id necessary if you want JavaScript enabled for this column + */ + public function __construct($id = null) { + $id !== null || $id = md5(uniqid("CalendarColumn_".self::$number++)); + $this->setId($id); + } + + /** + * returns the id of the column + * + * @return string + */ + public function getId() { + return $this->id; + } + + /** + * sets the id for this column, which is only necessary if you want + * Javascript to be enabled for this calendar + * + * @param string $id new id for this column + * @return CalendarColumn + */ + public function setId($id) { + $this->id = $id; + return $this; + } + + /** + * sets a title like "monday" for this column, which will be displayed in the calendar + * + * @param string $new_title new title + * @return CalendarColumn + */ + public function setTitle($new_title) { + $this->title = $new_title; + return $this; + } + + /** + * returns the title of this column like "monday" + * + * @return string title of column + */ + public function getTitle() { + return $this->title; + } + + /** + * sets the url to be directed to when clicking on the title of the column. + * Usually this is a single-day-view of the calendar. + * + * @param string $new_url an url + * @return CalendarColumn + */ + public function setURL($new_url) { + $this->url = $new_url; + return $this; + } + + /** + * returns the URL of the column (see setURL) + * + * @return string an url + */ + public function getURL() { + return $this->url; + } + + /** + * adds a new entry in the column. The entry needs to be an associative array + * with parameters as follows: + * + * @param array $entry_array associative array for an entry in the column like + * array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) + */ + public function addEntry($entry_array) { + if (!isset($entry_array['start']) || !isset($entry_array['end']) + || !isset($entry_array['title']) ) { + throw new InvalidArgumentException('The entry '. print_r($entry_array, true) .' does not follow the specifications!'); + } else { + $this->entries[] = $entry_array; + } + return $this; + } + + /** + * adds many entries to the column. For the syntax of an entry see addEntry() + * + * @param array $entries_array + * @return CalendarColumn + */ + public function addEntries($entries_array = []) { + foreach ($entries_array as $entry_array) { + $this->addEntry($entry_array); + } + return $this; + } + + /** + * returns all entries of this column + * + * @return array of arrays like + * array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) + */ + public function getEntries() { + return $this->entries; + } + + /** + * deletes all entries of this column. So the only way to edit an entry is + * getting all entries with getEntries, edit this entry, eraseEntries() and + * addEntries(). Not very short, but at least it works. + * + * @return CalendarColumn + */ + public function eraseEntries() { + $this->entries = []; + return $this; + } + + /** + * Returns an array of calendar-entries, grouped by day and additionally grouped by same start and end + * if groupEntries(true) has been called. + * + * @return mixed the (double-)grouped entries + */ + public function getGroupedEntries() + { + if (empty($this->sorted_entries)) { + if ($this->isGrouped()) { + $this->sorted_entries = $this->sortAndGroupEntries(); + } else { + $this->sorted_entries = $this->sortEntries(); + } + } + + return $this->sorted_entries; + } + + /** + * sorts and groups entries and returns them + * only used by columns with grouped entries like instituteschedules + * + * @return array + */ + public function sortAndGroupEntries() + { + $day = $this->getTitle(); + + $entries_for_column = $this->getEntries(); + $result = []; + $new_entries = []; + + // 1st step - group all entries with the same duration + foreach ($entries_for_column as $entry_id => $entry) { + $new_entries[$entry['start'] .'_'. $entry['end']][] = $entry; + } + + $column = 0; + + // 2nd step - optimize the groups + while (sizeof($new_entries) > 0) { + $lstart = 2399; $lend = 0; + + foreach ($new_entries as $time => $grouped_entries) { + list($start, $end) = explode('_', $time); + if ($start < $lstart /*&& ($end - $start) >= ($lend - $lstart)*/ ) { + $lstart = $start; + $lend = $end; + } + } + + $result['col_'. $column][] = $new_entries[$lstart .'_'. $lend]; + unset($new_entries[$lstart .'_'. $lend]); + + $hit = true; + + while ($hit) { + $hit = false; + $hstart = 2399; $hend = 2399; + + // check, if there is something, that can be placed after + foreach ($new_entries as $time => $grouped_entries) { + list($start, $end) = explode('_', $time); + + if ( ($start >= $lend) && ($start < $hstart) ) { + $hstart = $start; + $hend = $end; + $hit = true; + } + } + + if ($hit) { + $lend = $hend; + $result['col_'. $column][] = $new_entries[$hstart .'_'. $hend]; + unset($new_entries[$hstart .'_'. $hend]); + } + } + + $column++; + } // 2nd step + + return $result; + + } + + /** + * sorts entries and returns them + * + * @return array + */ + public function sortEntries() + { + $entries_for_column = $this->getEntries(); + + $result = []; + $column = 0; + + // 2nd step - optimize the groups + while (sizeof($entries_for_column) > 0) { + $lstart = 2399; $lend = 0; $lkey = null; + + foreach ($entries_for_column as $entry_key => $entry) { + if ($entry['start'] < $lstart /*&& ($end - $start) >= ($lend - $lstart)*/ ) { + $lstart = $entry['start']; + $lend = $entry['end']; + $lkey = $entry_key; + } + } + + $result['col_'. $column][] = $entries_for_column[$lkey]; + unset($entries_for_column[$lkey]); + + $hit = true; + + while ($hit) { + $hit = false; + $hstart = 2399; $hend = 2399; $hkey = null; + + // check, if there is something, that can be placed after + foreach ($entries_for_column as $entry_key => $entry) { + if ( ($entry['start'] >= $lend) && ($entry['start'] < $hstart) ) { + // && (($end - $start) > ($hend - $hstart)) ) { + $hstart = $entry['start']; + $hend = $entry['end']; + $hkey = $entry_key; + $hit = true; + } + } + + if ($hit) { + $lend = $hend; + $result['col_'. $column][] = $entries_for_column[$hkey]; + unset($entries_for_column[$hkey]); + } + } + + $column++; + } // 2nd step + return $result; + + } + + /** + * returns a matrix that tells the number of entries for a given timeslot + * + * @return array + */ + public function getMatrix() { + $group_matrix = []; + foreach ($this->getGroupedEntries() as $groups) { + foreach ($groups as $group) { + if (isset($group[0]) && is_array($group[0])) { + $data = $group[0]; + } else { + $data = $group; + } + + for ($i = floor($data['start'] / 100); $i <= floor($data['end'] / 100); $i++) { + for ($j = 0; $j < 60; $j++) { + if (($i * 100) + $j >= $data['start'] && ($i * 100) + $j < $data['end']) { + if (!isset($group_matrix[$i * 100 + $j])) { + $group_matrix[$i * 100 + $j] = 0; + } + $group_matrix[$i * 100 + $j]++; + } + } + } + } + } + return $group_matrix; + } + + /** + * check, if a grouped view of the entries is requested + * + * @return bool true if grouped, false otherwise + */ + public function isGrouped() + { + return $this->grouped; + } + + /** + * Call this function th enable/disable the grouping of entries with the same start and end. + * + * @param bool $group optional, defaults to true + * @return void + */ + public function groupEntries($grouped = true) + { + $this->grouped = $grouped; + } + +} diff --git a/lib/calendar/CalendarView.class.php b/lib/calendar/CalendarView.class.php deleted file mode 100644 index 3d89ef1..0000000 --- a/lib/calendar/CalendarView.class.php +++ /dev/null @@ -1,344 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * Kind of bean class for the calendar view. - * - * Example of use: - * - * // create a calendar-view and add a column - * $plan = new CalendarView(); - * $plan->addColumn(_('Spalte 1')) - * ->addEntry(array( - * 'id' => 1, - * 'color' => '#5C2D64', - * 'start' => '0930', - * 'end' => '1100', - * 'title' => 'Mathe 2', - * 'content' => 'Die Mathematiker kreiden sich mal wieder was an.' - * ) - * ); - * - * // display the calendar (containing one column) - * print $plan->render(); - * - * @since 2.0 - * - * @deprecated since Stud.IP 5.5 - */ - -class CalendarView -{ - - protected $entries = []; - protected $entry_columns = []; - protected $height = 40; - protected $grouped = false; - protected $start_hour = 8; - protected $end_hour = 21; - protected $insertFunction = ""; - protected $templates = []; - protected $read_only = false; - - protected static $number_of_instances = 1; - protected $view_id; - - public $sorted_entries = []; - - - /** - * You need to pass an instance of this class to the template. The constructor - * expects an array of entries of the following type: - * array( - * $day_number => array(array ( - * 'color' => the color in hex (css-like, without the #) - * 'start' => the (start hour * 100) + (start minute) - * 'end' => the (end hour * 100) + (end minute) - * //'day' => day of week (0 = Sunday, ... , 6 = Saturday) - * 'title' => the entry`s title - * 'content' => whatever shall be the content of the entry as a string - * ) ...) ... - * ) - * - * @param mixed $entries an array of entries (see above) - * @param string $controller the name of the controller. Used to create links. - */ - public function __construct($entries = []) - { - if (!is_array($entries)) { - throw new Exception('You need to pass some entries to the CalendarView!'); - } - $this->view_id = self::$number_of_instances++; - $this->checkEntries($entries); - $this->entries = $entries; - } - - /** - * set the height for one hour. This value is used to calculate the whole height of the schedule. - * - * @param int $entry_height the height of one hour in the schedule - */ - public function setHeight($height) - { - $this->height = $height; - } - - /** - * set the range of hours to be displayed. the start_hour has to be smaller than the end_hour - * - * @param int $start_hour the hour to start displaying at - * @param int $end_hour the hour to stop displaying at - */ - public function setRange($start_hour, $end_hour) - { - $this->start_hour = $start_hour; - $this->end_hour = $end_hour; - } - - /** - * does some plausability checks on an array of calendar-entries - * - * @param mixed $entries an array of calendar-entries - * - * @return bool false if check failed, true otherwise - */ - protected function checkEntries($entries) - { - foreach ($entries as $column) { - if (!$column instanceof CalendarColumn) { - throw new Exception('A column of the entries in the CalenarView is not of type CalendarColumn.'); - } - } - return true; - } - - /** - * adds a new column to this view. All entries created with addEntry will be - * added to this column. - * - * @param string $title like "monday" to be displayed on top of the column - * @param string $url to be called when clicked on the title of the column - * @param string $id any kind of id of the column - * @return CalendarView - */ - public function addColumn($title, $url = "", $id = null) - { - $this->entries[] = CalendarColumn::create($id) - ->setTitle($title) - ->setURL($url); - return $this; - } - - - /** - * adds a new entry to the last current column. The entry needs to be an - * associative array with parameters as follows: - * @param array $entry_array: associative array for an entry in the column like - * array ( - * 'color' => the color in hex (css-like, without the #) - * 'start' => the (start hour * 100) + (start minute) - * 'end' => the (end hour * 100) + (end minute) - * 'title' => the entry`s title - * 'content' => whatever shall be the content of the entry as a string - * ) - * @return CalendarView - */ - public function addEntry($entry_array) - { - if (count($this->entries)) { - $this->entries[count($this->entries)-1]->addEntry($entry_array); - } else { - throw new InvalidArgumentException(_("Es existiert noch keine Spalte in der Ansicht, zu der der Eintrag hinzugefügt werden kann.")); - } - return $this; - } - - - /** - * Call this function to enable/disable the grouping of entries with the same start and end. - * - * @param bool $group optional, defaults to true - */ - public function groupEntries($grouped = true) - { - $this->grouped = $grouped; - foreach($this->getColumns() as $entry_column) { - $entry_column->groupEntries(); - } - } - - /** - * When a column is clicked at no entry this function is called. - * First the templates generates a new entry at the clicked time. Then this - * js-function is called which gets the parameters - * "function (new_entry_dom_object, column_id, hour) { ... }" - * with new_entry_dom_object: a real dom-object of the div of the entry - * column_id: id of the column - * hour: integer number from 0 to 23 - * If js_function_object is an empty string, nothing will be done. - * - * @param string $js_function_object name of js-function or anonymous js-function - * @return CalendarView - */ - public function setInsertFunction($js_function_object) - { - $this->insertFunction = $js_function_object; - return $this; - } - - /** - * outputs the CalendarView with all (grouped) dates in columns. - * - * @param array $params you can pass some additional variables to the templates - * - * @return string - */ - public function render($params = []) - { - $style_parameters = [ - 'whole_height' => $this->getOverallHeight(), - 'entry_height' => $this->getHeight() - ]; - $factory = new Flexi\Factory(dirname(__file__).'/../../app/views'); - PageLayout::addStyle($factory->render('calendar/schedule/stylesheet', $style_parameters)); - - $template = $GLOBALS['template_factory']->open("calendar/calendar_view.php"); - $template->set_attribute("calendar_view", $this); - $template->set_attribute("view_id", $this->view_id); - return $template->render($params); - } - - - /* * * * * * * * * * * * * * * - * * * G E T T E R S * * * - * * * * * * * * * * * * * * */ - - /** - * Returns an array of calendar-entries, grouped by day and additionally grouped by same start and end - * if groupEntries(true) has been called. - * - * @return mixed the (double-)grouped entries - */ - public function getEntries() - { - $this->sorted_entries = []; - foreach ($this->getColumns() as $entry_column) { - $this->sorted_entries['day_'. $entry_column->getId()] = $entry_column->getGroupedEntries(); - } - return $this->sorted_entries; - } - - /** - * Returns an array where for each hour the number of concurrent entries is denoted. - * Used by the calendar to display the entries in parallel. - * - * @return mixed concurrent entries at each hour - */ - public function getMatrix() - { - $matrix = []; - foreach ($this->getColumns() as $day => $entry_column) { - $matrix['day_'.$day] = $entry_column->getMatrix(); - } - return $matrix; - } - - - /** - * returns the previously set start- and end-hour, denoting the - * range of entries to be displayed in the current calendar-view - * - * @return array consisting of the start and end hour - */ - public function getRange() - { - return [$this->start_hour, $this->end_hour]; - } - - /** - * the calendar can be used in two modes. Use this function to check, - * if the grouping-mode is enabled for the current calendar-view - * - * @return bool true if grouped, false otherwise - */ - public function isGrouped() - { - return $this->grouped; - } - - /** - * returns the previously set height for one hour - * - * @return mixed the height - */ - public function getHeight() - { - return $this->height; - } - - /** - * returns the overall height of the calendar - * - * @return mixed the overall height - */ - public function getOverallHeight() - { - return $this->height * ($this->end_hour - $this->start_hour) + $this->height - + 2 + ($this->end_hour - $this->start_hour) * 2; - } - - /** - * returns the previously set javasscript insert-function - * - * @return string name of js-function or anonymous js-function - */ - public function getInsertFunction() { - return $this->insertFunction; - } - - /** - * returns all columns of the calendar-view - * - * @return array of CalendarColumn - */ - public function getColumns() { - return $this->entries; - } - - /** - * Set the read-only status of the calendar - * - * @param bool $readonly true to make it read only, false otherwise - * - * @return void - */ - public function setReadOnly($readonly = true) - { - $this->read_only = $readonly; - } - - /** - * Return the read-only status of this calendar - * - * @return bool the read-only status of this calendar - */ - public function getReadOnly() { - return $this->read_only; - } - -} diff --git a/lib/calendar/CalendarView.php b/lib/calendar/CalendarView.php new file mode 100644 index 0000000..3d89ef1 --- /dev/null +++ b/lib/calendar/CalendarView.php @@ -0,0 +1,344 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * Kind of bean class for the calendar view. + * + * Example of use: + * + * // create a calendar-view and add a column + * $plan = new CalendarView(); + * $plan->addColumn(_('Spalte 1')) + * ->addEntry(array( + * 'id' => 1, + * 'color' => '#5C2D64', + * 'start' => '0930', + * 'end' => '1100', + * 'title' => 'Mathe 2', + * 'content' => 'Die Mathematiker kreiden sich mal wieder was an.' + * ) + * ); + * + * // display the calendar (containing one column) + * print $plan->render(); + * + * @since 2.0 + * + * @deprecated since Stud.IP 5.5 + */ + +class CalendarView +{ + + protected $entries = []; + protected $entry_columns = []; + protected $height = 40; + protected $grouped = false; + protected $start_hour = 8; + protected $end_hour = 21; + protected $insertFunction = ""; + protected $templates = []; + protected $read_only = false; + + protected static $number_of_instances = 1; + protected $view_id; + + public $sorted_entries = []; + + + /** + * You need to pass an instance of this class to the template. The constructor + * expects an array of entries of the following type: + * array( + * $day_number => array(array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * //'day' => day of week (0 = Sunday, ... , 6 = Saturday) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) ...) ... + * ) + * + * @param mixed $entries an array of entries (see above) + * @param string $controller the name of the controller. Used to create links. + */ + public function __construct($entries = []) + { + if (!is_array($entries)) { + throw new Exception('You need to pass some entries to the CalendarView!'); + } + $this->view_id = self::$number_of_instances++; + $this->checkEntries($entries); + $this->entries = $entries; + } + + /** + * set the height for one hour. This value is used to calculate the whole height of the schedule. + * + * @param int $entry_height the height of one hour in the schedule + */ + public function setHeight($height) + { + $this->height = $height; + } + + /** + * set the range of hours to be displayed. the start_hour has to be smaller than the end_hour + * + * @param int $start_hour the hour to start displaying at + * @param int $end_hour the hour to stop displaying at + */ + public function setRange($start_hour, $end_hour) + { + $this->start_hour = $start_hour; + $this->end_hour = $end_hour; + } + + /** + * does some plausability checks on an array of calendar-entries + * + * @param mixed $entries an array of calendar-entries + * + * @return bool false if check failed, true otherwise + */ + protected function checkEntries($entries) + { + foreach ($entries as $column) { + if (!$column instanceof CalendarColumn) { + throw new Exception('A column of the entries in the CalenarView is not of type CalendarColumn.'); + } + } + return true; + } + + /** + * adds a new column to this view. All entries created with addEntry will be + * added to this column. + * + * @param string $title like "monday" to be displayed on top of the column + * @param string $url to be called when clicked on the title of the column + * @param string $id any kind of id of the column + * @return CalendarView + */ + public function addColumn($title, $url = "", $id = null) + { + $this->entries[] = CalendarColumn::create($id) + ->setTitle($title) + ->setURL($url); + return $this; + } + + + /** + * adds a new entry to the last current column. The entry needs to be an + * associative array with parameters as follows: + * @param array $entry_array: associative array for an entry in the column like + * array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) + * @return CalendarView + */ + public function addEntry($entry_array) + { + if (count($this->entries)) { + $this->entries[count($this->entries)-1]->addEntry($entry_array); + } else { + throw new InvalidArgumentException(_("Es existiert noch keine Spalte in der Ansicht, zu der der Eintrag hinzugefügt werden kann.")); + } + return $this; + } + + + /** + * Call this function to enable/disable the grouping of entries with the same start and end. + * + * @param bool $group optional, defaults to true + */ + public function groupEntries($grouped = true) + { + $this->grouped = $grouped; + foreach($this->getColumns() as $entry_column) { + $entry_column->groupEntries(); + } + } + + /** + * When a column is clicked at no entry this function is called. + * First the templates generates a new entry at the clicked time. Then this + * js-function is called which gets the parameters + * "function (new_entry_dom_object, column_id, hour) { ... }" + * with new_entry_dom_object: a real dom-object of the div of the entry + * column_id: id of the column + * hour: integer number from 0 to 23 + * If js_function_object is an empty string, nothing will be done. + * + * @param string $js_function_object name of js-function or anonymous js-function + * @return CalendarView + */ + public function setInsertFunction($js_function_object) + { + $this->insertFunction = $js_function_object; + return $this; + } + + /** + * outputs the CalendarView with all (grouped) dates in columns. + * + * @param array $params you can pass some additional variables to the templates + * + * @return string + */ + public function render($params = []) + { + $style_parameters = [ + 'whole_height' => $this->getOverallHeight(), + 'entry_height' => $this->getHeight() + ]; + $factory = new Flexi\Factory(dirname(__file__).'/../../app/views'); + PageLayout::addStyle($factory->render('calendar/schedule/stylesheet', $style_parameters)); + + $template = $GLOBALS['template_factory']->open("calendar/calendar_view.php"); + $template->set_attribute("calendar_view", $this); + $template->set_attribute("view_id", $this->view_id); + return $template->render($params); + } + + + /* * * * * * * * * * * * * * * + * * * G E T T E R S * * * + * * * * * * * * * * * * * * */ + + /** + * Returns an array of calendar-entries, grouped by day and additionally grouped by same start and end + * if groupEntries(true) has been called. + * + * @return mixed the (double-)grouped entries + */ + public function getEntries() + { + $this->sorted_entries = []; + foreach ($this->getColumns() as $entry_column) { + $this->sorted_entries['day_'. $entry_column->getId()] = $entry_column->getGroupedEntries(); + } + return $this->sorted_entries; + } + + /** + * Returns an array where for each hour the number of concurrent entries is denoted. + * Used by the calendar to display the entries in parallel. + * + * @return mixed concurrent entries at each hour + */ + public function getMatrix() + { + $matrix = []; + foreach ($this->getColumns() as $day => $entry_column) { + $matrix['day_'.$day] = $entry_column->getMatrix(); + } + return $matrix; + } + + + /** + * returns the previously set start- and end-hour, denoting the + * range of entries to be displayed in the current calendar-view + * + * @return array consisting of the start and end hour + */ + public function getRange() + { + return [$this->start_hour, $this->end_hour]; + } + + /** + * the calendar can be used in two modes. Use this function to check, + * if the grouping-mode is enabled for the current calendar-view + * + * @return bool true if grouped, false otherwise + */ + public function isGrouped() + { + return $this->grouped; + } + + /** + * returns the previously set height for one hour + * + * @return mixed the height + */ + public function getHeight() + { + return $this->height; + } + + /** + * returns the overall height of the calendar + * + * @return mixed the overall height + */ + public function getOverallHeight() + { + return $this->height * ($this->end_hour - $this->start_hour) + $this->height + + 2 + ($this->end_hour - $this->start_hour) * 2; + } + + /** + * returns the previously set javasscript insert-function + * + * @return string name of js-function or anonymous js-function + */ + public function getInsertFunction() { + return $this->insertFunction; + } + + /** + * returns all columns of the calendar-view + * + * @return array of CalendarColumn + */ + public function getColumns() { + return $this->entries; + } + + /** + * Set the read-only status of the calendar + * + * @param bool $readonly true to make it read only, false otherwise + * + * @return void + */ + public function setReadOnly($readonly = true) + { + $this->read_only = $readonly; + } + + /** + * Return the read-only status of this calendar + * + * @return bool the read-only status of this calendar + */ + public function getReadOnly() { + return $this->read_only; + } + +} diff --git a/lib/calendar/CalendarWeekView.class.php b/lib/calendar/CalendarWeekView.class.php deleted file mode 100644 index c1e0f24..0000000 --- a/lib/calendar/CalendarWeekView.class.php +++ /dev/null @@ -1,124 +0,0 @@ - & Rasmus Fuhse - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * Kind of bean class for the calendar view. - * - * @since 2.0 - * - * @deprecated since Stud.IP 5.5 - */ - -class CalendarWeekView extends CalendarView -{ - protected $days = [1,2,3,4,5]; - protected $context; - - - /** - * You need to pass an instance of this class to the template. The constructor - * expects an array of entries of the following type: - * array( - * $day_number => array(array ( - * 'color' => the color in hex (css-like, without the #) - * 'start' => the (start hour * 100) + (start minute) - * 'end' => the (end hour * 100) + (end minute) - * //'day' => day of week (0 = Sunday, ... , 6 = Saturday) - * 'title' => the entry`s title - * 'content' => whatever shall be the content of the entry as a string - * ) ...) ... - * ) - * - * @param mixed $entries an array of entries (see above) - * @param string $controller the name of the controller. Used to create links. - */ - public function __construct($entries, $controller) - { - parent::__construct($entries); - $this->context = $controller; - } - - /** - * Call this function th enable/disable the grouping of entries with the same start and end. - * - * @param bool $group optional, defaults to true - */ - public function groupEntries($grouped = true) - { - $this->grouped = $grouped; - foreach($this->entries as $entry_column) { - $entry_column->groupEntries(); - } - } - - /* * * * * * * * * * * * * * * - * * * G E T T E R S * * * - * * * * * * * * * * * * * * */ - - /** - * @return mixed the context - */ - public function getContext() - { - return $this->context; - } - - /** - * @return mixed the days - */ - public function getDays() - { - return $this->days; - } - - /** - * returns the previously set javasscript insert-function only - * if read_only is not set. - * - * @return string name of js-function or anonymous js-function - */ - public function getInsertFunction() { - if (!$this->read_only) { - return parent::getInsertFunction(); - } - - return false; - } - - /** - * returns all columns of the calendar-view nad removes the url if - * read_only is set - * - * @return array of CalendarColumn - */ - public function getColumns() { - // remove links and urls if calendar-view is read-only - if ($this->read_only) { - foreach ($this->entries as $column) { - $column->setURL(false); - foreach ($column->entries as $key => $entry) { - unset($column->entries[$key]['url']); - unset($column->entries[$key]['onClick']); - unset($column->entries[$key]['icons']); - } - } - } - - return parent::getColumns(); - } -} diff --git a/lib/calendar/CalendarWeekView.php b/lib/calendar/CalendarWeekView.php new file mode 100644 index 0000000..c1e0f24 --- /dev/null +++ b/lib/calendar/CalendarWeekView.php @@ -0,0 +1,124 @@ + & Rasmus Fuhse + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * Kind of bean class for the calendar view. + * + * @since 2.0 + * + * @deprecated since Stud.IP 5.5 + */ + +class CalendarWeekView extends CalendarView +{ + protected $days = [1,2,3,4,5]; + protected $context; + + + /** + * You need to pass an instance of this class to the template. The constructor + * expects an array of entries of the following type: + * array( + * $day_number => array(array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * //'day' => day of week (0 = Sunday, ... , 6 = Saturday) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) ...) ... + * ) + * + * @param mixed $entries an array of entries (see above) + * @param string $controller the name of the controller. Used to create links. + */ + public function __construct($entries, $controller) + { + parent::__construct($entries); + $this->context = $controller; + } + + /** + * Call this function th enable/disable the grouping of entries with the same start and end. + * + * @param bool $group optional, defaults to true + */ + public function groupEntries($grouped = true) + { + $this->grouped = $grouped; + foreach($this->entries as $entry_column) { + $entry_column->groupEntries(); + } + } + + /* * * * * * * * * * * * * * * + * * * G E T T E R S * * * + * * * * * * * * * * * * * * */ + + /** + * @return mixed the context + */ + public function getContext() + { + return $this->context; + } + + /** + * @return mixed the days + */ + public function getDays() + { + return $this->days; + } + + /** + * returns the previously set javasscript insert-function only + * if read_only is not set. + * + * @return string name of js-function or anonymous js-function + */ + public function getInsertFunction() { + if (!$this->read_only) { + return parent::getInsertFunction(); + } + + return false; + } + + /** + * returns all columns of the calendar-view nad removes the url if + * read_only is set + * + * @return array of CalendarColumn + */ + public function getColumns() { + // remove links and urls if calendar-view is read-only + if ($this->read_only) { + foreach ($this->entries as $column) { + $column->setURL(false); + foreach ($column->entries as $key => $entry) { + unset($column->entries[$key]['url']); + unset($column->entries[$key]['onClick']); + unset($column->entries[$key]['icons']); + } + } + } + + return parent::getColumns(); + } +} diff --git a/lib/classes/AdminCourseFilter.class.php b/lib/classes/AdminCourseFilter.class.php deleted file mode 100644 index f643e09..0000000 --- a/lib/classes/AdminCourseFilter.class.php +++ /dev/null @@ -1,220 +0,0 @@ -cfg->getValue("LECTURESHIP_FILTER")) { - * $filter->query->join('lehrauftrag', 'seminare.Seminar_id = lehrauftrag.seminar_id'); - * } - * } - * - * Within this method you alter the public $filter->query object. That query object is of type SQLQuery. - * - */ -class AdminCourseFilter -{ - static protected $instance = null; - - /** @var SQLQuery|null */ - public $query = null; - public $max_show_courses = 500; - public $settings = []; - - /** - * returns an AdminCourseFilter singleton object - * @return AdminCourseFilter or derived-class object - */ - static public function get($reset_settings = false) - { - if (!self::$instance) { - $class = get_called_class(); - self::$instance = new $class($reset_settings); - } - return self::$instance; - } - - /** - * Constructor of the singleton-object. - */ - public function __construct() - { - $this->initSettings(); - } - - protected function initSettings() - { - $this->query = SQLQuery::table('seminare'); - $this->query->join('sem_types', 'sem_types', 'sem_types.id = seminare.status'); - $this->query->join('sem_classes', 'sem_classes', 'sem_classes.id = sem_types.class'); - $this->query->where("sem_classes.studygroup_mode = '0'"); - $this->query->groupBy('seminare.Seminar_id'); - - if ($GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT) { - $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'"); - $this->query->join('teachers', 'auth_user_md5', 'teachers.user_id = teachers_su.user_id'); - $this->query->where( - 'search', - "(seminare.name LIKE :search OR seminare.VeranstaltungsNummer LIKE :search OR seminare.untertitel LIKE :search OR CONCAT(teachers.Vorname, ' ', teachers.Nachname) LIKE :search)", - ['search' => '%'.$GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT.'%'] - ); - } - if (Request::option('course_id')) { - $this->query->where('course_id', 'seminare.Seminar_id = :course_id', ['course_id' => Request::option('course_id')]); - } - $inst_ids = []; - - if ( - !$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT - || $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === 'all' - ) { - $inst = new SimpleCollection(Institute::getMyInstitutes($GLOBALS['user']->id)); - $inst_ids = $inst->map(function ($a) { - return $a['Institut_id']; - }); - } else { - - $inst_id = $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT; - $inst_ids[] = $inst_id; - - if ($GLOBALS['user']->cfg->MY_INSTITUTES_INCLUDE_CHILDREN) { - $inst = Institute::find($inst_id); - if ($inst && $inst->isFaculty()) { - foreach ($inst->sub_institutes->pluck('Institut_id') as $institut_id) { - $inst_ids[] = $institut_id; - } - } - } - } - - if (Config::get()->ALLOW_ADMIN_RELATED_INST) { - $this->query->where('seminar_inst', 'EXISTS (SELECT 1 FROM seminar_inst WHERE seminar_id = seminare.Seminar_id AND institut_id IN (:institut_ids))'); - } else { - $this->query->where("seminar_inst", "seminare.institut_id IN (:institut_ids)"); - } - $this->query->parameter('institut_ids', $inst_ids); - - if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE) { - $this->query->join('semester_courses', 'semester_courses.course_id = seminare.Seminar_id', 'LEFT JOIN'); - $this->query->where('semester_id', '(semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)', [ - 'semester_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE - ]); - } - - if ($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER && $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER !== 'all') { - if (str_contains($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER, '_')) { - list($sem_class_id, $sem_type_id) = explode('_', $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER); - $this->query->where('course_type', 'seminare.status = :course_type', ['course_type' => $sem_type_id]); - } else { - //sem class - $this->query->where('course_class', 'sem_types.class = :course_class', [ - 'course_class' => $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER - ]); - } - - } - - if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL) { - $this->query->join('mvv_lvgruppe_seminar', '`mvv_lvgruppe_seminar`.`seminar_id` = `seminare`.`Seminar_id`'); - $this->query->join('mvv_lvgruppe_modulteil', '`mvv_lvgruppe_modulteil`.`lvgruppe_id` = `mvv_lvgruppe_seminar`.`lvgruppe_id`'); - $this->query->join('mvv_modulteil', '`mvv_modulteil`.`modulteil_id` = `mvv_lvgruppe_modulteil`.`modulteil_id`'); - $this->query->join('mvv_modul', '`mvv_modul`.`modul_id` = `mvv_modulteil`.`modul_id`'); - $this->query->join('mvv_stgteilabschnitt_modul', '`mvv_stgteilabschnitt_modul`.`modul_id` = `mvv_modul`.`modul_id`'); - $this->query->join('mvv_stgteilabschnitt', '`mvv_stgteilabschnitt`.`abschnitt_id` = `mvv_stgteilabschnitt_modul`.`abschnitt_id`'); - $this->query->join('mvv_stgteilversion', '`mvv_stgteilversion`.`version_id` = `mvv_stgteilabschnitt`.`version_id`'); - $this->query->where('stgteil', 'mvv_stgteilversion.stgteil_id = :stgteil_id', [ - 'stgteil_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL - ]); - } - - if ($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER) { - $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'"); - $this->query->where( - 'teacher_filter', - "teachers_su.user_id = :teacher_id", - ['teacher_id' => $GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER] - ); - } - - - - $datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS; - foreach ($datafields_filters as $datafield_id => $value) { - $this->query->join('de_'.$datafield_id, 'datafields_entries', 'de_'.$datafield_id.'.range_id = seminare.Seminar_id AND `de_'.$datafield_id.'`.datafield_id = :de_'.$datafield_id.'_id'); - $this->query->where('de_' . $datafield_id . '_contents', 'de_' . $datafield_id . '.`content` LIKE :de_' . $datafield_id . '_content', - [ - 'de_' . $datafield_id . '_id' => $datafield_id, - 'de_' . $datafield_id . '_content' => '%' . $value . '%' - ]); - } - } - - /** - * Returns the data of the resultset of the AdminCourseFilter. - * Also saves the settings in the session. - * Note that a notification AdminCourseFilterWillQuery will be posted, before the result is computed. - * Plugins may register at this event to fully alter this AdminCourseFilter-object and so the resultset. - * @return array associative array with seminar_ids as keys and seminar-data-arrays as values. - */ - public function getCourses() - { - NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this); - return $this->query->fetchAll(Course::class); - } - - /** - * @return integer number of courses that this filter would return - */ - public function countCourses() - { - NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this); - return $this->query->count(); - } - - /** - * @param int|null $limit - * @return Course[] - */ - public function fetchCourses(?int $limit = null): array - { - NotificationCenter::postNotification('AdminCourseFilterWillQuery', $this); - return $this->query->fetchAll(Course::class, $limit); - } - - /** - * Returns the data of the resultset of the AdminCourseFilter. - * - * @param string $order_by possible values name or number - * - * Note that a notification AdminCourseFilterWillQuery will be posted, before the result is computed. - * Plugins may register at this event to fully alter this AdminCourseFilter-object and so the resultset. - * @return array associative array with seminar_ids as keys and seminar-data-arrays as values. - */ - public function getCoursesForAdminWidget(string $order_by = 'name') - { - try { - $order = 'seminare.name'; - if ($order_by === 'number') { - $order = 'seminare.veranstaltungsnummer, seminare.name'; - } - $this->query->orderBy($order); - return $this->fetchCourses($this->max_show_courses); - } catch (OverflowException $e) { - return []; - } - } - -} diff --git a/lib/classes/AdminCourseFilter.php b/lib/classes/AdminCourseFilter.php new file mode 100644 index 0000000..f643e09 --- /dev/null +++ b/lib/classes/AdminCourseFilter.php @@ -0,0 +1,220 @@ +cfg->getValue("LECTURESHIP_FILTER")) { + * $filter->query->join('lehrauftrag', 'seminare.Seminar_id = lehrauftrag.seminar_id'); + * } + * } + * + * Within this method you alter the public $filter->query object. That query object is of type SQLQuery. + * + */ +class AdminCourseFilter +{ + static protected $instance = null; + + /** @var SQLQuery|null */ + public $query = null; + public $max_show_courses = 500; + public $settings = []; + + /** + * returns an AdminCourseFilter singleton object + * @return AdminCourseFilter or derived-class object + */ + static public function get($reset_settings = false) + { + if (!self::$instance) { + $class = get_called_class(); + self::$instance = new $class($reset_settings); + } + return self::$instance; + } + + /** + * Constructor of the singleton-object. + */ + public function __construct() + { + $this->initSettings(); + } + + protected function initSettings() + { + $this->query = SQLQuery::table('seminare'); + $this->query->join('sem_types', 'sem_types', 'sem_types.id = seminare.status'); + $this->query->join('sem_classes', 'sem_classes', 'sem_classes.id = sem_types.class'); + $this->query->where("sem_classes.studygroup_mode = '0'"); + $this->query->groupBy('seminare.Seminar_id'); + + if ($GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT) { + $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'"); + $this->query->join('teachers', 'auth_user_md5', 'teachers.user_id = teachers_su.user_id'); + $this->query->where( + 'search', + "(seminare.name LIKE :search OR seminare.VeranstaltungsNummer LIKE :search OR seminare.untertitel LIKE :search OR CONCAT(teachers.Vorname, ' ', teachers.Nachname) LIKE :search)", + ['search' => '%'.$GLOBALS['user']->cfg->ADMIN_COURSES_SEARCHTEXT.'%'] + ); + } + if (Request::option('course_id')) { + $this->query->where('course_id', 'seminare.Seminar_id = :course_id', ['course_id' => Request::option('course_id')]); + } + $inst_ids = []; + + if ( + !$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT + || $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT === 'all' + ) { + $inst = new SimpleCollection(Institute::getMyInstitutes($GLOBALS['user']->id)); + $inst_ids = $inst->map(function ($a) { + return $a['Institut_id']; + }); + } else { + + $inst_id = $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT; + $inst_ids[] = $inst_id; + + if ($GLOBALS['user']->cfg->MY_INSTITUTES_INCLUDE_CHILDREN) { + $inst = Institute::find($inst_id); + if ($inst && $inst->isFaculty()) { + foreach ($inst->sub_institutes->pluck('Institut_id') as $institut_id) { + $inst_ids[] = $institut_id; + } + } + } + } + + if (Config::get()->ALLOW_ADMIN_RELATED_INST) { + $this->query->where('seminar_inst', 'EXISTS (SELECT 1 FROM seminar_inst WHERE seminar_id = seminare.Seminar_id AND institut_id IN (:institut_ids))'); + } else { + $this->query->where("seminar_inst", "seminare.institut_id IN (:institut_ids)"); + } + $this->query->parameter('institut_ids', $inst_ids); + + if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE) { + $this->query->join('semester_courses', 'semester_courses.course_id = seminare.Seminar_id', 'LEFT JOIN'); + $this->query->where('semester_id', '(semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)', [ + 'semester_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE + ]); + } + + if ($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER && $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER !== 'all') { + if (str_contains($GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER, '_')) { + list($sem_class_id, $sem_type_id) = explode('_', $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER); + $this->query->where('course_type', 'seminare.status = :course_type', ['course_type' => $sem_type_id]); + } else { + //sem class + $this->query->where('course_class', 'sem_types.class = :course_class', [ + 'course_class' => $GLOBALS['user']->cfg->MY_COURSES_TYPE_FILTER + ]); + } + + } + + if ($GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL) { + $this->query->join('mvv_lvgruppe_seminar', '`mvv_lvgruppe_seminar`.`seminar_id` = `seminare`.`Seminar_id`'); + $this->query->join('mvv_lvgruppe_modulteil', '`mvv_lvgruppe_modulteil`.`lvgruppe_id` = `mvv_lvgruppe_seminar`.`lvgruppe_id`'); + $this->query->join('mvv_modulteil', '`mvv_modulteil`.`modulteil_id` = `mvv_lvgruppe_modulteil`.`modulteil_id`'); + $this->query->join('mvv_modul', '`mvv_modul`.`modul_id` = `mvv_modulteil`.`modul_id`'); + $this->query->join('mvv_stgteilabschnitt_modul', '`mvv_stgteilabschnitt_modul`.`modul_id` = `mvv_modul`.`modul_id`'); + $this->query->join('mvv_stgteilabschnitt', '`mvv_stgteilabschnitt`.`abschnitt_id` = `mvv_stgteilabschnitt_modul`.`abschnitt_id`'); + $this->query->join('mvv_stgteilversion', '`mvv_stgteilversion`.`version_id` = `mvv_stgteilabschnitt`.`version_id`'); + $this->query->where('stgteil', 'mvv_stgteilversion.stgteil_id = :stgteil_id', [ + 'stgteil_id' => $GLOBALS['user']->cfg->MY_COURSES_SELECTED_STGTEIL + ]); + } + + if ($GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER) { + $this->query->join('teachers_su', 'seminar_user', "teachers_su.Seminar_id = seminare.Seminar_id AND teachers_su.status = 'dozent'"); + $this->query->where( + 'teacher_filter', + "teachers_su.user_id = :teacher_id", + ['teacher_id' => $GLOBALS['user']->cfg->ADMIN_COURSES_TEACHERFILTER] + ); + } + + + + $datafields_filters = $GLOBALS['user']->cfg->ADMIN_COURSES_DATAFIELDS_FILTERS; + foreach ($datafields_filters as $datafield_id => $value) { + $this->query->join('de_'.$datafield_id, 'datafields_entries', 'de_'.$datafield_id.'.range_id = seminare.Seminar_id AND `de_'.$datafield_id.'`.datafield_id = :de_'.$datafield_id.'_id'); + $this->query->where('de_' . $datafield_id . '_contents', 'de_' . $datafield_id . '.`content` LIKE :de_' . $datafield_id . '_content', + [ + 'de_' . $datafield_id . '_id' => $datafield_id, + 'de_' . $datafield_id . '_content' => '%' . $value . '%' + ]); + } + } + + /** + * Returns the data of the resultset of the AdminCourseFilter. + * Also saves the settings in the session. + * Note that a notification AdminCourseFilterWillQuery will be posted, before the result is computed. + * Plugins may register at this event to fully alter this AdminCourseFilter-object and so the resultset. + * @return array associative array with seminar_ids as keys and seminar-data-arrays as values. + */ + public function getCourses() + { + NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this); + return $this->query->fetchAll(Course::class); + } + + /** + * @return integer number of courses that this filter would return + */ + public function countCourses() + { + NotificationCenter::postNotification("AdminCourseFilterWillQuery", $this); + return $this->query->count(); + } + + /** + * @param int|null $limit + * @return Course[] + */ + public function fetchCourses(?int $limit = null): array + { + NotificationCenter::postNotification('AdminCourseFilterWillQuery', $this); + return $this->query->fetchAll(Course::class, $limit); + } + + /** + * Returns the data of the resultset of the AdminCourseFilter. + * + * @param string $order_by possible values name or number + * + * Note that a notification AdminCourseFilterWillQuery will be posted, before the result is computed. + * Plugins may register at this event to fully alter this AdminCourseFilter-object and so the resultset. + * @return array associative array with seminar_ids as keys and seminar-data-arrays as values. + */ + public function getCoursesForAdminWidget(string $order_by = 'name') + { + try { + $order = 'seminare.name'; + if ($order_by === 'number') { + $order = 'seminare.veranstaltungsnummer, seminare.name'; + } + $this->query->orderBy($order); + return $this->fetchCourses($this->max_show_courses); + } catch (OverflowException $e) { + return []; + } + } + +} diff --git a/lib/classes/Assets.class.php b/lib/classes/Assets.class.php deleted file mode 100644 index 928c47c..0000000 --- a/lib/classes/Assets.class.php +++ /dev/null @@ -1,439 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - - -/** - * This class is used to construct URLs for static content like images, - * stylesheets or javascripts. As the URL to the "assets" directory is - * configurable one always has to construct the above mentioned URLs - * dynamically. - * - * Example: - * - * # construct the URL for the image "blank.gif" - * $url = Assets::image_path('blank.gif'); - * - * @package studip - * - * @author mlunzena - * @copyright (c) Authors - */ -class Assets -{ - - const NUMBER_OF_ALIASES = 2; - - /** - * @ignore - */ - private static $assets_url; - private static $assets_path; - private static $dynamic; - private static $counter_cache; - - /** - * This method sets the URL to your assets. - * - * @param string $path the path to the assets - */ - public static function set_assets_path(string $path): void - { - self::$assets_path = $path; - } - - /** - * This method sets the URL to your assets. - * - * @param string the URL to the assets - * - * @return void - */ - public static function set_assets_url($url) - { - self::$assets_url = $url; - self::$counter_cache = NULL; - self::$dynamic = mb_strpos($url, '%d') !== FALSE; - } - - /** - * This class method is an accessor to the URL "prefix" for all things "asset" - * Prepend the return value of this method to the relative path of the wanted - * static content. - * - * Additionally if the ASSETS_URL contains the string '%d', it will be - * replaced with a random number between 0 and 3. If you passed an argument - * this number will not be random but specific to that argument thus being - * referentially transparent. - * - * Example: - * - * # static ASSETS_URL - * $ASSETS_URL = 'http://www.example.com/public/'; - * echo Assets::url() . 'javascripts/prototype.js' . "\n"; - * echo Assets::url('javascripts/prototype.js') . "\n"; - * - * # output - * http://www.example.com/public/javascripts/prototype.js - * http://www.example.com/public/javascripts/prototype.js - * - * - * # dynamic ASSETS_URL - * $ASSETS_URL = 'http://www%d.example.com/public/'; - * echo Assets::url() . 'javascripts/prototype.js' . "\n"; - * echo Assets::url() . 'javascripts/prototype.js' . "\n"; - * echo Assets::url() . 'javascripts/prototype.js' . "\n"; - * echo Assets::url('javascripts/prototype.js') . "\n"; - * echo Assets::url('javascripts/prototype.js') . "\n"; - * echo Assets::url('javascripts/prototype.js') . "\n"; - * - * # output - * http://www0.example.com/public/javascripts/prototype.js - * http://www1.example.com/public/javascripts/prototype.js - * http://www2.example.com/public/javascripts/prototype.js - * http://www1.example.com/public/javascripts/prototype.js - * http://www1.example.com/public/javascripts/prototype.js - * http://www1.example.com/public/javascripts/prototype.js - * - * - * @param string an optional suffix which is used to construct a number if - * ASSETS_URL is dynamic (contains '%d') - * - * @return string the URL "prefix" - */ - public static function url($to = '') - { - if (!self::$dynamic) { - return self::$assets_url . $to; - } - - # dynamic ASSETS_URL - return sprintf(self::$assets_url, - $to == '' - ? self::$counter_cache++ % self::NUMBER_OF_ALIASES - # alternative implementation - # : hexdec(mb_substr(sha1($to),-1)) & 3) - : ord($to[1]) & (self::NUMBER_OF_ALIASES - 1)) - - . $to; - } - - /** - * This class method is an accessor to the path "prefix" for all things "asset" - */ - public static function path($to = ''): string - { - return self::$assets_path . $to; - } - - /** - * Returns an image tag using options as html attributes on the - * tag, but with these special cases: - * - * 'alt' - If no alt text is given, the file name part of the $source is used - * (capitalized and without the extension) - * * 'size' - Supplied as "X@Y", so "30@45" becomes width="30" and height="45" - * - * The source can be supplied as a... - * * full path, like "/my_images/image.gif" - * * file name, like "rss.png", that gets expanded to "/images/rss.png" - * * file name without extension, like "logo", that gets expanded to "/images/logo.png" - * - * Do not use this to render icons. Use the more appropiate class - * Icon for this. - */ - public static function img($source, $opt = []) - { - if (!$source) { - return ''; - } - - $size = $opt['size'] ?? null; - - $opt = self::parse_attributes($opt); - - $opt['src'] = self::image_path($source); - - if (!isset($opt['alt'])) { - $opt['alt'] = ucfirst(current(explode('.', basename($opt['src'])))); - } - - if (isset($size) && !isset($opt['width'])) { - $size = explode('@', $size, 2); - $opt['width'] = $size[0]; - $opt['height'] = $size[1] ?? null; - - unset($opt['size']); - } - - return self::tag('img', $opt); - } - - - /** - * Returns an input tag using options as html attributes on the - * tag, but with these special cases: - * - * * 'size' - Supplied as "X@Y", so "30@45" becomes width="30" and height="45" - * - * The source can be supplied as a... - * * full path, like "/my_images/image.gif" - * * file name, like "rss.png", that gets expanded to "/images/rss.png" - * * file name without extension, like "logo", that gets expanded to "/images/logo.png" - * - * Do not use this to render icons. Use the more appropiate class - * Icon for this. - */ - public static function input($source, $opt = []) - { - - if (!$source) { - return ''; - } - - $parts = explode('/', $source); - - $size = $opt['size']; - - $opt = self::parse_attributes($opt); - - $opt['src'] = self::image_path($source); - $opt['type'] = 'image'; - - if (isset($size) && !isset($opt['width'])) { - [$opt['width'], $opt['height']] = explode('@', $size, 2); - unset($opt['size']); - } - - return self::tag('input', $opt); - } - - /** - * Returns path to an image asset. - * - * Example: - * - * The src can be supplied as a... - * - * full path, - * like "/my_images/image.gif" - * - * file name, - * like "rss.png", that gets expanded to "/images/rss.png" - * - * file name without extension, - * like "logo", that gets expanded to "/images/logo.png" - * - * Note: This function should be private/depracated for the use in other - * scripts, as we would like to always generate the complete oder - * tag. Please use Assets::img or Assets::input instead. - */ - public static function image_path($source, $respect_retina = false) - { - $path = self::compute_public_path($source, 'images', 'png'); - - return $path; - } - - /** - * Returns a script include tag per source given as argument. - * - * Examples: - * - * Assets::script('prototype') => - * - * - * Assets::script('common.javascript', '/elsewhere/cools') => - * - * - */ - public static function script($atLeastOneArgument) - { - $html = ''; - foreach (func_get_args() as $source) { - $source = self::javascript_path($source); - $html .= self::content_tag('script', '', - ['src' => $source]); - $html .= "\n"; - } - - return $html; - } - - - /** - * Returns path to a javascript asset. - * - * Example: - * - * Assets::javascript_path('ajax') => /javascripts/ajax.js - */ - public static function javascript_path($source) - { - return self::compute_public_path($source, 'javascripts', 'js'); - } - - - /** - * Returns a css link tag per source given as argument. - * - * Examples: - * - * Assets::stylesheet('style') => - * - * - * Assets::stylesheet('style', array('media' => 'all')) => - * - * - * Assets::stylesheet('random.styles', '/css/stylish') => - * - * - */ - public static function stylesheet($atLeastOneArgument) - { - $sources = func_get_args(); - $sourceOptions = (func_num_args() > 1 && - is_array($sources[func_num_args() - 1])) - ? array_pop($sources) - : []; - - $html = ''; - foreach ($sources as $source) { - $source = self::stylesheet_path($source); - $opt = array_merge(['rel' => 'stylesheet', - 'media' => 'screen', - 'href' => $source], - $sourceOptions); - $html .= self::tag('link', $opt) . "\n"; - } - - return $html; - } - - - /** - * Returns path to a stylesheet asset. - * - * Example: - * - * stylesheet_path('style') => /stylesheets/style.css - */ - public static function stylesheet_path($source) - { - return self::compute_public_path($source, 'stylesheets', 'css'); - } - - - /** - * This function computes the public path to the given source by using default - * dir and ext if not specified by the source. If source is not an absolute - * URL, the assets url is incorporated. - * - * @ignore - */ - private static function compute_public_path($source, $dir, $ext) - { - - # add extension if not present - if ('' == mb_substr(mb_strrchr($source, "."), 1)) - $source .= ".$ext"; - - # if source is not absolute - if (FALSE === mb_strpos($source, ':')) { - - # add dir if url does not contain a path - if ('/' !== $source[0]) - $source = "$dir/$source"; - - # consider asset host - $source = self::url(ltrim($source, '/')); - } - - return $source; - } - - - /** - * Constructs an html tag. - * - * @ignore - * - * @param string tag name - * @param array tag options - * @param boolean true to leave tag open - * - * @return string - */ - private static function tag($name, $options = [], $open = FALSE) - { - if (!$name) { - return ''; - } - - ksort($options); - return '<' . $name . ' ' . arrayToHtmlAttributes($options) . ($open ? '>' : '>'); - } - - - /** - * Helper function for content tags. - * - * @param string $name tag name - * @param string $content tag content - * @param array $options tag options - * - * @return string - */ - private static function content_tag($name, $content = '', $options = []) - { - if (!$name) { - return ''; - } - return '<' . $name . ' ' . arrayToHtmlAttributes($options) . '>' . - $content . - ''; - } - - /** - * Parse a HTML attribute string into an array. - * - * @ignore - */ - private static function parse_attributes($stringOrArray) - { - - if (is_array($stringOrArray)) - return $stringOrArray; - - preg_match_all('/ - \s*(\w+) # key \\1 - \s*=\s* # = - (\'|")? # values may be included in \' or " \\2 - (.*?) # value \\3 - (?(2) \\2) # matching \' or " if needed \\4 - \s*(?: - (?=\w+\s*=) | \s*$ # followed by another key= or the end of the string - ) - /x', $stringOrArray, $matches, PREG_SET_ORDER); - - $attributes = []; - foreach ($matches as $val) - $attributes[$val[1]] = $val[3]; - - return $attributes; - } -} diff --git a/lib/classes/Assets.php b/lib/classes/Assets.php new file mode 100644 index 0000000..928c47c --- /dev/null +++ b/lib/classes/Assets.php @@ -0,0 +1,439 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + + +/** + * This class is used to construct URLs for static content like images, + * stylesheets or javascripts. As the URL to the "assets" directory is + * configurable one always has to construct the above mentioned URLs + * dynamically. + * + * Example: + * + * # construct the URL for the image "blank.gif" + * $url = Assets::image_path('blank.gif'); + * + * @package studip + * + * @author mlunzena + * @copyright (c) Authors + */ +class Assets +{ + + const NUMBER_OF_ALIASES = 2; + + /** + * @ignore + */ + private static $assets_url; + private static $assets_path; + private static $dynamic; + private static $counter_cache; + + /** + * This method sets the URL to your assets. + * + * @param string $path the path to the assets + */ + public static function set_assets_path(string $path): void + { + self::$assets_path = $path; + } + + /** + * This method sets the URL to your assets. + * + * @param string the URL to the assets + * + * @return void + */ + public static function set_assets_url($url) + { + self::$assets_url = $url; + self::$counter_cache = NULL; + self::$dynamic = mb_strpos($url, '%d') !== FALSE; + } + + /** + * This class method is an accessor to the URL "prefix" for all things "asset" + * Prepend the return value of this method to the relative path of the wanted + * static content. + * + * Additionally if the ASSETS_URL contains the string '%d', it will be + * replaced with a random number between 0 and 3. If you passed an argument + * this number will not be random but specific to that argument thus being + * referentially transparent. + * + * Example: + * + * # static ASSETS_URL + * $ASSETS_URL = 'http://www.example.com/public/'; + * echo Assets::url() . 'javascripts/prototype.js' . "\n"; + * echo Assets::url('javascripts/prototype.js') . "\n"; + * + * # output + * http://www.example.com/public/javascripts/prototype.js + * http://www.example.com/public/javascripts/prototype.js + * + * + * # dynamic ASSETS_URL + * $ASSETS_URL = 'http://www%d.example.com/public/'; + * echo Assets::url() . 'javascripts/prototype.js' . "\n"; + * echo Assets::url() . 'javascripts/prototype.js' . "\n"; + * echo Assets::url() . 'javascripts/prototype.js' . "\n"; + * echo Assets::url('javascripts/prototype.js') . "\n"; + * echo Assets::url('javascripts/prototype.js') . "\n"; + * echo Assets::url('javascripts/prototype.js') . "\n"; + * + * # output + * http://www0.example.com/public/javascripts/prototype.js + * http://www1.example.com/public/javascripts/prototype.js + * http://www2.example.com/public/javascripts/prototype.js + * http://www1.example.com/public/javascripts/prototype.js + * http://www1.example.com/public/javascripts/prototype.js + * http://www1.example.com/public/javascripts/prototype.js + * + * + * @param string an optional suffix which is used to construct a number if + * ASSETS_URL is dynamic (contains '%d') + * + * @return string the URL "prefix" + */ + public static function url($to = '') + { + if (!self::$dynamic) { + return self::$assets_url . $to; + } + + # dynamic ASSETS_URL + return sprintf(self::$assets_url, + $to == '' + ? self::$counter_cache++ % self::NUMBER_OF_ALIASES + # alternative implementation + # : hexdec(mb_substr(sha1($to),-1)) & 3) + : ord($to[1]) & (self::NUMBER_OF_ALIASES - 1)) + + . $to; + } + + /** + * This class method is an accessor to the path "prefix" for all things "asset" + */ + public static function path($to = ''): string + { + return self::$assets_path . $to; + } + + /** + * Returns an image tag using options as html attributes on the + * tag, but with these special cases: + * + * 'alt' - If no alt text is given, the file name part of the $source is used + * (capitalized and without the extension) + * * 'size' - Supplied as "X@Y", so "30@45" becomes width="30" and height="45" + * + * The source can be supplied as a... + * * full path, like "/my_images/image.gif" + * * file name, like "rss.png", that gets expanded to "/images/rss.png" + * * file name without extension, like "logo", that gets expanded to "/images/logo.png" + * + * Do not use this to render icons. Use the more appropiate class + * Icon for this. + */ + public static function img($source, $opt = []) + { + if (!$source) { + return ''; + } + + $size = $opt['size'] ?? null; + + $opt = self::parse_attributes($opt); + + $opt['src'] = self::image_path($source); + + if (!isset($opt['alt'])) { + $opt['alt'] = ucfirst(current(explode('.', basename($opt['src'])))); + } + + if (isset($size) && !isset($opt['width'])) { + $size = explode('@', $size, 2); + $opt['width'] = $size[0]; + $opt['height'] = $size[1] ?? null; + + unset($opt['size']); + } + + return self::tag('img', $opt); + } + + + /** + * Returns an input tag using options as html attributes on the + * tag, but with these special cases: + * + * * 'size' - Supplied as "X@Y", so "30@45" becomes width="30" and height="45" + * + * The source can be supplied as a... + * * full path, like "/my_images/image.gif" + * * file name, like "rss.png", that gets expanded to "/images/rss.png" + * * file name without extension, like "logo", that gets expanded to "/images/logo.png" + * + * Do not use this to render icons. Use the more appropiate class + * Icon for this. + */ + public static function input($source, $opt = []) + { + + if (!$source) { + return ''; + } + + $parts = explode('/', $source); + + $size = $opt['size']; + + $opt = self::parse_attributes($opt); + + $opt['src'] = self::image_path($source); + $opt['type'] = 'image'; + + if (isset($size) && !isset($opt['width'])) { + [$opt['width'], $opt['height']] = explode('@', $size, 2); + unset($opt['size']); + } + + return self::tag('input', $opt); + } + + /** + * Returns path to an image asset. + * + * Example: + * + * The src can be supplied as a... + * + * full path, + * like "/my_images/image.gif" + * + * file name, + * like "rss.png", that gets expanded to "/images/rss.png" + * + * file name without extension, + * like "logo", that gets expanded to "/images/logo.png" + * + * Note: This function should be private/depracated for the use in other + * scripts, as we would like to always generate the complete oder + * tag. Please use Assets::img or Assets::input instead. + */ + public static function image_path($source, $respect_retina = false) + { + $path = self::compute_public_path($source, 'images', 'png'); + + return $path; + } + + /** + * Returns a script include tag per source given as argument. + * + * Examples: + * + * Assets::script('prototype') => + * + * + * Assets::script('common.javascript', '/elsewhere/cools') => + * + * + */ + public static function script($atLeastOneArgument) + { + $html = ''; + foreach (func_get_args() as $source) { + $source = self::javascript_path($source); + $html .= self::content_tag('script', '', + ['src' => $source]); + $html .= "\n"; + } + + return $html; + } + + + /** + * Returns path to a javascript asset. + * + * Example: + * + * Assets::javascript_path('ajax') => /javascripts/ajax.js + */ + public static function javascript_path($source) + { + return self::compute_public_path($source, 'javascripts', 'js'); + } + + + /** + * Returns a css link tag per source given as argument. + * + * Examples: + * + * Assets::stylesheet('style') => + * + * + * Assets::stylesheet('style', array('media' => 'all')) => + * + * + * Assets::stylesheet('random.styles', '/css/stylish') => + * + * + */ + public static function stylesheet($atLeastOneArgument) + { + $sources = func_get_args(); + $sourceOptions = (func_num_args() > 1 && + is_array($sources[func_num_args() - 1])) + ? array_pop($sources) + : []; + + $html = ''; + foreach ($sources as $source) { + $source = self::stylesheet_path($source); + $opt = array_merge(['rel' => 'stylesheet', + 'media' => 'screen', + 'href' => $source], + $sourceOptions); + $html .= self::tag('link', $opt) . "\n"; + } + + return $html; + } + + + /** + * Returns path to a stylesheet asset. + * + * Example: + * + * stylesheet_path('style') => /stylesheets/style.css + */ + public static function stylesheet_path($source) + { + return self::compute_public_path($source, 'stylesheets', 'css'); + } + + + /** + * This function computes the public path to the given source by using default + * dir and ext if not specified by the source. If source is not an absolute + * URL, the assets url is incorporated. + * + * @ignore + */ + private static function compute_public_path($source, $dir, $ext) + { + + # add extension if not present + if ('' == mb_substr(mb_strrchr($source, "."), 1)) + $source .= ".$ext"; + + # if source is not absolute + if (FALSE === mb_strpos($source, ':')) { + + # add dir if url does not contain a path + if ('/' !== $source[0]) + $source = "$dir/$source"; + + # consider asset host + $source = self::url(ltrim($source, '/')); + } + + return $source; + } + + + /** + * Constructs an html tag. + * + * @ignore + * + * @param string tag name + * @param array tag options + * @param boolean true to leave tag open + * + * @return string + */ + private static function tag($name, $options = [], $open = FALSE) + { + if (!$name) { + return ''; + } + + ksort($options); + return '<' . $name . ' ' . arrayToHtmlAttributes($options) . ($open ? '>' : '>'); + } + + + /** + * Helper function for content tags. + * + * @param string $name tag name + * @param string $content tag content + * @param array $options tag options + * + * @return string + */ + private static function content_tag($name, $content = '', $options = []) + { + if (!$name) { + return ''; + } + return '<' . $name . ' ' . arrayToHtmlAttributes($options) . '>' . + $content . + ''; + } + + /** + * Parse a HTML attribute string into an array. + * + * @ignore + */ + private static function parse_attributes($stringOrArray) + { + + if (is_array($stringOrArray)) + return $stringOrArray; + + preg_match_all('/ + \s*(\w+) # key \\1 + \s*=\s* # = + (\'|")? # values may be included in \' or " \\2 + (.*?) # value \\3 + (?(2) \\2) # matching \' or " if needed \\4 + \s*(?: + (?=\w+\s*=) | \s*$ # followed by another key= or the end of the string + ) + /x', $stringOrArray, $matches, PREG_SET_ORDER); + + $attributes = []; + foreach ($matches as $val) + $attributes[$val[1]] = $val[3]; + + return $attributes; + } +} diff --git a/lib/classes/AuthorObject.class.php b/lib/classes/AuthorObject.class.php deleted file mode 100644 index b3e98e3..0000000 --- a/lib/classes/AuthorObject.class.php +++ /dev/null @@ -1,139 +0,0 @@ - -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -define("ERROR_NORMAL", "1"); -define("ERROR_CRITICAL", "8"); - - -/** - * AuthorObject.class.php - * - * Class to provide basic properties of an object in Stud.IP - * - * @author Alexander Willner - * @copyright 2003 Stud.IP-Project - * @access public - * @package studip_core - * @modulegroup core - */ -class AuthorObject -{ - - /** - * Holds the code and description of an internal error - * @access private - * @var array $errorArray - */ - public $errorArray; - - /** - * Holds the type of object. See INSTANCEOF_* - * @access private - * @var string $instanceof - */ - public $instanceof; - - /** - * Constructor - * @access public - */ - public function __construct() - { - $this->instanceof = 'AuthorObject'; - $this->errorArray = []; - } - - /** - * Gets the type of object - * @access public - * @return string The type of object. See INSTANCEOF_* - */ - public function x_instanceof() - { - return $this->instanceof; - } - - /** - * Gives the internal errorcode - * @access public - * @return boolean True if an error exists - */ - public function isError() - { - return count($this->errorArray) > 0; - } - - /** - * Gives the codes and descriptions of the internal errors - * @access public - * @return array The errors as an Array like "1" => "Could not open DB" - */ - public function getErrors() - { - return $this->errorArray; - } - - /** - * Resets the errorcodes and descriptions - * @access public - */ - public function resetErrors() - { - $this->errorArray = []; - } - - /** - * Sets the errorcode (internal) - * @access public - * @param integer $errcode The code of the error - * @param string $errstring The description of the error - */ - public function throwError($errcode, $errstring) - { - if (!is_array($this->errorArray)) { - $this->errorArray = []; - } - - $this->errorArray [] = [ - 'code' => $errcode, - 'string' => $errstring, - ]; - } - - /** - * Sets the errorcode from other classes (internal) - * @access private - * @param object $class The class with the error - */ - public function throwErrorFromClass(AuthorObject $class) - { - $this->errorArray = $class->getErrors(); - $class->resetErrors(); - } -} - diff --git a/lib/classes/AuthorObject.php b/lib/classes/AuthorObject.php new file mode 100644 index 0000000..b3e98e3 --- /dev/null +++ b/lib/classes/AuthorObject.php @@ -0,0 +1,139 @@ + +// +--------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +--------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +--------------------------------------------------------------------------+ + + +define("ERROR_NORMAL", "1"); +define("ERROR_CRITICAL", "8"); + + +/** + * AuthorObject.class.php + * + * Class to provide basic properties of an object in Stud.IP + * + * @author Alexander Willner + * @copyright 2003 Stud.IP-Project + * @access public + * @package studip_core + * @modulegroup core + */ +class AuthorObject +{ + + /** + * Holds the code and description of an internal error + * @access private + * @var array $errorArray + */ + public $errorArray; + + /** + * Holds the type of object. See INSTANCEOF_* + * @access private + * @var string $instanceof + */ + public $instanceof; + + /** + * Constructor + * @access public + */ + public function __construct() + { + $this->instanceof = 'AuthorObject'; + $this->errorArray = []; + } + + /** + * Gets the type of object + * @access public + * @return string The type of object. See INSTANCEOF_* + */ + public function x_instanceof() + { + return $this->instanceof; + } + + /** + * Gives the internal errorcode + * @access public + * @return boolean True if an error exists + */ + public function isError() + { + return count($this->errorArray) > 0; + } + + /** + * Gives the codes and descriptions of the internal errors + * @access public + * @return array The errors as an Array like "1" => "Could not open DB" + */ + public function getErrors() + { + return $this->errorArray; + } + + /** + * Resets the errorcodes and descriptions + * @access public + */ + public function resetErrors() + { + $this->errorArray = []; + } + + /** + * Sets the errorcode (internal) + * @access public + * @param integer $errcode The code of the error + * @param string $errstring The description of the error + */ + public function throwError($errcode, $errstring) + { + if (!is_array($this->errorArray)) { + $this->errorArray = []; + } + + $this->errorArray [] = [ + 'code' => $errcode, + 'string' => $errstring, + ]; + } + + /** + * Sets the errorcode from other classes (internal) + * @access private + * @param object $class The class with the error + */ + public function throwErrorFromClass(AuthorObject $class) + { + $this->errorArray = $class->getErrors(); + $class->resetErrors(); + } +} + diff --git a/lib/classes/AutoInsert.class.php b/lib/classes/AutoInsert.class.php deleted file mode 100644 index b2168cc..0000000 --- a/lib/classes/AutoInsert.class.php +++ /dev/null @@ -1,342 +0,0 @@ - - * @author Michael Riehemann - * @author Jan Hendrik Willms - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.1 - */ - -/** - * AutoInsert.class.php - * Provides functions required by StEP00216: - * - Assign seminars for automatic registration of certain user types - * - Maintenance of registration rules - * - * Example of use: - * @code - * - * # show all auto insert seminars - * $auto_sems = AutoInsert::getAllSeminars(); - * - * # Save a new auto insert seminar with the user status - * AutoInsert::saveSeminar($sem_id, $rechte); - * - * @endcode - */ -class AutoInsert -{ - private static $instance = null; - protected static $seminar_cache = null; - - private $settings = []; - - public static function instance() - { - if (self::$instance === null) { - self::$instance = new self(); - } - return self::$instance; - } - - public function __construct() - { - $this->loadSettings(); - } - - private function loadSettings() - { - $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff, s.start_time "; - $query .= "FROM auto_insert_sem a "; - $query .= "JOIN seminare AS s USING (Seminar_id) "; - $query .= "GROUP BY s.seminar_id "; - $query .= "ORDER BY s.Name"; - $statement = DBManager::get()->query($query); - $results = $statement->fetchAll(PDO::FETCH_ASSOC); - foreach ($results as $result) { - if ($result['Schreibzugriff'] < 3) { - $domains = explode(',', $result['domain_status']); - - foreach ($domains as $domain) { - $array = explode(':', $domain); - $key = $array[1] . '.' . $array[0]; - $this->settings[$key][$result['seminar_id']] = ['Seminar_id' => $result['seminar_id'], - 'name' => $result['Name'], - 'Schreibzugriff' => $result['Schreibzugriff'], - 'start_time' => $result['start_time']]; - } - } - } - } - - - private function getUserSeminars($user_id, $seminare) - { - $statement = DBManager::get()->prepare("SELECT Seminar_id,s.name,s.Schreibzugriff,s.start_time,su.status - FROM seminar_user su - INNER JOIN seminare s USING(Seminar_id) - WHERE user_id = ? AND Seminar_id IN(?)"); - $statement->execute([$user_id, $seminare]); - return $statement->fetchAll(PDO::FETCH_ASSOC); - } - - /** - * Trägt den Benutzer in den Eingestellten veranstaltungen automatisch ein. - * @param string $user_id - * @param string|bool $status Wenn Status nicht angegeben wird, wird der Status des Users aus user_id genommen - * @return array 'added' Namen der Seminare in die der User eingetragen wurde - * array 'removed' Namen der Seminare aus denen der User ausgetragen wurde - */ - public function saveUser($user_id, $status = false) - { - $domains = []; - if (!$status) { - $status = $GLOBALS['perm']->get_perm($user_id); - } - foreach (UserDomain::getUserDomainsForUser($user_id) as $d) { - $domains [] = $d->id; //Domains des Users - } - - if (count($domains) === 0) { - $domains [] = 'keine'; - } - $settings = []; - $all_seminare = []; - foreach ($domains as $domain) { - - $key = $domain . '.' . $status; - if (is_array($this->settings[$key])) { - foreach ($this->settings[$key] as $id => $value) { - $settings[$id] = $value; - } - } - foreach ($this->settings as $key) { - foreach ($key as $id => $sem) { - $all_seminare[$id] = $sem; - } - } - } - - $seminare = []; - $seminare_tutor_dozent = []; - foreach ($this->getUserSeminars($user_id, array_keys($all_seminare)) as $sem) { - $seminare[$sem['Seminar_id']] = $sem; - if (in_array($sem['status'], ['tutor', 'dozent'])) { - $seminare_tutor_dozent[$sem['Seminar_id']] = $sem; - } - } - $toAdd = array_diff_key($settings, $seminare); - $toRemove = array_diff_key($all_seminare, $toAdd, $settings, $seminare_tutor_dozent); - - $added = []; - $removed = []; - - foreach ($toAdd as $id => $seminar) { - if ($this->addUser($user_id, $seminar)) $added[] = $seminar['name']; - } - foreach ($toRemove as $id => $seminar) { - if ($this->removeUser($user_id, $seminar)) $removed[] = $seminar['name']; - } - - return ['added' => $added, 'removed' => $removed]; - } - - private function addUser($user_id, $seminar) - { - return CourseMember::insertCourseMember($seminar['Seminar_id'], $user_id, 'autor'); - } - - private function removeUser($user_id, $seminar) - { - $rows = CourseMember::deleteBySQL(' user_id = ? AND Seminar_id = ?', [$user_id, $seminar['Seminar_id']]); - $statusgruppe_rows = StatusgruppeUser::deleteBySQL( - 'user_id = ? AND statusgruppe_id IN (SELECT statusgruppe_id FROM statusgruppen WHERE range_id = ?)', - [$user_id, $seminar['Seminar_id']] - ); - if ($rows > 0 || $statusgruppe_rows > 0) return true; - - return false; - } - - /** - * Tests if a seminar already has an autoinsert record - * @param string $seminar_id Id of the seminar - * @return bool Indicating whether the seminar already has an autoinsert record - */ - public static function checkSeminar($seminar_id, $domain_id = false) - { - $cached = self::getSeminarCache(); - - if (!isset($cached[$seminar_id])) { - $query = "SELECT domain_id, 1 - FROM auto_insert_sem - WHERE seminar_id = ?"; - $cached[$seminar_id] = DBManager::get()->fetchGroupedPairs( - $query, - [$seminar_id], - function ($value) { - return (bool) $value; - } - ); - } - - return array_key_exists($domain_id ?: '', $cached[$seminar_id]) - ? $cached[$seminar_id][$domain_id ?: ''] - : false; - } - - /** - * Enables a seminar for autoinsertion of users with the given status(ses) - * @param string $seminar_id Id of the seminar - * @param mixed $status Either a single string or an array of strings - * containing the status(ses) to enable for - * autoinsertion - */ - public static function saveSeminar($seminar_id, $status, $domain_id) - { - $query = "INSERT INTO auto_insert_sem (seminar_id, status,domain_id) VALUES (?, ?,?)"; - $statement = DBManager::get()->prepare($query); - - foreach ((array)$status as $s) { - $statement->execute([$seminar_id, $s, $domain_id]); - } - } - - /** - * Updates an autoinsert record for a given seminar, dependent on the - * parameter $remove it either inserts or removes the record for the given - * parameters - * - * @param string $seminar_id Id of the seminar - * @param string $status Status for autoinsertion - * @param bool $remove Whether the record should be added or removed - */ - public static function updateSeminar($seminar_id, $domain, $status, $remove = false) - { - $query = $remove ? "DELETE FROM auto_insert_sem WHERE seminar_id = ? AND status= ? AND domain_id = ?" : "INSERT IGNORE INTO auto_insert_sem (seminar_id, status,domain_id) VALUES (?, ?, ?)"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$seminar_id, $status, $domain]); - - if ($remove) { - unset(self::getSeminarCache()[$seminar_id]); - } - } - - /** - * Removes a seminar from the autoinsertion process. - * @param string $seminar_id Id of the seminar - */ - public static function deleteSeminar($seminar_id): bool - { - $query = "DELETE FROM auto_insert_sem WHERE seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $result = $statement->execute([$seminar_id]); - - unset(self::getSeminarCache()[$seminar_id]); - - return $result > 0; - } - - /** - * Returns a list of all seminars enabled for autoinsertion - * @param bool Indicates whether only the seminar ids (true) or the full - * dataset shall be returned (false) - * @return array The list of all enabled seminars (format according to $only_sem_id) - */ - public static function getAllSeminars($only_sem_id = false) - { - if ($only_sem_id) { - $query = "SELECT DISTINCT seminar_id FROM auto_insert_sem"; - $statement = DBManager::get()->query($query); - $results = $statement->fetchAll(PDO::FETCH_COLUMN); - } else { - $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff, s.start_time "; - $query .= "FROM auto_insert_sem a "; - $query .= "JOIN seminare AS s USING (Seminar_id) "; - - $query .= "GROUP BY s.seminar_id "; - $query .= "ORDER BY s.Name"; - $statement = DBManager::get()->query($query); - $results = $statement->fetchAll(PDO::FETCH_ASSOC); - foreach ($results as $index => $result) { - $domains = explode(',', $result['domain_status']); - foreach ($domains as $domain) { - $array = explode(':', $domain); - $results[$index]['status'][$array[1]][] = $array[0]; - } - } - } - - return $results; - } - - /** - * Returns a seminar's info for autoinsertion - * @param string $seminar_id Id of the seminar - * @return array The seminar's data as an associative array - */ - public static function getSeminar($seminar_id) - { - $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status) AS status, s.Name "; - $query .= "FROM auto_insert_sem a "; - $query .= "JOIN seminare AS s USING (Seminar_id) "; - $query .= "WHERE a.seminar_id = ? "; - $query .= "GROUP BY s.seminar_id"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$seminar_id]); - - $result = $statement->fetch(PDO::FETCH_ASSOC); - $result['status'] = explode(',', $result['status']); - return $result; - } - - /** - * Store the user's automatic registration in a seminar redundantly to - * avoid an annoying reregistration although the user explicitely left the - * according seminar - * @param string $user_id Id of the user - * @param string $seminar_id Id of the seminar - */ - public static function saveAutoInsertUser($seminar_id, $user_id) - { - $query = "INSERT IGNORE INTO auto_insert_user (Seminar_id, user_id, mkdate) - SELECT ?, user_id, UNIX_TIMESTAMP() - FROM auth_user_md5 - WHERE user_id = ? AND perms NOT IN ('root','admin')"; - return DBManager::get()->execute($query, [$seminar_id, $user_id]); - } - - /** - * Tests whether a user was already automatically registered for a certain - * seminar. - * @param string $seminar_id Id of the seminar - * @param string $user_id If of the user - * @return bool Indicates whether the user was already registered - */ - public static function checkAutoInsertUser($seminar_id, $user_id) - { - $query = "SELECT 1 FROM auto_insert_user WHERE seminar_id = ? AND user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$seminar_id, $user_id]); - $result = $statement->fetchColumn(); - - return $result > 0; - } - - /** - * Returns the cache for seminars. - */ - protected static function getSeminarCache(): StudipCachedArray - { - if (self::$seminar_cache === null) { - self::$seminar_cache = new StudipCachedArray('AutoInsertSeminars'); - } - return self::$seminar_cache; - } -} diff --git a/lib/classes/AutoInsert.php b/lib/classes/AutoInsert.php new file mode 100644 index 0000000..b2168cc --- /dev/null +++ b/lib/classes/AutoInsert.php @@ -0,0 +1,342 @@ + + * @author Michael Riehemann + * @author Jan Hendrik Willms + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.1 + */ + +/** + * AutoInsert.class.php + * Provides functions required by StEP00216: + * - Assign seminars for automatic registration of certain user types + * - Maintenance of registration rules + * + * Example of use: + * @code + * + * # show all auto insert seminars + * $auto_sems = AutoInsert::getAllSeminars(); + * + * # Save a new auto insert seminar with the user status + * AutoInsert::saveSeminar($sem_id, $rechte); + * + * @endcode + */ +class AutoInsert +{ + private static $instance = null; + protected static $seminar_cache = null; + + private $settings = []; + + public static function instance() + { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + public function __construct() + { + $this->loadSettings(); + } + + private function loadSettings() + { + $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff, s.start_time "; + $query .= "FROM auto_insert_sem a "; + $query .= "JOIN seminare AS s USING (Seminar_id) "; + $query .= "GROUP BY s.seminar_id "; + $query .= "ORDER BY s.Name"; + $statement = DBManager::get()->query($query); + $results = $statement->fetchAll(PDO::FETCH_ASSOC); + foreach ($results as $result) { + if ($result['Schreibzugriff'] < 3) { + $domains = explode(',', $result['domain_status']); + + foreach ($domains as $domain) { + $array = explode(':', $domain); + $key = $array[1] . '.' . $array[0]; + $this->settings[$key][$result['seminar_id']] = ['Seminar_id' => $result['seminar_id'], + 'name' => $result['Name'], + 'Schreibzugriff' => $result['Schreibzugriff'], + 'start_time' => $result['start_time']]; + } + } + } + } + + + private function getUserSeminars($user_id, $seminare) + { + $statement = DBManager::get()->prepare("SELECT Seminar_id,s.name,s.Schreibzugriff,s.start_time,su.status + FROM seminar_user su + INNER JOIN seminare s USING(Seminar_id) + WHERE user_id = ? AND Seminar_id IN(?)"); + $statement->execute([$user_id, $seminare]); + return $statement->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Trägt den Benutzer in den Eingestellten veranstaltungen automatisch ein. + * @param string $user_id + * @param string|bool $status Wenn Status nicht angegeben wird, wird der Status des Users aus user_id genommen + * @return array 'added' Namen der Seminare in die der User eingetragen wurde + * array 'removed' Namen der Seminare aus denen der User ausgetragen wurde + */ + public function saveUser($user_id, $status = false) + { + $domains = []; + if (!$status) { + $status = $GLOBALS['perm']->get_perm($user_id); + } + foreach (UserDomain::getUserDomainsForUser($user_id) as $d) { + $domains [] = $d->id; //Domains des Users + } + + if (count($domains) === 0) { + $domains [] = 'keine'; + } + $settings = []; + $all_seminare = []; + foreach ($domains as $domain) { + + $key = $domain . '.' . $status; + if (is_array($this->settings[$key])) { + foreach ($this->settings[$key] as $id => $value) { + $settings[$id] = $value; + } + } + foreach ($this->settings as $key) { + foreach ($key as $id => $sem) { + $all_seminare[$id] = $sem; + } + } + } + + $seminare = []; + $seminare_tutor_dozent = []; + foreach ($this->getUserSeminars($user_id, array_keys($all_seminare)) as $sem) { + $seminare[$sem['Seminar_id']] = $sem; + if (in_array($sem['status'], ['tutor', 'dozent'])) { + $seminare_tutor_dozent[$sem['Seminar_id']] = $sem; + } + } + $toAdd = array_diff_key($settings, $seminare); + $toRemove = array_diff_key($all_seminare, $toAdd, $settings, $seminare_tutor_dozent); + + $added = []; + $removed = []; + + foreach ($toAdd as $id => $seminar) { + if ($this->addUser($user_id, $seminar)) $added[] = $seminar['name']; + } + foreach ($toRemove as $id => $seminar) { + if ($this->removeUser($user_id, $seminar)) $removed[] = $seminar['name']; + } + + return ['added' => $added, 'removed' => $removed]; + } + + private function addUser($user_id, $seminar) + { + return CourseMember::insertCourseMember($seminar['Seminar_id'], $user_id, 'autor'); + } + + private function removeUser($user_id, $seminar) + { + $rows = CourseMember::deleteBySQL(' user_id = ? AND Seminar_id = ?', [$user_id, $seminar['Seminar_id']]); + $statusgruppe_rows = StatusgruppeUser::deleteBySQL( + 'user_id = ? AND statusgruppe_id IN (SELECT statusgruppe_id FROM statusgruppen WHERE range_id = ?)', + [$user_id, $seminar['Seminar_id']] + ); + if ($rows > 0 || $statusgruppe_rows > 0) return true; + + return false; + } + + /** + * Tests if a seminar already has an autoinsert record + * @param string $seminar_id Id of the seminar + * @return bool Indicating whether the seminar already has an autoinsert record + */ + public static function checkSeminar($seminar_id, $domain_id = false) + { + $cached = self::getSeminarCache(); + + if (!isset($cached[$seminar_id])) { + $query = "SELECT domain_id, 1 + FROM auto_insert_sem + WHERE seminar_id = ?"; + $cached[$seminar_id] = DBManager::get()->fetchGroupedPairs( + $query, + [$seminar_id], + function ($value) { + return (bool) $value; + } + ); + } + + return array_key_exists($domain_id ?: '', $cached[$seminar_id]) + ? $cached[$seminar_id][$domain_id ?: ''] + : false; + } + + /** + * Enables a seminar for autoinsertion of users with the given status(ses) + * @param string $seminar_id Id of the seminar + * @param mixed $status Either a single string or an array of strings + * containing the status(ses) to enable for + * autoinsertion + */ + public static function saveSeminar($seminar_id, $status, $domain_id) + { + $query = "INSERT INTO auto_insert_sem (seminar_id, status,domain_id) VALUES (?, ?,?)"; + $statement = DBManager::get()->prepare($query); + + foreach ((array)$status as $s) { + $statement->execute([$seminar_id, $s, $domain_id]); + } + } + + /** + * Updates an autoinsert record for a given seminar, dependent on the + * parameter $remove it either inserts or removes the record for the given + * parameters + * + * @param string $seminar_id Id of the seminar + * @param string $status Status for autoinsertion + * @param bool $remove Whether the record should be added or removed + */ + public static function updateSeminar($seminar_id, $domain, $status, $remove = false) + { + $query = $remove ? "DELETE FROM auto_insert_sem WHERE seminar_id = ? AND status= ? AND domain_id = ?" : "INSERT IGNORE INTO auto_insert_sem (seminar_id, status,domain_id) VALUES (?, ?, ?)"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$seminar_id, $status, $domain]); + + if ($remove) { + unset(self::getSeminarCache()[$seminar_id]); + } + } + + /** + * Removes a seminar from the autoinsertion process. + * @param string $seminar_id Id of the seminar + */ + public static function deleteSeminar($seminar_id): bool + { + $query = "DELETE FROM auto_insert_sem WHERE seminar_id = ?"; + $statement = DBManager::get()->prepare($query); + $result = $statement->execute([$seminar_id]); + + unset(self::getSeminarCache()[$seminar_id]); + + return $result > 0; + } + + /** + * Returns a list of all seminars enabled for autoinsertion + * @param bool Indicates whether only the seminar ids (true) or the full + * dataset shall be returned (false) + * @return array The list of all enabled seminars (format according to $only_sem_id) + */ + public static function getAllSeminars($only_sem_id = false) + { + if ($only_sem_id) { + $query = "SELECT DISTINCT seminar_id FROM auto_insert_sem"; + $statement = DBManager::get()->query($query); + $results = $statement->fetchAll(PDO::FETCH_COLUMN); + } else { + $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff, s.start_time "; + $query .= "FROM auto_insert_sem a "; + $query .= "JOIN seminare AS s USING (Seminar_id) "; + + $query .= "GROUP BY s.seminar_id "; + $query .= "ORDER BY s.Name"; + $statement = DBManager::get()->query($query); + $results = $statement->fetchAll(PDO::FETCH_ASSOC); + foreach ($results as $index => $result) { + $domains = explode(',', $result['domain_status']); + foreach ($domains as $domain) { + $array = explode(':', $domain); + $results[$index]['status'][$array[1]][] = $array[0]; + } + } + } + + return $results; + } + + /** + * Returns a seminar's info for autoinsertion + * @param string $seminar_id Id of the seminar + * @return array The seminar's data as an associative array + */ + public static function getSeminar($seminar_id) + { + $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status) AS status, s.Name "; + $query .= "FROM auto_insert_sem a "; + $query .= "JOIN seminare AS s USING (Seminar_id) "; + $query .= "WHERE a.seminar_id = ? "; + $query .= "GROUP BY s.seminar_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$seminar_id]); + + $result = $statement->fetch(PDO::FETCH_ASSOC); + $result['status'] = explode(',', $result['status']); + return $result; + } + + /** + * Store the user's automatic registration in a seminar redundantly to + * avoid an annoying reregistration although the user explicitely left the + * according seminar + * @param string $user_id Id of the user + * @param string $seminar_id Id of the seminar + */ + public static function saveAutoInsertUser($seminar_id, $user_id) + { + $query = "INSERT IGNORE INTO auto_insert_user (Seminar_id, user_id, mkdate) + SELECT ?, user_id, UNIX_TIMESTAMP() + FROM auth_user_md5 + WHERE user_id = ? AND perms NOT IN ('root','admin')"; + return DBManager::get()->execute($query, [$seminar_id, $user_id]); + } + + /** + * Tests whether a user was already automatically registered for a certain + * seminar. + * @param string $seminar_id Id of the seminar + * @param string $user_id If of the user + * @return bool Indicates whether the user was already registered + */ + public static function checkAutoInsertUser($seminar_id, $user_id) + { + $query = "SELECT 1 FROM auto_insert_user WHERE seminar_id = ? AND user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$seminar_id, $user_id]); + $result = $statement->fetchColumn(); + + return $result > 0; + } + + /** + * Returns the cache for seminars. + */ + protected static function getSeminarCache(): StudipCachedArray + { + if (self::$seminar_cache === null) { + self::$seminar_cache = new StudipCachedArray('AutoInsertSeminars'); + } + return self::$seminar_cache; + } +} diff --git a/lib/classes/Avatar.class.php b/lib/classes/Avatar.class.php deleted file mode 100644 index 959523f..0000000 --- a/lib/classes/Avatar.class.php +++ /dev/null @@ -1,635 +0,0 @@ - - * @license GPL2 or any later version - * @since 1.7 - */ -class Avatar -{ - public const AVATAR_TYPE = 'user'; - protected const CREATE_CHUNKED_FOLDERS = true; - - public const EXTENSION = 'webp'; - public const IMAGE_QUALITY = 90; - - /** - * This constant stands for the maximal size of a user picture. - */ - public const ORIGINAL = 'original'; - - /** - * This constant stands for the maximal size of a user picture. - */ - public const NORMAL = 'normal'; - - /** - * This constant stands for a medium size of a user picture. - */ - public const MEDIUM = 'medium'; - - /** - * This constant stands for an icon size of a user picture. - */ - public const SMALL = 'small'; - - /** - * This constant represents the maximal size of a user picture in bytes. - */ - public const MAX_FILE_SIZE = 10485760; - - /** - * This constant holds the username and ID of the "nobody" avatar. - */ - protected const NOBODY = 'nobody'; - - /** - * Holds the user's id - * - * @var string - */ - protected $user_id; - - /** - * Holds the user's username - * - * @var string - */ - protected $username; - - /** - * Returns an avatar object of the appropriate class. - * - * @param string $id the user's id - * @param string $username the user's username (optional) - * - * @return static the user's avatar. - */ - public static function getAvatar($id) - { - $username = null; - - if (func_num_args() === 2) { - $username = func_get_arg(1); - } - - return new static($id, $username); - } - - /** - * Returns an avatar object for "nobody". - * - * @return Avatar the user's avatar. - */ - public static function getNobody() - { - return new static(static::NOBODY, static::NOBODY); - } - - /** - * Returns the url to the customized avatars - * - * @return string - */ - public function getAvatarDirectoryUrl() - { - return sprintf( - '%s/%s%s', - $GLOBALS['DYNAMIC_CONTENT_URL'], - static::AVATAR_TYPE, - static::CREATE_CHUNKED_FOLDERS ? '/' . substr($this->user_id, 0, 2) : '' - ); - } - - /** - * Returns the path to the customized avatars - * - * @return string - */ - public function getAvatarDirectoryPath() - { - return sprintf( - '%s/%s%s', - $GLOBALS['DYNAMIC_CONTENT_PATH'], - static::AVATAR_TYPE, - static::CREATE_CHUNKED_FOLDERS ? '/' . substr($this->user_id, 0, 2) : '' - ); - } - - /** - * Returns the url to the default avatars - */ - public function getDefaultAvatarDirectoryUrl(): string - { - return Assets::url('images/avatars/' . static::AVATAR_TYPE); - } - - /** - * Returns the path to the default avatars - */ - public function getDefaultAvatarDirectoryPath(): string - { - return Assets::path('images/avatars/' . static::AVATAR_TYPE); - } - - /** - * Returns the url to a customized avatar - * - * @return string - */ - public function getCustomAvatarUrl($size) - { - if ($this->isNobody()) { - return sprintf( - '%s/%s_%s.%s', - $this->getDefaultAvatarDirectoryUrl(), - $this->user_id, - $size, - self::EXTENSION - ); - } - - return sprintf( - '%s/%s_%s.%s?d=%s', - $this->getAvatarDirectoryUrl(), - $this->user_id, - $size, - self::EXTENSION, - @filemtime($this->getCustomAvatarPath($size)) ?: "0" - ); - } - - /** - * Returns the path to a customized avatar - * - * @return string - */ - public function getCustomAvatarPath($size) - { - if ($this->isNobody()) { - return sprintf( - '%s/%s_%s.%s', - $this->getDefaultAvatarDirectoryPath(), - $this->user_id, - $size, - self::EXTENSION - ); - } - - return sprintf( - '%s/%s_%s.%s', - $this->getAvatarDirectoryPath(), - $this->user_id, - $size, - self::EXTENSION - ); - } - - /** - * Constructs a new Avatar object belonging to a user with the given id. - * - * @param string $user_id the user's id - * @param string $username the user's username (optional) - */ - protected function __construct($user_id, $username = null) - { - $this->user_id = $user_id; - $this->username = $username; - - $this->checkAvatarVisibility(); - } - - /** - * Returns the file name of a user's avatar. - * - * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) - * - * @return string the absolute file path to the avatar - */ - public function getFilename($size) - { - return $this->is_customized() - ? $this->getCustomAvatarPath($size) - : $this->getNobody()->getCustomAvatarPath($size); - } - - /** - * Returns the URL of a user's picture. - * - * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) - * - * @return string the URL to the user's picture - */ - # TODO (mlunzena) in Url umbenennen - public function getURL($size) - { - return $this->is_customized() - ? $this->getCustomAvatarUrl($size) - : $this->getNobody()->getCustomAvatarUrl($size); - } - - /** - * Returns whether this avatar is a default/"nobody" avatar. - */ - public function isNobody(): bool - { - return $this->user_id === static::NOBODY; - } - - /** - * Returns whether a customized file exists - */ - public function customizedFileExists(): bool - { - return file_exists($this->getCustomAvatarPath(static::MEDIUM)); - } - - /** - * Returns whether a user has uploaded a custom picture. - * - * @return boolean returns TRUE if the user customized her picture, FALSE - * otherwise. - */ - public function is_customized() - { - return !$this->isNobody() - && $this->customizedFileExists(); - } - - /** - * Returns the CSS class to use for this avatar image. - * - * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) - * - * @return string CSS class to use for the avatar - */ - protected function getCssClass($size) - { - if (!isset($this->username)) { - $this->username = get_username($this->user_id); - } - - return sprintf( - 'avatar-%s user-%s' . ($this->is_customized() ? '' : ' recolor'), - $size, - htmlReady($this->username) - ); - } - - /** - * Constructs a desired HTML image tag for an Avatar. Additional - * html attributes may also be specified using the $opt parameter. - * - * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) - * @param array $opt array of attributes to add to the HTML image tag - * - * @return string returns the HTML image tag - */ - public function getImageTag($size = self::MEDIUM, $opt = []) - { - $opt['src'] = $this->getURL($size); - - if (isset($opt['class'])) { - $opt['class'] = $this->getCssClass($size) . ' ' . $opt['class']; - } else { - $opt['class'] = $this->getCssClass($size); - } - - // Apply cast to string for title if necessary - if (isset($opt['title']) && !is_string($opt['title'])) { - $opt['title'] = (string) $opt['title']; - } - - if (!empty($opt['title']) && $opt['title'] !== html_entity_decode($opt['title'])) { - // Decode already htmlready encoded titles (which were used until - // all attributes were encoded inside this method) - $opt['title'] = html_entity_decode($opt['title']); - - if (Studip\ENV === 'development') { - $trace = debug_backtrace(); - $caller = array_shift($trace); - - $file = str_replace("{$GLOBALS['STUDIP_BASE_PATH']}/", '', $caller['file']); - trigger_error( - "{$file}:{$caller['line']}: Passes already encoded title to Avatar::getImageTag()", - E_USER_DEPRECATED - ); - } - } - - if (!isset($opt['alt']) && !isset($opt['title'])) { - //Add an empty alt attribute to prevent screen readers from - //reading the URL of the icon: - $opt['alt'] = ''; - } - - return ''; - } - - /** - * Creates all the different sized thumbnails for an uploaded file. - * - * @param string $userfile the key of the uploaded file, see documentation about $_FILES - * - * @return void - */ - public function createFromUpload($userfile) - { - try { - // keine Datei ausgewählt! - if (!$_FILES[$userfile]['name']) { - throw new Exception(_('Sie haben keine Datei zum Hochladen ausgewählt!')); - } - - // Fehler beim Hochladen - if ($_FILES[$userfile]['error'] !== UPLOAD_ERR_OK) { - throw new Exception(_('Es gab einen Fehler beim Hochladen der Datei!')); - } - - - // Bilddatei ist zu groß - if ($_FILES[$userfile]['size'] > self::MAX_FILE_SIZE) { - throw new Exception(sprintf( - _('Die hochgeladene Bilddatei ist %s KB groß. Die maximale Dateigröße beträgt %s KB!'), - round($_FILES[$userfile]['size'] / 1024), - self::MAX_FILE_SIZE / 1024) - ); - } - - // get extension - $pathinfo = pathinfo($_FILES[$userfile]['name']); - $ext = mb_strtolower($pathinfo['extension']); - - // passende Endung ? - if (!in_array($ext, words('jpg jpeg gif png webp'))) { - throw new Exception(sprintf( - _('Der Dateityp der Bilddatei ist falsch (%s). Es sind nur die Dateiendungen .gif, .png, .jpeg, .jpg oder .webp erlaubt!'), - $ext - )); - } - - // na dann kopieren wir mal... - $filename = tempnam($GLOBALS['TMP_PATH'], 'avatar-upload'); - - if (!@move_uploaded_file($_FILES[$userfile]['tmp_name'], $filename)) { - throw new Exception(_("Es ist ein Fehler beim Kopieren der Datei aufgetreten. Das Bild wurde nicht hochgeladen!")); - } - - // set permissions for uploaded file - @chmod($filename, 0666 & ~umask()); - - $this->sanitizeOrientation($filename); - $this->createFrom($filename); - } finally { - if (isset($filename)) { - @unlink($filename); - } - } - } - - /** - * Creates thumbnails from an image. - * - * @param string $filename filename of the image to create thumbnails from - * - * @return void - */ - public function createFrom($filename) - { - if (!extension_loaded('gd')) { - throw new Exception(_('Es ist ein Fehler beim Bearbeiten des Bildes aufgetreten.') . ' (' . _('Fehlende GD-Lib') . ')'); - } - - set_error_handler([__CLASS__, 'error_handler']); - - NotificationCenter::postNotification('AvatarWillCreate', $this->user_id); - $this->resize(static::NORMAL, $filename); - $this->resize(static::MEDIUM, $filename); - $this->resize(static::SMALL, $filename); - NotificationCenter::postNotification('AvatarDidCreate', $this->user_id); - - restore_error_handler(); - } - - /** - * Removes all uploaded pictures of a user. - */ - public function reset() - { - if ($this->is_customized()) { - NotificationCenter::postNotification('AvatarWillDelete', $this->user_id); - @unlink($this->getCustomAvatarPath(static::NORMAL)); - @unlink($this->getCustomAvatarPath(static::SMALL)); - @unlink($this->getCustomAvatarPath(static::MEDIUM)); - NotificationCenter::postNotification('AvatarDidDelete', $this->user_id); - } - } - - /** - * Return the dimension of a size - * - * @param string $size the dimension of a size - * @return array{0: int, 1: int} a tupel of integers [width, height] - */ - public static function getDimension($size) - { - $dimensions = [ - static::NORMAL => [250, 250], - static::MEDIUM => [100, 100], - static::SMALL => [25, 25] - ]; - return $dimensions[$size]; - } - - /** - * Create from an image thumbnails of a specified size. - * - * @param string $size the size of the thumbnail to create - * @param string $filename the filename of the image to make thumbnail of - */ - private function resize(string $size, string $filename) - { - [$thumb_width, $thumb_height] = static::getDimension($size); - - $thumb_width = $thumb_width * 2; - $thumb_height = $thumb_height * 2; - - [$width, $height, $type] = getimagesize($filename); - - # create image resource from filename - $lookup = [ - IMAGETYPE_GIF => 'imagecreatefromgif', - IMAGETYPE_JPEG => 'imagecreatefromjpeg', - IMAGETYPE_PNG => 'imagecreatefrompng', - IMAGETYPE_WEBP => 'imagecreatefromwebp', - ]; - if (!isset($lookup[$type])) { - throw new Exception(_("Der Typ des Bilds wird nicht unterstützt.")); - } - $image = $lookup[$type]($filename); - - imagealphablending($image, false); - imagesavealpha($image, true); - - # resize image if needed - if ($height > $thumb_height || $width > $thumb_width) { - $factor = max($thumb_width / $width, $thumb_height / $height); - $resized_width = round($width * $factor); - $resized_height = round($height * $factor); - } else { - $resized_width = $width; - $resized_height = $height; - } - - $image = self::imageresize($image, $width, $height, $resized_width, $resized_height); - - $dst = imagecreatetruecolor($thumb_width, $thumb_height); - imagealphablending($dst, false); - imagesavealpha($dst, true); - - $trans_colour = imagecolorallocatealpha($dst, 0, 0, 0, 127); - imagefill($dst, 0, 0, $trans_colour); - - // center the new image - $ypos = intval($thumb_height - $resized_height) >> 1; - $xpos = intval($thumb_width - $resized_width) >> 1; - - imagecopy( - $dst, $image, - $xpos, $ypos, - 0, 0, - $resized_width, $resized_height - ); - - $output_file = $this->getCustomAvatarPath($size); - $directory = dirname($output_file); - if (!is_dir($directory) && !mkdir($directory)) { - throw new Exception(_('Das Verzeichnis zum Speichern der Datei konnte nicht angelegt werden.')); - } - - imagewebp($dst, $output_file, self::IMAGE_QUALITY); - } - - private function imageresize($image, $current_width, $current_height, $width, $height) - { - $image_resized = imagecreatetruecolor($width, $height); - - imagealphablending($image_resized, false); - imagesavealpha($image_resized, true); - imagecopyresampled( - $image_resized, $image, - 0, 0, - 0, 0, - $width, $height, - $current_width, $current_height - ); - - return $image_resized; - } - - public static function error_handler($errno, $errstr, $errfile, $errline) - { - if (defined('E_RECOVERABLE_ERROR') - && $errno == constant('E_RECOVERABLE_ERROR')) - { - $message = sprintf( - 'Recoverable error "%s" occured in file %s line %u.', - $errstr, - $errfile, - $errline - ); - throw new Exception($message); - } - - # execute PHP internal error handler - return false; - } - - /** - * Return the default title of the avatar. - * @return string the default title - */ - public function getDefaultTitle() - { - if ($this->isNobody()) { - return static::NOBODY; - } - - require_once 'lib/functions.php'; - return get_fullname($this->user_id); - } - - /** - * Return if avatar is visible to the current user. - * Also set the user_id of avatar to nobody if not visible to current user. - * @return boolean: true if visible - */ - protected function checkAvatarVisibility() - { - $visible = Visibility::verify('picture', $this->user_id); - if (!$visible) { - $this->user_id = self::NOBODY; - } - return $visible; - } - - /** - * Corrects the orientation of images from iOS/OS X devices which might - * lead to a rotated image. EXIF information is checked and when the - * orientation is set by EXIF data, we rotate the image accordingly. - * - * @param string $filename Filename of the image to correct - */ - protected function sanitizeOrientation($filename) - { - if (!function_exists('exif_read_data')) { - return; - } - - if (exif_imagetype($filename) !== IMAGETYPE_JPEG) { - return; - } - - $exif = exif_read_data($filename); - if (!$exif || !$exif['Orientation'] || $exif['Orientation'] == 1) { - return; - } - - $degree = 0; - switch ($exif['Orientation']) { - case 3: - $degree = 180; - break; - case 6: - $degree = -90; - break; - case 8: - $degree = 90; - break; - } - - if ($degree) { - $img = imagecreatefromstring(file_get_contents($filename)); - $img = imagerotate($img, $degree, 0); - - $extension = pathinfo($filename, PATHINFO_EXTENSION); - if ($extension === 'jpg' || $extension === 'jpeg') { - imagejpeg($img, $filename, 95); - } elseif ($extension === 'gif') { - imagegif($img, $filename); - } elseif ($extension === 'png') { - imagepng($img, $filename, 9); - } else { - imagewebp($img, $filename, self::IMAGE_QUALITY); - } - - imagedestroy($img); - } - } -} diff --git a/lib/classes/Avatar.php b/lib/classes/Avatar.php new file mode 100644 index 0000000..959523f --- /dev/null +++ b/lib/classes/Avatar.php @@ -0,0 +1,635 @@ + + * @license GPL2 or any later version + * @since 1.7 + */ +class Avatar +{ + public const AVATAR_TYPE = 'user'; + protected const CREATE_CHUNKED_FOLDERS = true; + + public const EXTENSION = 'webp'; + public const IMAGE_QUALITY = 90; + + /** + * This constant stands for the maximal size of a user picture. + */ + public const ORIGINAL = 'original'; + + /** + * This constant stands for the maximal size of a user picture. + */ + public const NORMAL = 'normal'; + + /** + * This constant stands for a medium size of a user picture. + */ + public const MEDIUM = 'medium'; + + /** + * This constant stands for an icon size of a user picture. + */ + public const SMALL = 'small'; + + /** + * This constant represents the maximal size of a user picture in bytes. + */ + public const MAX_FILE_SIZE = 10485760; + + /** + * This constant holds the username and ID of the "nobody" avatar. + */ + protected const NOBODY = 'nobody'; + + /** + * Holds the user's id + * + * @var string + */ + protected $user_id; + + /** + * Holds the user's username + * + * @var string + */ + protected $username; + + /** + * Returns an avatar object of the appropriate class. + * + * @param string $id the user's id + * @param string $username the user's username (optional) + * + * @return static the user's avatar. + */ + public static function getAvatar($id) + { + $username = null; + + if (func_num_args() === 2) { + $username = func_get_arg(1); + } + + return new static($id, $username); + } + + /** + * Returns an avatar object for "nobody". + * + * @return Avatar the user's avatar. + */ + public static function getNobody() + { + return new static(static::NOBODY, static::NOBODY); + } + + /** + * Returns the url to the customized avatars + * + * @return string + */ + public function getAvatarDirectoryUrl() + { + return sprintf( + '%s/%s%s', + $GLOBALS['DYNAMIC_CONTENT_URL'], + static::AVATAR_TYPE, + static::CREATE_CHUNKED_FOLDERS ? '/' . substr($this->user_id, 0, 2) : '' + ); + } + + /** + * Returns the path to the customized avatars + * + * @return string + */ + public function getAvatarDirectoryPath() + { + return sprintf( + '%s/%s%s', + $GLOBALS['DYNAMIC_CONTENT_PATH'], + static::AVATAR_TYPE, + static::CREATE_CHUNKED_FOLDERS ? '/' . substr($this->user_id, 0, 2) : '' + ); + } + + /** + * Returns the url to the default avatars + */ + public function getDefaultAvatarDirectoryUrl(): string + { + return Assets::url('images/avatars/' . static::AVATAR_TYPE); + } + + /** + * Returns the path to the default avatars + */ + public function getDefaultAvatarDirectoryPath(): string + { + return Assets::path('images/avatars/' . static::AVATAR_TYPE); + } + + /** + * Returns the url to a customized avatar + * + * @return string + */ + public function getCustomAvatarUrl($size) + { + if ($this->isNobody()) { + return sprintf( + '%s/%s_%s.%s', + $this->getDefaultAvatarDirectoryUrl(), + $this->user_id, + $size, + self::EXTENSION + ); + } + + return sprintf( + '%s/%s_%s.%s?d=%s', + $this->getAvatarDirectoryUrl(), + $this->user_id, + $size, + self::EXTENSION, + @filemtime($this->getCustomAvatarPath($size)) ?: "0" + ); + } + + /** + * Returns the path to a customized avatar + * + * @return string + */ + public function getCustomAvatarPath($size) + { + if ($this->isNobody()) { + return sprintf( + '%s/%s_%s.%s', + $this->getDefaultAvatarDirectoryPath(), + $this->user_id, + $size, + self::EXTENSION + ); + } + + return sprintf( + '%s/%s_%s.%s', + $this->getAvatarDirectoryPath(), + $this->user_id, + $size, + self::EXTENSION + ); + } + + /** + * Constructs a new Avatar object belonging to a user with the given id. + * + * @param string $user_id the user's id + * @param string $username the user's username (optional) + */ + protected function __construct($user_id, $username = null) + { + $this->user_id = $user_id; + $this->username = $username; + + $this->checkAvatarVisibility(); + } + + /** + * Returns the file name of a user's avatar. + * + * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) + * + * @return string the absolute file path to the avatar + */ + public function getFilename($size) + { + return $this->is_customized() + ? $this->getCustomAvatarPath($size) + : $this->getNobody()->getCustomAvatarPath($size); + } + + /** + * Returns the URL of a user's picture. + * + * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) + * + * @return string the URL to the user's picture + */ + # TODO (mlunzena) in Url umbenennen + public function getURL($size) + { + return $this->is_customized() + ? $this->getCustomAvatarUrl($size) + : $this->getNobody()->getCustomAvatarUrl($size); + } + + /** + * Returns whether this avatar is a default/"nobody" avatar. + */ + public function isNobody(): bool + { + return $this->user_id === static::NOBODY; + } + + /** + * Returns whether a customized file exists + */ + public function customizedFileExists(): bool + { + return file_exists($this->getCustomAvatarPath(static::MEDIUM)); + } + + /** + * Returns whether a user has uploaded a custom picture. + * + * @return boolean returns TRUE if the user customized her picture, FALSE + * otherwise. + */ + public function is_customized() + { + return !$this->isNobody() + && $this->customizedFileExists(); + } + + /** + * Returns the CSS class to use for this avatar image. + * + * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) + * + * @return string CSS class to use for the avatar + */ + protected function getCssClass($size) + { + if (!isset($this->username)) { + $this->username = get_username($this->user_id); + } + + return sprintf( + 'avatar-%s user-%s' . ($this->is_customized() ? '' : ' recolor'), + $size, + htmlReady($this->username) + ); + } + + /** + * Constructs a desired HTML image tag for an Avatar. Additional + * html attributes may also be specified using the $opt parameter. + * + * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) + * @param array $opt array of attributes to add to the HTML image tag + * + * @return string returns the HTML image tag + */ + public function getImageTag($size = self::MEDIUM, $opt = []) + { + $opt['src'] = $this->getURL($size); + + if (isset($opt['class'])) { + $opt['class'] = $this->getCssClass($size) . ' ' . $opt['class']; + } else { + $opt['class'] = $this->getCssClass($size); + } + + // Apply cast to string for title if necessary + if (isset($opt['title']) && !is_string($opt['title'])) { + $opt['title'] = (string) $opt['title']; + } + + if (!empty($opt['title']) && $opt['title'] !== html_entity_decode($opt['title'])) { + // Decode already htmlready encoded titles (which were used until + // all attributes were encoded inside this method) + $opt['title'] = html_entity_decode($opt['title']); + + if (Studip\ENV === 'development') { + $trace = debug_backtrace(); + $caller = array_shift($trace); + + $file = str_replace("{$GLOBALS['STUDIP_BASE_PATH']}/", '', $caller['file']); + trigger_error( + "{$file}:{$caller['line']}: Passes already encoded title to Avatar::getImageTag()", + E_USER_DEPRECATED + ); + } + } + + if (!isset($opt['alt']) && !isset($opt['title'])) { + //Add an empty alt attribute to prevent screen readers from + //reading the URL of the icon: + $opt['alt'] = ''; + } + + return ''; + } + + /** + * Creates all the different sized thumbnails for an uploaded file. + * + * @param string $userfile the key of the uploaded file, see documentation about $_FILES + * + * @return void + */ + public function createFromUpload($userfile) + { + try { + // keine Datei ausgewählt! + if (!$_FILES[$userfile]['name']) { + throw new Exception(_('Sie haben keine Datei zum Hochladen ausgewählt!')); + } + + // Fehler beim Hochladen + if ($_FILES[$userfile]['error'] !== UPLOAD_ERR_OK) { + throw new Exception(_('Es gab einen Fehler beim Hochladen der Datei!')); + } + + + // Bilddatei ist zu groß + if ($_FILES[$userfile]['size'] > self::MAX_FILE_SIZE) { + throw new Exception(sprintf( + _('Die hochgeladene Bilddatei ist %s KB groß. Die maximale Dateigröße beträgt %s KB!'), + round($_FILES[$userfile]['size'] / 1024), + self::MAX_FILE_SIZE / 1024) + ); + } + + // get extension + $pathinfo = pathinfo($_FILES[$userfile]['name']); + $ext = mb_strtolower($pathinfo['extension']); + + // passende Endung ? + if (!in_array($ext, words('jpg jpeg gif png webp'))) { + throw new Exception(sprintf( + _('Der Dateityp der Bilddatei ist falsch (%s). Es sind nur die Dateiendungen .gif, .png, .jpeg, .jpg oder .webp erlaubt!'), + $ext + )); + } + + // na dann kopieren wir mal... + $filename = tempnam($GLOBALS['TMP_PATH'], 'avatar-upload'); + + if (!@move_uploaded_file($_FILES[$userfile]['tmp_name'], $filename)) { + throw new Exception(_("Es ist ein Fehler beim Kopieren der Datei aufgetreten. Das Bild wurde nicht hochgeladen!")); + } + + // set permissions for uploaded file + @chmod($filename, 0666 & ~umask()); + + $this->sanitizeOrientation($filename); + $this->createFrom($filename); + } finally { + if (isset($filename)) { + @unlink($filename); + } + } + } + + /** + * Creates thumbnails from an image. + * + * @param string $filename filename of the image to create thumbnails from + * + * @return void + */ + public function createFrom($filename) + { + if (!extension_loaded('gd')) { + throw new Exception(_('Es ist ein Fehler beim Bearbeiten des Bildes aufgetreten.') . ' (' . _('Fehlende GD-Lib') . ')'); + } + + set_error_handler([__CLASS__, 'error_handler']); + + NotificationCenter::postNotification('AvatarWillCreate', $this->user_id); + $this->resize(static::NORMAL, $filename); + $this->resize(static::MEDIUM, $filename); + $this->resize(static::SMALL, $filename); + NotificationCenter::postNotification('AvatarDidCreate', $this->user_id); + + restore_error_handler(); + } + + /** + * Removes all uploaded pictures of a user. + */ + public function reset() + { + if ($this->is_customized()) { + NotificationCenter::postNotification('AvatarWillDelete', $this->user_id); + @unlink($this->getCustomAvatarPath(static::NORMAL)); + @unlink($this->getCustomAvatarPath(static::SMALL)); + @unlink($this->getCustomAvatarPath(static::MEDIUM)); + NotificationCenter::postNotification('AvatarDidDelete', $this->user_id); + } + } + + /** + * Return the dimension of a size + * + * @param string $size the dimension of a size + * @return array{0: int, 1: int} a tupel of integers [width, height] + */ + public static function getDimension($size) + { + $dimensions = [ + static::NORMAL => [250, 250], + static::MEDIUM => [100, 100], + static::SMALL => [25, 25] + ]; + return $dimensions[$size]; + } + + /** + * Create from an image thumbnails of a specified size. + * + * @param string $size the size of the thumbnail to create + * @param string $filename the filename of the image to make thumbnail of + */ + private function resize(string $size, string $filename) + { + [$thumb_width, $thumb_height] = static::getDimension($size); + + $thumb_width = $thumb_width * 2; + $thumb_height = $thumb_height * 2; + + [$width, $height, $type] = getimagesize($filename); + + # create image resource from filename + $lookup = [ + IMAGETYPE_GIF => 'imagecreatefromgif', + IMAGETYPE_JPEG => 'imagecreatefromjpeg', + IMAGETYPE_PNG => 'imagecreatefrompng', + IMAGETYPE_WEBP => 'imagecreatefromwebp', + ]; + if (!isset($lookup[$type])) { + throw new Exception(_("Der Typ des Bilds wird nicht unterstützt.")); + } + $image = $lookup[$type]($filename); + + imagealphablending($image, false); + imagesavealpha($image, true); + + # resize image if needed + if ($height > $thumb_height || $width > $thumb_width) { + $factor = max($thumb_width / $width, $thumb_height / $height); + $resized_width = round($width * $factor); + $resized_height = round($height * $factor); + } else { + $resized_width = $width; + $resized_height = $height; + } + + $image = self::imageresize($image, $width, $height, $resized_width, $resized_height); + + $dst = imagecreatetruecolor($thumb_width, $thumb_height); + imagealphablending($dst, false); + imagesavealpha($dst, true); + + $trans_colour = imagecolorallocatealpha($dst, 0, 0, 0, 127); + imagefill($dst, 0, 0, $trans_colour); + + // center the new image + $ypos = intval($thumb_height - $resized_height) >> 1; + $xpos = intval($thumb_width - $resized_width) >> 1; + + imagecopy( + $dst, $image, + $xpos, $ypos, + 0, 0, + $resized_width, $resized_height + ); + + $output_file = $this->getCustomAvatarPath($size); + $directory = dirname($output_file); + if (!is_dir($directory) && !mkdir($directory)) { + throw new Exception(_('Das Verzeichnis zum Speichern der Datei konnte nicht angelegt werden.')); + } + + imagewebp($dst, $output_file, self::IMAGE_QUALITY); + } + + private function imageresize($image, $current_width, $current_height, $width, $height) + { + $image_resized = imagecreatetruecolor($width, $height); + + imagealphablending($image_resized, false); + imagesavealpha($image_resized, true); + imagecopyresampled( + $image_resized, $image, + 0, 0, + 0, 0, + $width, $height, + $current_width, $current_height + ); + + return $image_resized; + } + + public static function error_handler($errno, $errstr, $errfile, $errline) + { + if (defined('E_RECOVERABLE_ERROR') + && $errno == constant('E_RECOVERABLE_ERROR')) + { + $message = sprintf( + 'Recoverable error "%s" occured in file %s line %u.', + $errstr, + $errfile, + $errline + ); + throw new Exception($message); + } + + # execute PHP internal error handler + return false; + } + + /** + * Return the default title of the avatar. + * @return string the default title + */ + public function getDefaultTitle() + { + if ($this->isNobody()) { + return static::NOBODY; + } + + require_once 'lib/functions.php'; + return get_fullname($this->user_id); + } + + /** + * Return if avatar is visible to the current user. + * Also set the user_id of avatar to nobody if not visible to current user. + * @return boolean: true if visible + */ + protected function checkAvatarVisibility() + { + $visible = Visibility::verify('picture', $this->user_id); + if (!$visible) { + $this->user_id = self::NOBODY; + } + return $visible; + } + + /** + * Corrects the orientation of images from iOS/OS X devices which might + * lead to a rotated image. EXIF information is checked and when the + * orientation is set by EXIF data, we rotate the image accordingly. + * + * @param string $filename Filename of the image to correct + */ + protected function sanitizeOrientation($filename) + { + if (!function_exists('exif_read_data')) { + return; + } + + if (exif_imagetype($filename) !== IMAGETYPE_JPEG) { + return; + } + + $exif = exif_read_data($filename); + if (!$exif || !$exif['Orientation'] || $exif['Orientation'] == 1) { + return; + } + + $degree = 0; + switch ($exif['Orientation']) { + case 3: + $degree = 180; + break; + case 6: + $degree = -90; + break; + case 8: + $degree = 90; + break; + } + + if ($degree) { + $img = imagecreatefromstring(file_get_contents($filename)); + $img = imagerotate($img, $degree, 0); + + $extension = pathinfo($filename, PATHINFO_EXTENSION); + if ($extension === 'jpg' || $extension === 'jpeg') { + imagejpeg($img, $filename, 95); + } elseif ($extension === 'gif') { + imagegif($img, $filename); + } elseif ($extension === 'png') { + imagepng($img, $filename, 9); + } else { + imagewebp($img, $filename, self::IMAGE_QUALITY); + } + + imagedestroy($img); + } + } +} diff --git a/lib/classes/BreadCrumb.class.php b/lib/classes/BreadCrumb.class.php deleted file mode 100644 index 73a2086..0000000 --- a/lib/classes/BreadCrumb.class.php +++ /dev/null @@ -1,103 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 3.5 - */ -class BreadCrumb -{ - - /** - * Array with parts of bread crumb navigation. - * - * @var array - */ - private $trail = []; - - public function __construct() - { - URLHelper::bindLinkParam('trail', $this->trail); - } - - /** - * Appends a new element to the end of the bread crumb navigation. - * - * @param MvvTreeItem $object The MvvTreeItem object of the current view - * to append. - * @param string $action The url to the current view. - */ - public function append($object, $action) - { - $trail = $this->getTrail(); - $id = ''; - if (is_object($object)) { - $type = get_class($object); - $id = $object->id; - $trail[$type] = [ - 'id' => $object->id, - 'actn' => $action - ]; - } else if (is_array($object)) { - $id = reset($object)->id; - $type = get_class(reset($object)); - foreach ($object as $obj) { - if ($obj && $obj->id != $id) { - $additional_objects[get_class($obj)] = $obj->id; - } - } - $trail[$type] = [ - 'id' => $id, - 'add' => $additional_objects, - 'actn' => $action - ]; - } else { - $trail[$action] = [ - 'name' => $object, - 'actn' => $action - ]; - } - $newTrail = []; - $lastElement = false; - foreach ($trail as $key => $trail_item) { - if ($lastElement) break; - $newTrail[$key] = $trail_item; - $lastElement = $key === $id; - } - $this->trail = $newTrail; - } - - /** - * Removes the last element from the bread crumb navigation. - */ - public function pop() - { - array_pop($this->trail); - } - - /** - * Returns all elements of the bread crumb navigation. - * - * @return array All elements of the bread crumb navigation - */ - public function getTrail() - { - return $this->trail; - } - - /** - * Initialize a new bread crumb navigation. - */ - public function init() - { - $this->trail = []; - } - -} diff --git a/lib/classes/BreadCrumb.php b/lib/classes/BreadCrumb.php new file mode 100644 index 0000000..73a2086 --- /dev/null +++ b/lib/classes/BreadCrumb.php @@ -0,0 +1,103 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 3.5 + */ +class BreadCrumb +{ + + /** + * Array with parts of bread crumb navigation. + * + * @var array + */ + private $trail = []; + + public function __construct() + { + URLHelper::bindLinkParam('trail', $this->trail); + } + + /** + * Appends a new element to the end of the bread crumb navigation. + * + * @param MvvTreeItem $object The MvvTreeItem object of the current view + * to append. + * @param string $action The url to the current view. + */ + public function append($object, $action) + { + $trail = $this->getTrail(); + $id = ''; + if (is_object($object)) { + $type = get_class($object); + $id = $object->id; + $trail[$type] = [ + 'id' => $object->id, + 'actn' => $action + ]; + } else if (is_array($object)) { + $id = reset($object)->id; + $type = get_class(reset($object)); + foreach ($object as $obj) { + if ($obj && $obj->id != $id) { + $additional_objects[get_class($obj)] = $obj->id; + } + } + $trail[$type] = [ + 'id' => $id, + 'add' => $additional_objects, + 'actn' => $action + ]; + } else { + $trail[$action] = [ + 'name' => $object, + 'actn' => $action + ]; + } + $newTrail = []; + $lastElement = false; + foreach ($trail as $key => $trail_item) { + if ($lastElement) break; + $newTrail[$key] = $trail_item; + $lastElement = $key === $id; + } + $this->trail = $newTrail; + } + + /** + * Removes the last element from the bread crumb navigation. + */ + public function pop() + { + array_pop($this->trail); + } + + /** + * Returns all elements of the bread crumb navigation. + * + * @return array All elements of the bread crumb navigation + */ + public function getTrail() + { + return $this->trail; + } + + /** + * Initialize a new bread crumb navigation. + */ + public function init() + { + $this->trail = []; + } + +} diff --git a/lib/classes/Button.class.php b/lib/classes/Button.class.php deleted file mode 100644 index 1c2c437..0000000 --- a/lib/classes/Button.class.php +++ /dev/null @@ -1,58 +0,0 @@ - HTML element. - * - * @param string $label the label of the button element - * @param string $name the @name element of the button element - * @param array $attributes the attributes of the button element - */ - protected function initialize($label, $name, $attributes) - { - $this->attributes['name'] = $name ?: $this->label; - } - - /** - * @return string returns a HTML representation of this button. - */ - public function __toString() - { - // add "button" to attribute @class - if (!isset($this->attributes['class'])) { - $this->attributes['class'] = ''; - } - $this->attributes['class'] .= ' button'; - - $attributes = []; - ksort($this->attributes); - foreach ($this->attributes as $k => $v) { - $attributes[] = sprintf(' %s="%s"', $k, htmlReady($v)); - } - - return sprintf( - '', - join('', $attributes), - htmlReady($this->label) - ); - } -} diff --git a/lib/classes/Button.php b/lib/classes/Button.php new file mode 100644 index 0000000..1c2c437 --- /dev/null +++ b/lib/classes/Button.php @@ -0,0 +1,58 @@ + HTML element. + * + * @param string $label the label of the button element + * @param string $name the @name element of the button element + * @param array $attributes the attributes of the button element + */ + protected function initialize($label, $name, $attributes) + { + $this->attributes['name'] = $name ?: $this->label; + } + + /** + * @return string returns a HTML representation of this button. + */ + public function __toString() + { + // add "button" to attribute @class + if (!isset($this->attributes['class'])) { + $this->attributes['class'] = ''; + } + $this->attributes['class'] .= ' button'; + + $attributes = []; + ksort($this->attributes); + foreach ($this->attributes as $k => $v) { + $attributes[] = sprintf(' %s="%s"', $k, htmlReady($v)); + } + + return sprintf( + '', + join('', $attributes), + htmlReady($this->label) + ); + } +} diff --git a/lib/classes/CSVArrayObject.class.php b/lib/classes/CSVArrayObject.class.php deleted file mode 100644 index f075795..0000000 --- a/lib/classes/CSVArrayObject.class.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @link http://www.php.net/manual/en/class.arrayobject.php - */ -class CSVArrayObject extends StudipArrayObject -{ - /** - * Construct an array object from a string of comma separated items - * - * @param string $input a string of comma separated items - */ - function __construct($input) - { - if (is_string($input)) { - $input = mb_strlen($input) ? array_map('trim', explode(',', $input)) : []; - } - parent::__construct((array)$input); - } - - /** - * magic method for use of object in string context - * - * @return string internal array itmes converted to a comma separated list - */ - function __toString() - { - return implode(',', $this->getArrayCopy()); - } -} diff --git a/lib/classes/CSVArrayObject.php b/lib/classes/CSVArrayObject.php new file mode 100644 index 0000000..f075795 --- /dev/null +++ b/lib/classes/CSVArrayObject.php @@ -0,0 +1,47 @@ + + * @link http://www.php.net/manual/en/class.arrayobject.php + */ +class CSVArrayObject extends StudipArrayObject +{ + /** + * Construct an array object from a string of comma separated items + * + * @param string $input a string of comma separated items + */ + function __construct($input) + { + if (is_string($input)) { + $input = mb_strlen($input) ? array_map('trim', explode(',', $input)) : []; + } + parent::__construct((array)$input); + } + + /** + * magic method for use of object in string context + * + * @return string internal array itmes converted to a comma separated list + */ + function __toString() + { + return implode(',', $this->getArrayCopy()); + } +} diff --git a/lib/classes/Color.class.php b/lib/classes/Color.class.php deleted file mode 100644 index a9b4506..0000000 --- a/lib/classes/Color.class.php +++ /dev/null @@ -1,370 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * class to mix colors and convert them between different types - * - * @since 2.1 - */ -class Color { - /** - * HTML-4-Standard color-names plus a few additions - * May be expanded in a later version to allow - * X11 color names as described in - * http://en.wikipedia.org/wiki/Web_colors - * - * @var array - */ - static $colorstrings = [ - 'aqua' => [0, 255, 255, 1], - 'azure' => [240, 255, 255, 1], - 'blue' => [0, 0, 255, 1], - 'cyan' => [0, 255, 255, 1], - 'darkblue' => [0, 0, 139, 1], - 'darkred' => [139, 0, 0, 1], - 'gold' => [255, 215, 0, 1], - 'gray' => [128, 128, 128, 1], - 'indigo' => [75, 0, 130, 1], - 'lightsteelblue' => [176, 196, 222, 1], - 'lightyellow' => [255, 255, 224, 1], - 'lime' => [0, 255, 0, 1], - 'magenta' => [255, 0, 255, 1], - 'navy' => [0, 0, 128, 1], - 'olive' => [128, 128, 0, 1], - 'orange' => [255, 165, 0, 1], - 'pink' => [255, 192, 203, 1], - 'red' => [255, 0, 0, 1], - 'purple' => [128, 0, 128, 1], - 'violet' => [238, 130, 238, 1], - 'yellow' => [255, 255, 0, 1], - 'black' => [0, 0, 0, 1], - 'white' => [255, 255, 255, 1] - ]; - - /** - * converts a css-hex-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function hex2array($color) { - $color = str_replace('#','',$color); - $arr[0] = hexdec(mb_substr($color,0,2)); - $arr[1] = hexdec(mb_substr($color,2,2)); - $arr[2] = hexdec(mb_substr($color,4,2)); - $arr[3] = 1.0; - return $arr; - } - - /** - * converts a css-rgba-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function rgba2array($color) { - preg_match("/rgba\(\s*(\d+\%?),\s*(\d+\%?),\s*(\d+\%?),\s*(\d*\.?\d*)\s*\)/", $color, $matches); - array_shift($matches); - $matches[3] = floatval($matches[3]); - return $matches; - } - - /** - * converts a css-rgb-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function rgb2array($color) { - preg_match("/rgb\(\s*(\d+\%?),\s*(\d+\%?),\s*(\d+\%?)\s*\)/", $color, $matches); - array_shift($matches); - $matches[3] = 1.0; - return $matches; - } - - /** - * converts a css-hsl-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function hsl2array($color) { - preg_match("/hsl\(\s*(\d+),\s*(\d+)\%,\s*(\d+)\%\s*\)/", $color, $matches); - array_shift($matches); - $matches[0] %= 360; - $matches[3] = 1.0; - $h = $matches[0]; - $s = $matches[1]; - $l = $matches[2]; - $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s; - $m1 = $l * 2 - $m2; - return [self::_color_hue2rgb($m1, $m2, $h + 0.33333), - self::_color_hue2rgb($m1, $m2, $h), - self::_color_hue2rgb($m1, $m2, $h - 0.33333), - 1.0 - ]; - } - - static private function _color_hue2rgb($m1, $m2, $h) { - $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h); - if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6; - if ($h * 2 < 1) return $m2; - if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6; - return $m1; - } - - /** - * Mixes two colors by preferring the first one with $percentOfColor1 percent. - * If color2 is the only color with alpha-chanel, it will return a color - * in the format of color2 else (most of the time) in the format of color1. - * @return string: "#ffffff", "rgb(...)", "rgba(...)" depending on color1 and alpha-chanel - */ - static function mix($color1, $color2, $percentOfColor1 = 50, $f = null) { - $percentOfColor1 = $percentOfColor1 > 100 - ? 100 - : ($percentOfColor1 < 0 ? 0 : $percentOfColor1); - list($color1, $format1) = self::_normalize($color1); - list($color2, $format2) = self::_normalize($color2); - $color_new[0] = floor(($color1[0] * $percentOfColor1 - + $color2[0] * (100 - $percentOfColor1)) / 100); - $color_new[1] = floor(($color1[1] * $percentOfColor1 - + $color2[1] * (100 - $percentOfColor1)) / 100); - $color_new[2] = floor(($color1[2] * $percentOfColor1 - + $color2[2] * (100 - $percentOfColor1)) / 100); - $color_new[3] = ($color1[3] * $percentOfColor1 - + $color2[3] * (100 - $percentOfColor1)) / 100; - $format = ((mb_strpos($format2, "a") !== false) && (mb_strpos($format1, "a") === false)) - ? $format2 - : $format1; - $func = "_array2" . $format; - return self::$func($color_new); - } - - /** - * Sets the opacity for a color. Hue and saturation is kept, but opacity is changed. - * Returns a format with alpha-chanel (rgba or hsla) depending on the given format. - */ - static function opacity($color, $opacity) { - list($color, $format) = self::_normalize($color); - if (in_array($format, ["hex", "rgb"])) { - $format = "rgba"; - } elseif ($format = "hsl") { - $format = "hsla"; - } - $color[3] = floatval($opacity); - $color[3] = $color[3] < 0 ? 0 : ($color[3] > 1 ? 1 : $color[3]); - $func = "_array2" . $format; - return self::$func($color); - } - - /** - * Make the passed color brighter - * - * @author Till Glöggler - * - * @param string $color any type of css-valid color - * @param int $factor percentage of brightning, 100 for white and 0 for no changes to color - * @return string brightened color in same format as the passed one - */ - static function brighten($color, $factor = 35) { - // convert to color to rgba - list($color, $format) = self::_normalize($color); - if ($factor > 100) { - $factor = 100; - } elseif($factor < 0) { - $factor = 0; - } - - // return the color itself, if the conversion failed - if (!$format) return $color; - - // brighten the color - $color[0] = floor(($color[0] * (100 - $factor) + 255 * $factor) / 100); - $color[1] = floor(($color[1] * (100 - $factor) + 255 * $factor) / 100); - $color[2] = floor(($color[2] * (100 - $factor) + 255 * $factor) / 100); - - // convert the color back (if possible) - $func = "_array2" . $format; - return self::$func($color); - } - - /** - * converts any css-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - private static function _normalize($color) { - if ($color[0] === "#") { - $format = "hex"; - $arr = self::hex2array($color); - } elseif (preg_match("/\(.*\)/", $color)) { - $format = mb_substr($color, 0, mb_strpos($color, "(")); - $func = $format."2array"; - $arr = self::$func($color); - } elseif (self::$colorstrings[mb_strtolower($color)]) { - $format = "rgb"; //we don't want colors as strings like "red" - $arr = self::$colorstrings[mb_strtolower($color)]; - } - return [$arr, $format]; - } - - /** - * converts a css-rgba-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function rgba($color) { - list($arr, $format) = self::_normalize($color); - return self::_array2rgba($arr); - } - - /** - * converts a css-rgb-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function rgb($color) { - list($arr, $format) = self::_normalize($color); - return self::_array2rgb($arr); - } - - /** - * converts a css-hex-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function hex($color) { - list($arr, $format) = self::_normalize($color); - return self::_array2hex($arr); - } - - /** - * converts a css-hsl-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function hsl($color) { - list($arr, $format) = self::_normalize($color); - return self::_array2hsl($arr); - } - - /** - * converts a css-hsla-color into a rgba-quadruple - * - * @param string $color the color to be converted - * @return array colors as rgba-quadruple - */ - static function hsla($color) { - list($arr, $format) = self::_normalize($color); - return self::_array2hsla($arr); - } - - - /** - * converts a rgba-quadruple into a css-hex-color - * - * @param array $arr the rgba-quadruple to be converted - * @return array colors as css-hex - */ - static private function _array2hex($arr) { - return "#" . - ($arr[0] < 16 ? "0" : "").dechex($arr[0]) . - ($arr[1] < 16 ? "0" : "").dechex($arr[1]) . - ($arr[2] < 16 ? "0" : "").dechex($arr[2]); - } - - /** - * converts a rgba-quadruple into a css-rgb-color - * - * @param array $arr the rgba-quadruple to be converted - * @return array colors as css-rgb - */ - static private function _array2rgb($arr) { - array_pop($arr); - return "rgb(".implode(", ", $arr).")"; - } - - /** - * converts a rgba-quadruple into a css-rgba-color - * - * @param array $arr the rgba-quadruple to be converted - * @return array colors as css-rgba - */ - static private function _array2rgba($arr) { - return "rgba(".implode(", ", $arr).")"; - } - - /** - * converts a rgba-quadruple into a css-hsl-color - * - * @param array $arr the rgba-quadruple to be converted - * @return array colors as css-hsl - */ - static private function _array2hsl($arr) { - $arr = self::_calculate_hsl($arr); - return "hsl(".$arr[0].", ".$arr[1]."%, ".$arr[2]."%)"; - } - - /** - * converts a rgba-quadruple into a css-hsla-color - * - * @param array $arr the rgba-quadruple to be converted - * @return array colors as css-hsla - */ - static private function _array2hsla($arr) { - $hsl = self::_calculate_hsl($arr); - return "hsl(".$hsl[0].", ".$hsl[1]."%, ".$hsl[2]."%, ".$arr[3].")"; - } - - static private function _calculate_hsl($arr) { - $clrR = ($arr[0]); - $clrG = ($arr[1]); - $clrB = ($arr[2]); - - $clrMin = min($clrR, $clrG, $clrB); - $clrMax = max($clrR, $clrG, $clrB); - $deltaMax = $clrMax - $clrMin; - - $L = ($clrMax + $clrMin) / 510; - - if (0 == $deltaMax) { - $H = 0; - $S = 0; - } else { - if (0.5 > $L) { - $S = $deltaMax / ($clrMax + $clrMin); - } else { - $S = $deltaMax / (510 - $clrMax - $clrMin); - } - - if ($clrMax == $clrR) { - $H = ($clrG - $clrB) / (6.0 * $deltaMax); - } else if ($clrMax == $clrG) { - $H = 1/3 + ($clrB - $clrR) / (6.0 * $deltaMax); - } else { - $H = 2 / 3 + ($clrR - $clrG) / (6.0 * $deltaMax); - } - - if (0 > $H) $H += 1; - if (1 < $H) $H -= 1; - } - return [$H, $S, $L]; - } - -} \ No newline at end of file diff --git a/lib/classes/Color.php b/lib/classes/Color.php new file mode 100644 index 0000000..a9b4506 --- /dev/null +++ b/lib/classes/Color.php @@ -0,0 +1,370 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * class to mix colors and convert them between different types + * + * @since 2.1 + */ +class Color { + /** + * HTML-4-Standard color-names plus a few additions + * May be expanded in a later version to allow + * X11 color names as described in + * http://en.wikipedia.org/wiki/Web_colors + * + * @var array + */ + static $colorstrings = [ + 'aqua' => [0, 255, 255, 1], + 'azure' => [240, 255, 255, 1], + 'blue' => [0, 0, 255, 1], + 'cyan' => [0, 255, 255, 1], + 'darkblue' => [0, 0, 139, 1], + 'darkred' => [139, 0, 0, 1], + 'gold' => [255, 215, 0, 1], + 'gray' => [128, 128, 128, 1], + 'indigo' => [75, 0, 130, 1], + 'lightsteelblue' => [176, 196, 222, 1], + 'lightyellow' => [255, 255, 224, 1], + 'lime' => [0, 255, 0, 1], + 'magenta' => [255, 0, 255, 1], + 'navy' => [0, 0, 128, 1], + 'olive' => [128, 128, 0, 1], + 'orange' => [255, 165, 0, 1], + 'pink' => [255, 192, 203, 1], + 'red' => [255, 0, 0, 1], + 'purple' => [128, 0, 128, 1], + 'violet' => [238, 130, 238, 1], + 'yellow' => [255, 255, 0, 1], + 'black' => [0, 0, 0, 1], + 'white' => [255, 255, 255, 1] + ]; + + /** + * converts a css-hex-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function hex2array($color) { + $color = str_replace('#','',$color); + $arr[0] = hexdec(mb_substr($color,0,2)); + $arr[1] = hexdec(mb_substr($color,2,2)); + $arr[2] = hexdec(mb_substr($color,4,2)); + $arr[3] = 1.0; + return $arr; + } + + /** + * converts a css-rgba-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function rgba2array($color) { + preg_match("/rgba\(\s*(\d+\%?),\s*(\d+\%?),\s*(\d+\%?),\s*(\d*\.?\d*)\s*\)/", $color, $matches); + array_shift($matches); + $matches[3] = floatval($matches[3]); + return $matches; + } + + /** + * converts a css-rgb-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function rgb2array($color) { + preg_match("/rgb\(\s*(\d+\%?),\s*(\d+\%?),\s*(\d+\%?)\s*\)/", $color, $matches); + array_shift($matches); + $matches[3] = 1.0; + return $matches; + } + + /** + * converts a css-hsl-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function hsl2array($color) { + preg_match("/hsl\(\s*(\d+),\s*(\d+)\%,\s*(\d+)\%\s*\)/", $color, $matches); + array_shift($matches); + $matches[0] %= 360; + $matches[3] = 1.0; + $h = $matches[0]; + $s = $matches[1]; + $l = $matches[2]; + $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s; + $m1 = $l * 2 - $m2; + return [self::_color_hue2rgb($m1, $m2, $h + 0.33333), + self::_color_hue2rgb($m1, $m2, $h), + self::_color_hue2rgb($m1, $m2, $h - 0.33333), + 1.0 + ]; + } + + static private function _color_hue2rgb($m1, $m2, $h) { + $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h); + if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6; + if ($h * 2 < 1) return $m2; + if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6; + return $m1; + } + + /** + * Mixes two colors by preferring the first one with $percentOfColor1 percent. + * If color2 is the only color with alpha-chanel, it will return a color + * in the format of color2 else (most of the time) in the format of color1. + * @return string: "#ffffff", "rgb(...)", "rgba(...)" depending on color1 and alpha-chanel + */ + static function mix($color1, $color2, $percentOfColor1 = 50, $f = null) { + $percentOfColor1 = $percentOfColor1 > 100 + ? 100 + : ($percentOfColor1 < 0 ? 0 : $percentOfColor1); + list($color1, $format1) = self::_normalize($color1); + list($color2, $format2) = self::_normalize($color2); + $color_new[0] = floor(($color1[0] * $percentOfColor1 + + $color2[0] * (100 - $percentOfColor1)) / 100); + $color_new[1] = floor(($color1[1] * $percentOfColor1 + + $color2[1] * (100 - $percentOfColor1)) / 100); + $color_new[2] = floor(($color1[2] * $percentOfColor1 + + $color2[2] * (100 - $percentOfColor1)) / 100); + $color_new[3] = ($color1[3] * $percentOfColor1 + + $color2[3] * (100 - $percentOfColor1)) / 100; + $format = ((mb_strpos($format2, "a") !== false) && (mb_strpos($format1, "a") === false)) + ? $format2 + : $format1; + $func = "_array2" . $format; + return self::$func($color_new); + } + + /** + * Sets the opacity for a color. Hue and saturation is kept, but opacity is changed. + * Returns a format with alpha-chanel (rgba or hsla) depending on the given format. + */ + static function opacity($color, $opacity) { + list($color, $format) = self::_normalize($color); + if (in_array($format, ["hex", "rgb"])) { + $format = "rgba"; + } elseif ($format = "hsl") { + $format = "hsla"; + } + $color[3] = floatval($opacity); + $color[3] = $color[3] < 0 ? 0 : ($color[3] > 1 ? 1 : $color[3]); + $func = "_array2" . $format; + return self::$func($color); + } + + /** + * Make the passed color brighter + * + * @author Till Glöggler + * + * @param string $color any type of css-valid color + * @param int $factor percentage of brightning, 100 for white and 0 for no changes to color + * @return string brightened color in same format as the passed one + */ + static function brighten($color, $factor = 35) { + // convert to color to rgba + list($color, $format) = self::_normalize($color); + if ($factor > 100) { + $factor = 100; + } elseif($factor < 0) { + $factor = 0; + } + + // return the color itself, if the conversion failed + if (!$format) return $color; + + // brighten the color + $color[0] = floor(($color[0] * (100 - $factor) + 255 * $factor) / 100); + $color[1] = floor(($color[1] * (100 - $factor) + 255 * $factor) / 100); + $color[2] = floor(($color[2] * (100 - $factor) + 255 * $factor) / 100); + + // convert the color back (if possible) + $func = "_array2" . $format; + return self::$func($color); + } + + /** + * converts any css-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + private static function _normalize($color) { + if ($color[0] === "#") { + $format = "hex"; + $arr = self::hex2array($color); + } elseif (preg_match("/\(.*\)/", $color)) { + $format = mb_substr($color, 0, mb_strpos($color, "(")); + $func = $format."2array"; + $arr = self::$func($color); + } elseif (self::$colorstrings[mb_strtolower($color)]) { + $format = "rgb"; //we don't want colors as strings like "red" + $arr = self::$colorstrings[mb_strtolower($color)]; + } + return [$arr, $format]; + } + + /** + * converts a css-rgba-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function rgba($color) { + list($arr, $format) = self::_normalize($color); + return self::_array2rgba($arr); + } + + /** + * converts a css-rgb-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function rgb($color) { + list($arr, $format) = self::_normalize($color); + return self::_array2rgb($arr); + } + + /** + * converts a css-hex-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function hex($color) { + list($arr, $format) = self::_normalize($color); + return self::_array2hex($arr); + } + + /** + * converts a css-hsl-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function hsl($color) { + list($arr, $format) = self::_normalize($color); + return self::_array2hsl($arr); + } + + /** + * converts a css-hsla-color into a rgba-quadruple + * + * @param string $color the color to be converted + * @return array colors as rgba-quadruple + */ + static function hsla($color) { + list($arr, $format) = self::_normalize($color); + return self::_array2hsla($arr); + } + + + /** + * converts a rgba-quadruple into a css-hex-color + * + * @param array $arr the rgba-quadruple to be converted + * @return array colors as css-hex + */ + static private function _array2hex($arr) { + return "#" . + ($arr[0] < 16 ? "0" : "").dechex($arr[0]) . + ($arr[1] < 16 ? "0" : "").dechex($arr[1]) . + ($arr[2] < 16 ? "0" : "").dechex($arr[2]); + } + + /** + * converts a rgba-quadruple into a css-rgb-color + * + * @param array $arr the rgba-quadruple to be converted + * @return array colors as css-rgb + */ + static private function _array2rgb($arr) { + array_pop($arr); + return "rgb(".implode(", ", $arr).")"; + } + + /** + * converts a rgba-quadruple into a css-rgba-color + * + * @param array $arr the rgba-quadruple to be converted + * @return array colors as css-rgba + */ + static private function _array2rgba($arr) { + return "rgba(".implode(", ", $arr).")"; + } + + /** + * converts a rgba-quadruple into a css-hsl-color + * + * @param array $arr the rgba-quadruple to be converted + * @return array colors as css-hsl + */ + static private function _array2hsl($arr) { + $arr = self::_calculate_hsl($arr); + return "hsl(".$arr[0].", ".$arr[1]."%, ".$arr[2]."%)"; + } + + /** + * converts a rgba-quadruple into a css-hsla-color + * + * @param array $arr the rgba-quadruple to be converted + * @return array colors as css-hsla + */ + static private function _array2hsla($arr) { + $hsl = self::_calculate_hsl($arr); + return "hsl(".$hsl[0].", ".$hsl[1]."%, ".$hsl[2]."%, ".$arr[3].")"; + } + + static private function _calculate_hsl($arr) { + $clrR = ($arr[0]); + $clrG = ($arr[1]); + $clrB = ($arr[2]); + + $clrMin = min($clrR, $clrG, $clrB); + $clrMax = max($clrR, $clrG, $clrB); + $deltaMax = $clrMax - $clrMin; + + $L = ($clrMax + $clrMin) / 510; + + if (0 == $deltaMax) { + $H = 0; + $S = 0; + } else { + if (0.5 > $L) { + $S = $deltaMax / ($clrMax + $clrMin); + } else { + $S = $deltaMax / (510 - $clrMax - $clrMin); + } + + if ($clrMax == $clrR) { + $H = ($clrG - $clrB) / (6.0 * $deltaMax); + } else if ($clrMax == $clrG) { + $H = 1/3 + ($clrB - $clrR) / (6.0 * $deltaMax); + } else { + $H = 2 / 3 + ($clrR - $clrG) / (6.0 * $deltaMax); + } + + if (0 > $H) $H += 1; + if (1 < $H) $H -= 1; + } + return [$H, $S, $L]; + } + +} \ No newline at end of file diff --git a/lib/classes/Config.class.php b/lib/classes/Config.class.php deleted file mode 100644 index 642665d..0000000 --- a/lib/classes/Config.class.php +++ /dev/null @@ -1,469 +0,0 @@ - - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -class Config implements ArrayAccess, Countable, IteratorAggregate -{ - private static $instance = null; - - /** - * contains all config entries as field => value pairs - * @var array - */ - protected $data = []; - /** - * contains additional metadata for config fields - * @var array - */ - protected $metadata = []; - - /** - * returns singleton instance - * @return Config - */ - public static function get() - { - if (self::$instance === null) { - $config = new Config(); - self::$instance = $config; - } - return self::$instance; - } - - /** - * alias of Config::get() for compatibility - * @return Config - */ - public static function getInstance() - { - return self::get(); - } - - /** - * use to set singleton instance for testing - * or to unset by passing null - * @param Config $my_instance - */ - public static function set() - { - $my_instance = func_get_arg(0); - self::$instance = $my_instance; - } - - /** - * pass array of config entries in field => value pairs - * to circumvent fetching from database - * @param array $data - */ - public function __construct($data = null) - { - $this->fetchData($data); - } - - /** - * returns a list of config entry names, filtered by - * given params - * @param string filter by range: global, range, user, course or institute - * @param string filter by section - * @param string filter by part of name - * @return array - */ - public function getFields($range = null, $section = null, $name = null) - { - if ($range && !in_array($range, words('global range user course institute'))) { - throw new Exception('Invalid range type'); - } - - $temp = $this->metadata; - - if ($range) { - $temp = array_filter($temp, function ($a) use ($range) { - return $a['range'] === $range - || ($a['range'] === 'range' && in_array($range, words('user course institute'))); - }); - } - if ($section) { - $temp = array_filter($temp, function ($a) use ($section) { - return $a['section'] === $section; - }); - } - if ($name) { - $temp = array_filter($temp, function ($a) use ($name) { - return mb_stripos($a['field'], $name) !== false; - }); - } - - return array_keys($temp); - } - - /** - * returns metadata for config entry - * @param string $field - * @return array - */ - public function getMetadata($field) - { - return $this->metadata[$field] ?? []; - } - - /** - * returns value of config entry - * for compatibility reasons an existing variable in global - * namespace with the same name is also returned - * @param string $field - * @return mixed - */ - public function getValue($field) - { - if ($this->fromEnv($field)) { - return $_ENV["STUDIP_CONFIG_{$field}"]; - } - - if (array_key_exists($field, $this->data)) { - return $this->data[$field]; - } - - if (isset($GLOBALS[$field]) && !isset($_REQUEST[$field])) { - return $GLOBALS[$field]; - } - - return null; - } - - /** - * set config entry to given value, but don't store it - * in database - * @param string $field - * @param mixed $value - * @return - */ - public function setValue($field, $value) - { - if (array_key_exists($field, $this->data)) { - return $this->data[$field] = $value; - } - } - - /** - * IteratorAggregate - */ - public function getIterator(): Traversable - { - return new ArrayIterator($this->data); - } - - /** - * magic method for dynamic properties - */ - public function __get($field) - { - return $this->getValue($field); - } - - /** - * magic method for dynamic properties - */ - public function __set($field, $value) - { - return $this->setValue($field, $value); - } - - /** - * magic method for dynamic properties - */ - public function __isset($field) - { - return isset($this->data[$field]); - } - - /** - * ArrayAccess: Check whether the given offset exists. - */ - public function offsetExists($offset): bool - { - return isset($this->$offset); - } - - /** - * ArrayAccess: Get the value at the given offset. - */ - public function offsetGet($offset): mixed - { - return $this->$offset; - } - - /** - * ArrayAccess: Set the value at the given offset. - */ - public function offsetSet($offset, $value): void - { - $this->$offset = $value; - } - - /** - * ArrayAccess: unset the value at the given offset (not applicable) - */ - public function offsetUnset($offset): void - { - - } - - /** - * Countable - */ - public function count(): int - { - return count($this->data); - } - - /** - * fetch config data from table config - * pass array to override database access - * @param array $data - */ - protected function fetchData($data = null) - { - if ($data !== null) { - $this->data = $data; - } else { - $this->data = []; - $db = DBManager::get(); - - try { - $query = "SELECT config.field, IFNULL(config_values.value, config.value) AS value, type, section, `range`, description, - config_values.comment, config_values.value = config.value AS is_default - FROM config - LEFT JOIN config_values ON config.field = config_values.field AND range_id = 'studip' - ORDER BY section, config.field"; - $rs = $db->query($query); - } catch (Exception $e) { - //if migration is smaller than 226 and Stud.IP needs to be migrated to version 4.1 or greater: - $query = "SELECT field, value, type, section, `range`, description, comment, is_default - FROM `config` - ORDER BY is_default DESC, section, field"; - $rs = $db->query($query); - } - - while ($row = $rs->fetch(PDO::FETCH_ASSOC)) { - // set the the type of the default entry for the modified entry - if (!empty($this->metadata[$row['field']])) { - $row['type'] = $this->metadata[$row['field']]['type']; - } - - $this->data[$row['field']] = $this->convertFromDatabase( - $row['type'], - $row['value'], - $row['field'] - ); - - $this->metadata[$row['field']] = array_intersect_key($row, array_flip(words('type section range description is_default comment'))); - $this->metadata[$row['field']]['field'] = $row['field']; - $this->metadata[$row['field']]['type'] = $row['type'] ?: 'string'; - } - } - } - - /** - * store new value for existing config entry in database - * posts notification ConfigValueChanged if entry is changed - * @param string $field - * @param string $data - * @throws InvalidArgumentException - * @return boolean - */ - public function store($field, $data) - { - if (!is_array($data) || !isset($data['value'])) { - $values['value'] = $data; - } else { - $values = $data; - } - - $values['value'] = $this->convertForDatabase( - $this->metadata[$field]['type'], - $values['value'], - $field - ); - - $entry = ConfigEntry::find($field); - if (!isset($entry)) { - throw new InvalidArgumentException($field . " not found in config table"); - } - $ret = 0; - if (isset($values['value'])) { - $value_entry = new ConfigValue([$field, 'studip']); - $old_value = $value_entry->isNew() ? $entry->value : $value_entry->value; - $value_entry->value = $values['value']; - if (isset($values['comment'])) { - $value_entry->comment = $values['comment']; - } - if ($entry->isDefault($value_entry)) { - $ret += $value_entry->delete(); - } else { - $ret += $value_entry->store(); - } - } - - if (isset($values['section'])) { - $entry->section = $values['section']; - $ret += $entry->store(); - } - - if ($ret) { - $this->fetchData(); - if (isset($value_entry)) { - NotificationCenter::postNotification('ConfigValueDidChange', $this, [ - 'field' => $field, - 'old_value' => $old_value, - 'new_value' => $value_entry->value, - ]); - } - } - return $ret > 0; - } - - /** - * creates a new config entry in database - * @param string name of entry - * @param array data to insert as assoc array - * @throws InvalidArgumentException - * @return null|ConfigEntry - */ - public function create($field, $data = []) - { - if (!$field) { - throw new InvalidArgumentException("config fieldname is mandatory"); - } - $entry = new ConfigEntry($field); - if (!$entry->isNew()) { - throw new InvalidArgumentException("config $field already exists"); - } - $entry->setData($data); - $ret = $entry->store() ? $entry : null; - if ($ret) { - $this->fetchData(); - } - return $ret; - } - - /** - * delete config entry from database - * @param string name of entry - * @throws InvalidArgumentException - * @return integer number of deleted rows - */ - public function delete($field) - { - if (!$field) { - throw new InvalidArgumentException("config fieldname is mandatory"); - } - ConfigValue::deleteBySql('field=?', [$field]); - $deleted = ConfigEntry::deleteBySql('field=?', [$field]); - if ($deleted) { - $this->fetchData(); - } - return $deleted; - } - - /** - * Returns the identifier for the i18n field. - * @param string $field - * @return string - */ - protected function getI18NIdentifier($field) - { - return md5($field); - } - - /** - * Transforms the data from the database for use. - * - * @param string $type - * @param mixed $value - * @param string $field - * @return mixed - */ - public function convertFromDatabase($type, $value, $field) - { - if ($type === 'integer') { - return (int) $value; - } - - if ($type === 'boolean') { - return (bool) $value; - } - - if ($type === 'array') { - return (array) json_decode($value, true); - } - - if ($type === 'i18n') { - return new I18NString($value, null, [ - 'object_id' => $this->getI18NIdentifier($field), - 'table' => 'config', - 'field' => 'value', - ]); - } - - return (string) $value; - } - - /** - * Transforms the given value to be stored in the database. - * - * @param string $type - * @param mixed $value - * @param string $field - * @return mixed - */ - public function convertForDatabase($type, $value, $field) - { - if ($type === 'boolean') { - return (bool) $value; - } - - if ($type === 'integer') { - return (int) $value; - } - - if ($type === 'array') { - return json_encode($value); - } - - if ($type === 'i18n') { - $value->setMetadata([ - 'object_id' => $this->getI18NIdentifier($field), - 'table' => 'config', - 'field' => 'value', - ]); - $value->storeTranslations(); - - return $value->original(); - } - - return (string) $value; - } - - /** - * Returns whether the value was set from the environment. - * - * @param string $field - * @return bool - */ - public function fromEnv(string $field): bool - { - return isset($_ENV["STUDIP_CONFIG_{$field}"]); - } -} diff --git a/lib/classes/Config.php b/lib/classes/Config.php new file mode 100644 index 0000000..642665d --- /dev/null +++ b/lib/classes/Config.php @@ -0,0 +1,469 @@ + + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class Config implements ArrayAccess, Countable, IteratorAggregate +{ + private static $instance = null; + + /** + * contains all config entries as field => value pairs + * @var array + */ + protected $data = []; + /** + * contains additional metadata for config fields + * @var array + */ + protected $metadata = []; + + /** + * returns singleton instance + * @return Config + */ + public static function get() + { + if (self::$instance === null) { + $config = new Config(); + self::$instance = $config; + } + return self::$instance; + } + + /** + * alias of Config::get() for compatibility + * @return Config + */ + public static function getInstance() + { + return self::get(); + } + + /** + * use to set singleton instance for testing + * or to unset by passing null + * @param Config $my_instance + */ + public static function set() + { + $my_instance = func_get_arg(0); + self::$instance = $my_instance; + } + + /** + * pass array of config entries in field => value pairs + * to circumvent fetching from database + * @param array $data + */ + public function __construct($data = null) + { + $this->fetchData($data); + } + + /** + * returns a list of config entry names, filtered by + * given params + * @param string filter by range: global, range, user, course or institute + * @param string filter by section + * @param string filter by part of name + * @return array + */ + public function getFields($range = null, $section = null, $name = null) + { + if ($range && !in_array($range, words('global range user course institute'))) { + throw new Exception('Invalid range type'); + } + + $temp = $this->metadata; + + if ($range) { + $temp = array_filter($temp, function ($a) use ($range) { + return $a['range'] === $range + || ($a['range'] === 'range' && in_array($range, words('user course institute'))); + }); + } + if ($section) { + $temp = array_filter($temp, function ($a) use ($section) { + return $a['section'] === $section; + }); + } + if ($name) { + $temp = array_filter($temp, function ($a) use ($name) { + return mb_stripos($a['field'], $name) !== false; + }); + } + + return array_keys($temp); + } + + /** + * returns metadata for config entry + * @param string $field + * @return array + */ + public function getMetadata($field) + { + return $this->metadata[$field] ?? []; + } + + /** + * returns value of config entry + * for compatibility reasons an existing variable in global + * namespace with the same name is also returned + * @param string $field + * @return mixed + */ + public function getValue($field) + { + if ($this->fromEnv($field)) { + return $_ENV["STUDIP_CONFIG_{$field}"]; + } + + if (array_key_exists($field, $this->data)) { + return $this->data[$field]; + } + + if (isset($GLOBALS[$field]) && !isset($_REQUEST[$field])) { + return $GLOBALS[$field]; + } + + return null; + } + + /** + * set config entry to given value, but don't store it + * in database + * @param string $field + * @param mixed $value + * @return + */ + public function setValue($field, $value) + { + if (array_key_exists($field, $this->data)) { + return $this->data[$field] = $value; + } + } + + /** + * IteratorAggregate + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->data); + } + + /** + * magic method for dynamic properties + */ + public function __get($field) + { + return $this->getValue($field); + } + + /** + * magic method for dynamic properties + */ + public function __set($field, $value) + { + return $this->setValue($field, $value); + } + + /** + * magic method for dynamic properties + */ + public function __isset($field) + { + return isset($this->data[$field]); + } + + /** + * ArrayAccess: Check whether the given offset exists. + */ + public function offsetExists($offset): bool + { + return isset($this->$offset); + } + + /** + * ArrayAccess: Get the value at the given offset. + */ + public function offsetGet($offset): mixed + { + return $this->$offset; + } + + /** + * ArrayAccess: Set the value at the given offset. + */ + public function offsetSet($offset, $value): void + { + $this->$offset = $value; + } + + /** + * ArrayAccess: unset the value at the given offset (not applicable) + */ + public function offsetUnset($offset): void + { + + } + + /** + * Countable + */ + public function count(): int + { + return count($this->data); + } + + /** + * fetch config data from table config + * pass array to override database access + * @param array $data + */ + protected function fetchData($data = null) + { + if ($data !== null) { + $this->data = $data; + } else { + $this->data = []; + $db = DBManager::get(); + + try { + $query = "SELECT config.field, IFNULL(config_values.value, config.value) AS value, type, section, `range`, description, + config_values.comment, config_values.value = config.value AS is_default + FROM config + LEFT JOIN config_values ON config.field = config_values.field AND range_id = 'studip' + ORDER BY section, config.field"; + $rs = $db->query($query); + } catch (Exception $e) { + //if migration is smaller than 226 and Stud.IP needs to be migrated to version 4.1 or greater: + $query = "SELECT field, value, type, section, `range`, description, comment, is_default + FROM `config` + ORDER BY is_default DESC, section, field"; + $rs = $db->query($query); + } + + while ($row = $rs->fetch(PDO::FETCH_ASSOC)) { + // set the the type of the default entry for the modified entry + if (!empty($this->metadata[$row['field']])) { + $row['type'] = $this->metadata[$row['field']]['type']; + } + + $this->data[$row['field']] = $this->convertFromDatabase( + $row['type'], + $row['value'], + $row['field'] + ); + + $this->metadata[$row['field']] = array_intersect_key($row, array_flip(words('type section range description is_default comment'))); + $this->metadata[$row['field']]['field'] = $row['field']; + $this->metadata[$row['field']]['type'] = $row['type'] ?: 'string'; + } + } + } + + /** + * store new value for existing config entry in database + * posts notification ConfigValueChanged if entry is changed + * @param string $field + * @param string $data + * @throws InvalidArgumentException + * @return boolean + */ + public function store($field, $data) + { + if (!is_array($data) || !isset($data['value'])) { + $values['value'] = $data; + } else { + $values = $data; + } + + $values['value'] = $this->convertForDatabase( + $this->metadata[$field]['type'], + $values['value'], + $field + ); + + $entry = ConfigEntry::find($field); + if (!isset($entry)) { + throw new InvalidArgumentException($field . " not found in config table"); + } + $ret = 0; + if (isset($values['value'])) { + $value_entry = new ConfigValue([$field, 'studip']); + $old_value = $value_entry->isNew() ? $entry->value : $value_entry->value; + $value_entry->value = $values['value']; + if (isset($values['comment'])) { + $value_entry->comment = $values['comment']; + } + if ($entry->isDefault($value_entry)) { + $ret += $value_entry->delete(); + } else { + $ret += $value_entry->store(); + } + } + + if (isset($values['section'])) { + $entry->section = $values['section']; + $ret += $entry->store(); + } + + if ($ret) { + $this->fetchData(); + if (isset($value_entry)) { + NotificationCenter::postNotification('ConfigValueDidChange', $this, [ + 'field' => $field, + 'old_value' => $old_value, + 'new_value' => $value_entry->value, + ]); + } + } + return $ret > 0; + } + + /** + * creates a new config entry in database + * @param string name of entry + * @param array data to insert as assoc array + * @throws InvalidArgumentException + * @return null|ConfigEntry + */ + public function create($field, $data = []) + { + if (!$field) { + throw new InvalidArgumentException("config fieldname is mandatory"); + } + $entry = new ConfigEntry($field); + if (!$entry->isNew()) { + throw new InvalidArgumentException("config $field already exists"); + } + $entry->setData($data); + $ret = $entry->store() ? $entry : null; + if ($ret) { + $this->fetchData(); + } + return $ret; + } + + /** + * delete config entry from database + * @param string name of entry + * @throws InvalidArgumentException + * @return integer number of deleted rows + */ + public function delete($field) + { + if (!$field) { + throw new InvalidArgumentException("config fieldname is mandatory"); + } + ConfigValue::deleteBySql('field=?', [$field]); + $deleted = ConfigEntry::deleteBySql('field=?', [$field]); + if ($deleted) { + $this->fetchData(); + } + return $deleted; + } + + /** + * Returns the identifier for the i18n field. + * @param string $field + * @return string + */ + protected function getI18NIdentifier($field) + { + return md5($field); + } + + /** + * Transforms the data from the database for use. + * + * @param string $type + * @param mixed $value + * @param string $field + * @return mixed + */ + public function convertFromDatabase($type, $value, $field) + { + if ($type === 'integer') { + return (int) $value; + } + + if ($type === 'boolean') { + return (bool) $value; + } + + if ($type === 'array') { + return (array) json_decode($value, true); + } + + if ($type === 'i18n') { + return new I18NString($value, null, [ + 'object_id' => $this->getI18NIdentifier($field), + 'table' => 'config', + 'field' => 'value', + ]); + } + + return (string) $value; + } + + /** + * Transforms the given value to be stored in the database. + * + * @param string $type + * @param mixed $value + * @param string $field + * @return mixed + */ + public function convertForDatabase($type, $value, $field) + { + if ($type === 'boolean') { + return (bool) $value; + } + + if ($type === 'integer') { + return (int) $value; + } + + if ($type === 'array') { + return json_encode($value); + } + + if ($type === 'i18n') { + $value->setMetadata([ + 'object_id' => $this->getI18NIdentifier($field), + 'table' => 'config', + 'field' => 'value', + ]); + $value->storeTranslations(); + + return $value->original(); + } + + return (string) $value; + } + + /** + * Returns whether the value was set from the environment. + * + * @param string $field + * @return bool + */ + public function fromEnv(string $field): bool + { + return isset($_ENV["STUDIP_CONFIG_{$field}"]); + } +} diff --git a/lib/classes/CourseAvatar.class.php b/lib/classes/CourseAvatar.class.php deleted file mode 100644 index 8c153a8..0000000 --- a/lib/classes/CourseAvatar.class.php +++ /dev/null @@ -1,44 +0,0 @@ -user_id}"; - } - - /** - * Return the default title of the avatar. - * @return string the default title - */ - public function getDefaultTitle() - { - return Seminar::GetInstance($this->user_id)->name; - } - - /** - * Return if avatar is visible to the current user. - * @return boolean: true if visible - */ - protected function checkAvatarVisibility() - { - //no special conditions for visibility of course-avatars yet - return true; - } -} diff --git a/lib/classes/CourseAvatar.php b/lib/classes/CourseAvatar.php new file mode 100644 index 0000000..8c153a8 --- /dev/null +++ b/lib/classes/CourseAvatar.php @@ -0,0 +1,44 @@ +user_id}"; + } + + /** + * Return the default title of the avatar. + * @return string the default title + */ + public function getDefaultTitle() + { + return Seminar::GetInstance($this->user_id)->name; + } + + /** + * Return if avatar is visible to the current user. + * @return boolean: true if visible + */ + protected function checkAvatarVisibility() + { + //no special conditions for visibility of course-avatars yet + return true; + } +} diff --git a/lib/classes/CourseConfig.class.php b/lib/classes/CourseConfig.class.php deleted file mode 100644 index ad62be0..0000000 --- a/lib/classes/CourseConfig.class.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -class CourseConfig extends RangeConfig -{ - /** - * range type - */ - const RANGE_TYPE = 'course'; -} diff --git a/lib/classes/CourseConfig.php b/lib/classes/CourseConfig.php new file mode 100644 index 0000000..ad62be0 --- /dev/null +++ b/lib/classes/CourseConfig.php @@ -0,0 +1,23 @@ + + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class CourseConfig extends RangeConfig +{ + /** + * range type + */ + const RANGE_TYPE = 'course'; +} diff --git a/lib/classes/CronJob.class.php b/lib/classes/CronJob.class.php deleted file mode 100644 index 1e7fb41..0000000 --- a/lib/classes/CronJob.class.php +++ /dev/null @@ -1,140 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.4 - */ - -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// Cronjob.class.php -// -// Copyright (C) 2013 Jan-Hendrik Willms -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -abstract class CronJob -{ - /** - * Return the name of the cronjob. - */ - abstract public static function getName(); - - /** - * Return the description of the cronjob. - */ - abstract public static function getDescription(); - - /** - * Execute the cronjob. - * - * @param mixed $last_result What the last execution of this cronjob - * returned. - * @param Array $parameters Parameters for this cronjob instance which - * were defined during scheduling. - */ - abstract public function execute($last_result, $parameters = []); - - /** - * Returns a list of available parameters for this cronjob. - * - * Each parameter is an entry in the resulting with a unique identifier - * with the following array fields: - * - * - "type" which is one of the following: - * - boolean, a simple binary option - * - string, a single line of text - * - text, a multiline chunk of text - * - integer, a number - * - select, a defined set of values (define in the field "values" as - * an array) - * - "default" provides a default value for this field (optional) - * - "status" is either "optional" or "mandatory" (optional, defaults to - * optional) - * - "description" provides a decription for this parameter - * - * Example: - * - * - * return array( - * 'area' => array( - * 'type' => 'select', - * 'values' => array('seminar', 'institute', 'user'), - * 'description' => 'Example parameter #1', - * ), - * 'verbose' => array( - * 'type' => 'boolean', - * 'default' => false, - * 'status' => 'optional', - * 'description' => 'Example parameter #2', - * ), - * ); - * - * - * @param Array List of paramters in the format described above. - */ - public static function getParameters() - { - return []; - } - - /** - * Setup method. - */ - public function setUp() - { - } - - /** - * Teardown method. - */ - public function tearDown() - { - } - -// Convenience methods to ease the usage - - /** - * Registers the cronjob and/or returns the corresponding task. - * - * @return CronjobTask Task for this cronjob - */ - public static function register() - { - $class_name = get_called_class(); - $reflection = new ReflectionClass($class_name); - - $task_id = CronjobScheduler::getInstance()->registerTask($reflection->newInstance()); - - return CronjobTask::find($task_id); - } - - /** - * Unregisters a previously registered task. - */ - public static function unregister() - { - $class_name = get_called_class(); - $task = CronjobTask::findOneByClass($class_name); - - if ($task !== null) { - CronjobScheduler::getInstance()->unregisterTask($task->id); - } - } - -} diff --git a/lib/classes/CronJob.php b/lib/classes/CronJob.php new file mode 100644 index 0000000..1e7fb41 --- /dev/null +++ b/lib/classes/CronJob.php @@ -0,0 +1,140 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.4 + */ + +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// Cronjob.class.php +// +// Copyright (C) 2013 Jan-Hendrik Willms +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +abstract class CronJob +{ + /** + * Return the name of the cronjob. + */ + abstract public static function getName(); + + /** + * Return the description of the cronjob. + */ + abstract public static function getDescription(); + + /** + * Execute the cronjob. + * + * @param mixed $last_result What the last execution of this cronjob + * returned. + * @param Array $parameters Parameters for this cronjob instance which + * were defined during scheduling. + */ + abstract public function execute($last_result, $parameters = []); + + /** + * Returns a list of available parameters for this cronjob. + * + * Each parameter is an entry in the resulting with a unique identifier + * with the following array fields: + * + * - "type" which is one of the following: + * - boolean, a simple binary option + * - string, a single line of text + * - text, a multiline chunk of text + * - integer, a number + * - select, a defined set of values (define in the field "values" as + * an array) + * - "default" provides a default value for this field (optional) + * - "status" is either "optional" or "mandatory" (optional, defaults to + * optional) + * - "description" provides a decription for this parameter + * + * Example: + * + * + * return array( + * 'area' => array( + * 'type' => 'select', + * 'values' => array('seminar', 'institute', 'user'), + * 'description' => 'Example parameter #1', + * ), + * 'verbose' => array( + * 'type' => 'boolean', + * 'default' => false, + * 'status' => 'optional', + * 'description' => 'Example parameter #2', + * ), + * ); + * + * + * @param Array List of paramters in the format described above. + */ + public static function getParameters() + { + return []; + } + + /** + * Setup method. + */ + public function setUp() + { + } + + /** + * Teardown method. + */ + public function tearDown() + { + } + +// Convenience methods to ease the usage + + /** + * Registers the cronjob and/or returns the corresponding task. + * + * @return CronjobTask Task for this cronjob + */ + public static function register() + { + $class_name = get_called_class(); + $reflection = new ReflectionClass($class_name); + + $task_id = CronjobScheduler::getInstance()->registerTask($reflection->newInstance()); + + return CronjobTask::find($task_id); + } + + /** + * Unregisters a previously registered task. + */ + public static function unregister() + { + $class_name = get_called_class(); + $task = CronjobTask::findOneByClass($class_name); + + if ($task !== null) { + CronjobScheduler::getInstance()->unregisterTask($task->id); + } + } + +} diff --git a/lib/classes/CronjobScheduler.class.php b/lib/classes/CronjobScheduler.class.php deleted file mode 100644 index cc4cf8b..0000000 --- a/lib/classes/CronjobScheduler.class.php +++ /dev/null @@ -1,333 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.4 - */ - -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// CronjobScheduler.class.php -// -// Copyright (C) 2013 Jan-Hendrik Willms -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -class CronjobScheduler -{ - protected static $instance = null; - - /** - * Returns the scheduler object. Implements the singleton pattern to - * ensure that only one scheduler exists. - * - * @return CronjobScheduler The scheduler object - */ - public static function getInstance() - { - if (self::$instance === null) { - self::$instance = new self(); - } - return self::$instance; - } - - /** - * Private constructor to ensure the singleton pattern is used correctly. - */ - private function __construct() - { - } - - /** - * Registers a new executable task. - * - * @param mixed $task Either path of the task class filename (relative - * to Stud.IP root) or an instance of CronJob - * @param bool $active Indicates whether the task should be set active - * or not - * @return String Id of the created task - * @throws InvalidArgumentException when the task class file does not - * exist - * @throws RuntimeException when task has already been registered - */ - public function registerTask($task, $active = true) - { - if (is_object($task)) { - $reflection = new ReflectionClass($task); - $class = $reflection->getName(); - $class_filename = studip_relative_path($reflection->getFileName()); - } else { - $filename = $GLOBALS['STUDIP_BASE_PATH'] . '/' . $task; - if (!file_exists($filename)) { - $message = sprintf('Task class file "%s" does not exist.', $task); - throw new InvalidArgumentException($message); - } - $class_filename = $task; - - $classes = get_declared_classes(); - require_once $filename; - $new_classes = array_diff(get_declared_classes(), $classes); - $new_classes = array_filter($new_classes, function ($class) { - return is_subclass_of($class, 'CronJob', true); - }); - $class = end($new_classes); - - if (empty($class)) { - throw new RuntimeException('No valid class was defined in file.'); - } - - $reflection = new ReflectionClass($class); - } - - if (!$reflection->isSubclassOf('CronJob')) { - $message = sprintf('Job class "%s" (defined in %s) does not extend the abstract CronJob class.', $class, $filename); - throw new RuntimeException($message); - } - - if ($task = CronjobTask::findOneByClass($class)) { - return $task->task_id; - } - - $task = new CronjobTask(); - $task->filename = $class_filename; - $task->class = $class; - $task->active = (int)$active; - $task->store(); - - return $task->task_id; - } - - /** - * Unregisters a previously registered task. - * - * @param String $task_id Id of the task to be unregistered - * @return CronjobScheduler to allow chaining - * @throws InvalidArgumentException when no task with the given id exists - */ - public function unregisterTask($task_id) - { - $task = CronjobTask::find($task_id); - if ($task === null) { - $message = sprintf('A task with the id "%s" does not exist.', $task_id); - throw new InvalidArgumentException($message); - } - $task->delete(); - - return $this; - } - - /** - * Schedules a task for periodic execution with the provided schedule. - * - * @param String $task_id The id of the task to be executed - * @param mixed $minute Minute part of the schedule: - * - null for "every minute" a.k.a. "don't care" - * - x < 0 for "every x minutes" - * - x >= 0 for "only at minute x" - * @param mixed $hour Hour part of the schedule: - * - null for "every hour" a.k.a. "don't care" - * - x < 0 for "every x hours" - * - x >= 0 for "only at hour x" - * @param mixed $day Day part of the schedule: - * - null for "every day" a.k.a. "don't care" - * - x < 0 for "every x days" - * - x > 0 for "only at day x" - * @param mixed $month Month part of the schedule: - * - null for "every month" a.k.a. "don't care" - * - x < 0 for "every x months" - * - x > 0 for "only at month x" - * @param mixed $day_of_week Day of week part of the schedule: - * - null for "every day" a.k.a. "don't care" - * - 1 >= x >= 7 for "exactly at day of week x" - * (x starts with monday at 1 and ends with - * sunday at 7) - * @param Array $parameters Optional parameters passed to the task - * @return CronjobSchedule The generated schedule object. - */ - public function schedule( - string $task_id, - ?int $minute = null, - ?int $hour = null, - ?int $day = null, - ?int $month = null, - ?int $day_of_week = null, - array $parameters = [] - ): CronjobSchedule { - $schedule = new CronjobSchedule(); - $schedule->task_id = $task_id; - $schedule->parameters = $parameters; - - $schedule->minute = $minute; - $schedule->hour = $hour; - $schedule->day = $day; - $schedule->month = $month; - $schedule->day_of_week = $day_of_week; - - $schedule->store(); - - $task = $schedule->task; - $task->assigned_count += 1; - $task->store(); - - return $schedule; - } - - /** - * An alias for schedule for backwards compatibility. - * - * @see CronjobScheduler::schedule() - */ - public function schedulePeriodic( - $task_id, - $minute = null, - $hour = null, - $day = null, - $month = null, - $day_of_week = null, - $priority = null, - $parameters = [] - ) { - return $this->schedule($task_id, $minute, $hour, $day, $month, $day_of_week, $parameters); - } - - /** - * Cancels the provided schedule. - * - * @param String $schedule_id Id of the schedule to be canceled - */ - public function cancel($schedule_id) - { - CronjobSchedule::find($schedule_id)->delete(); - } - - /** - * Cancels all schedules of the provided task. - * - * @param String $task_id Id of the task which schedules shall be canceled - */ - public function cancelByTask($task_id) - { - $schedules = CronjobSchedule::findByTask_id($task_id); - foreach ($schedules as $schedule) { - $schedule->delete(); - } - } - - /** - * Executes the available schedules if they are to be executed. - * This method can only be run once - even if one execution takes more - * than planned. This is ensured by a locking mechanism. - */ - public function run() - { - if (!Config::get()->CRONJOBS_ENABLE) { - return; - } - - $lock = new FileLock('studip-cronjob'); - - // Check whether a previous cronjob worker is still running. - if (!$lock->tryLock()) { - return; - } - - // Find all schedules that are due to execute and which task is active - $temp = CronjobSchedule::findBySQL('`active` = 1 AND `next_execution` <= UNIX_TIMESTAMP() ' - .'ORDER BY `next_execution`'); - $schedules = array_filter($temp, function ($schedule) { return $schedule->task->active; }); - - if (count($schedules) === 0) { - return; - } - - foreach ($schedules as $schedule) { - $log = new CronjobLog(); - $log->schedule_id = $schedule->schedule_id; - $log->scheduled = $schedule->next_execution; - $log->executed = time(); - $log->exception = null; - $log->duration = -1; - - try { - // Skip schedules with missing task classes - if (!$schedule->task->valid) { - throw new Exception(_('Die Klasse für den Cronjob-Task konnte nicht gefunden werden')); - } - - // Start capturing output and measuring duration - ob_start(); - $start_time = microtime(true); - - $schedule->execute(); - - // Actually capture output and duration - $end_time = microtime(true); - $output = ob_get_clean(); - - // Complete log - $log->output = $output; - $log->duration = $end_time - $start_time; - $log->store(); - } catch (Exception $e) { - $log->exception = $e; - $log->store(); - - // Deactivate schedule - $schedule->deactivate(); - - // Send mail to root accounts - $subject = sprintf('[Cronjobs] %s: %s', - _('Fehlerhafte Ausführung'), - $schedule->title); - - $message = sprintf(_('Der Cronjob "%s" wurde deaktiviert, da bei der Ausführung ein Fehler aufgetreten ist.'), $schedule->title) . "\n"; - $message .= "\n"; - $message .= display_exception($e) . "\n"; - - $message .= _('Für weiterführende Informationen klicken Sie bitten den folgenden Link:') . "\n"; - - $old = URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']); - $message .= URLHelper::getURL('dispatch.php/admin/cronjobs/logs/schedule/' . $schedule->schedule_id); - URLHelper::setBaseURL($old); - - $this->sendMailToRoots($subject, $message); - } - } - - // Release lock - $lock->release(); - } - - /** - * Sends an internal mail with the provided subject and message to all - * users with a global permission of "root". - * - * @param String $subject The subject of the message - * @param String $message The message itself - */ - private function sendMailToRoots($subject, $message) - { - $temp = User::findByPerms('root'); - $roots = SimpleORMapCollection::createFromArray($temp) - ->filter(function($r) { return $r->locked == 0; }) - ->pluck('username'); - - $msging = new messaging; - $msging->insert_message($message, $roots, '____%system%____', null, null, null, null, $subject, false, 'high'); - } -} diff --git a/lib/classes/CronjobScheduler.php b/lib/classes/CronjobScheduler.php new file mode 100644 index 0000000..cc4cf8b --- /dev/null +++ b/lib/classes/CronjobScheduler.php @@ -0,0 +1,333 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.4 + */ + +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// CronjobScheduler.class.php +// +// Copyright (C) 2013 Jan-Hendrik Willms +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +class CronjobScheduler +{ + protected static $instance = null; + + /** + * Returns the scheduler object. Implements the singleton pattern to + * ensure that only one scheduler exists. + * + * @return CronjobScheduler The scheduler object + */ + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Private constructor to ensure the singleton pattern is used correctly. + */ + private function __construct() + { + } + + /** + * Registers a new executable task. + * + * @param mixed $task Either path of the task class filename (relative + * to Stud.IP root) or an instance of CronJob + * @param bool $active Indicates whether the task should be set active + * or not + * @return String Id of the created task + * @throws InvalidArgumentException when the task class file does not + * exist + * @throws RuntimeException when task has already been registered + */ + public function registerTask($task, $active = true) + { + if (is_object($task)) { + $reflection = new ReflectionClass($task); + $class = $reflection->getName(); + $class_filename = studip_relative_path($reflection->getFileName()); + } else { + $filename = $GLOBALS['STUDIP_BASE_PATH'] . '/' . $task; + if (!file_exists($filename)) { + $message = sprintf('Task class file "%s" does not exist.', $task); + throw new InvalidArgumentException($message); + } + $class_filename = $task; + + $classes = get_declared_classes(); + require_once $filename; + $new_classes = array_diff(get_declared_classes(), $classes); + $new_classes = array_filter($new_classes, function ($class) { + return is_subclass_of($class, 'CronJob', true); + }); + $class = end($new_classes); + + if (empty($class)) { + throw new RuntimeException('No valid class was defined in file.'); + } + + $reflection = new ReflectionClass($class); + } + + if (!$reflection->isSubclassOf('CronJob')) { + $message = sprintf('Job class "%s" (defined in %s) does not extend the abstract CronJob class.', $class, $filename); + throw new RuntimeException($message); + } + + if ($task = CronjobTask::findOneByClass($class)) { + return $task->task_id; + } + + $task = new CronjobTask(); + $task->filename = $class_filename; + $task->class = $class; + $task->active = (int)$active; + $task->store(); + + return $task->task_id; + } + + /** + * Unregisters a previously registered task. + * + * @param String $task_id Id of the task to be unregistered + * @return CronjobScheduler to allow chaining + * @throws InvalidArgumentException when no task with the given id exists + */ + public function unregisterTask($task_id) + { + $task = CronjobTask::find($task_id); + if ($task === null) { + $message = sprintf('A task with the id "%s" does not exist.', $task_id); + throw new InvalidArgumentException($message); + } + $task->delete(); + + return $this; + } + + /** + * Schedules a task for periodic execution with the provided schedule. + * + * @param String $task_id The id of the task to be executed + * @param mixed $minute Minute part of the schedule: + * - null for "every minute" a.k.a. "don't care" + * - x < 0 for "every x minutes" + * - x >= 0 for "only at minute x" + * @param mixed $hour Hour part of the schedule: + * - null for "every hour" a.k.a. "don't care" + * - x < 0 for "every x hours" + * - x >= 0 for "only at hour x" + * @param mixed $day Day part of the schedule: + * - null for "every day" a.k.a. "don't care" + * - x < 0 for "every x days" + * - x > 0 for "only at day x" + * @param mixed $month Month part of the schedule: + * - null for "every month" a.k.a. "don't care" + * - x < 0 for "every x months" + * - x > 0 for "only at month x" + * @param mixed $day_of_week Day of week part of the schedule: + * - null for "every day" a.k.a. "don't care" + * - 1 >= x >= 7 for "exactly at day of week x" + * (x starts with monday at 1 and ends with + * sunday at 7) + * @param Array $parameters Optional parameters passed to the task + * @return CronjobSchedule The generated schedule object. + */ + public function schedule( + string $task_id, + ?int $minute = null, + ?int $hour = null, + ?int $day = null, + ?int $month = null, + ?int $day_of_week = null, + array $parameters = [] + ): CronjobSchedule { + $schedule = new CronjobSchedule(); + $schedule->task_id = $task_id; + $schedule->parameters = $parameters; + + $schedule->minute = $minute; + $schedule->hour = $hour; + $schedule->day = $day; + $schedule->month = $month; + $schedule->day_of_week = $day_of_week; + + $schedule->store(); + + $task = $schedule->task; + $task->assigned_count += 1; + $task->store(); + + return $schedule; + } + + /** + * An alias for schedule for backwards compatibility. + * + * @see CronjobScheduler::schedule() + */ + public function schedulePeriodic( + $task_id, + $minute = null, + $hour = null, + $day = null, + $month = null, + $day_of_week = null, + $priority = null, + $parameters = [] + ) { + return $this->schedule($task_id, $minute, $hour, $day, $month, $day_of_week, $parameters); + } + + /** + * Cancels the provided schedule. + * + * @param String $schedule_id Id of the schedule to be canceled + */ + public function cancel($schedule_id) + { + CronjobSchedule::find($schedule_id)->delete(); + } + + /** + * Cancels all schedules of the provided task. + * + * @param String $task_id Id of the task which schedules shall be canceled + */ + public function cancelByTask($task_id) + { + $schedules = CronjobSchedule::findByTask_id($task_id); + foreach ($schedules as $schedule) { + $schedule->delete(); + } + } + + /** + * Executes the available schedules if they are to be executed. + * This method can only be run once - even if one execution takes more + * than planned. This is ensured by a locking mechanism. + */ + public function run() + { + if (!Config::get()->CRONJOBS_ENABLE) { + return; + } + + $lock = new FileLock('studip-cronjob'); + + // Check whether a previous cronjob worker is still running. + if (!$lock->tryLock()) { + return; + } + + // Find all schedules that are due to execute and which task is active + $temp = CronjobSchedule::findBySQL('`active` = 1 AND `next_execution` <= UNIX_TIMESTAMP() ' + .'ORDER BY `next_execution`'); + $schedules = array_filter($temp, function ($schedule) { return $schedule->task->active; }); + + if (count($schedules) === 0) { + return; + } + + foreach ($schedules as $schedule) { + $log = new CronjobLog(); + $log->schedule_id = $schedule->schedule_id; + $log->scheduled = $schedule->next_execution; + $log->executed = time(); + $log->exception = null; + $log->duration = -1; + + try { + // Skip schedules with missing task classes + if (!$schedule->task->valid) { + throw new Exception(_('Die Klasse für den Cronjob-Task konnte nicht gefunden werden')); + } + + // Start capturing output and measuring duration + ob_start(); + $start_time = microtime(true); + + $schedule->execute(); + + // Actually capture output and duration + $end_time = microtime(true); + $output = ob_get_clean(); + + // Complete log + $log->output = $output; + $log->duration = $end_time - $start_time; + $log->store(); + } catch (Exception $e) { + $log->exception = $e; + $log->store(); + + // Deactivate schedule + $schedule->deactivate(); + + // Send mail to root accounts + $subject = sprintf('[Cronjobs] %s: %s', + _('Fehlerhafte Ausführung'), + $schedule->title); + + $message = sprintf(_('Der Cronjob "%s" wurde deaktiviert, da bei der Ausführung ein Fehler aufgetreten ist.'), $schedule->title) . "\n"; + $message .= "\n"; + $message .= display_exception($e) . "\n"; + + $message .= _('Für weiterführende Informationen klicken Sie bitten den folgenden Link:') . "\n"; + + $old = URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']); + $message .= URLHelper::getURL('dispatch.php/admin/cronjobs/logs/schedule/' . $schedule->schedule_id); + URLHelper::setBaseURL($old); + + $this->sendMailToRoots($subject, $message); + } + } + + // Release lock + $lock->release(); + } + + /** + * Sends an internal mail with the provided subject and message to all + * users with a global permission of "root". + * + * @param String $subject The subject of the message + * @param String $message The message itself + */ + private function sendMailToRoots($subject, $message) + { + $temp = User::findByPerms('root'); + $roots = SimpleORMapCollection::createFromArray($temp) + ->filter(function($r) { return $r->locked == 0; }) + ->pluck('username'); + + $msging = new messaging; + $msging->insert_message($message, $roots, '____%system%____', null, null, null, null, $subject, false, 'high'); + } +} diff --git a/lib/classes/DBManager.class.php b/lib/classes/DBManager.class.php deleted file mode 100644 index 7854cff..0000000 --- a/lib/classes/DBManager.class.php +++ /dev/null @@ -1,250 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -/** - * This class provides a singleton instance that is used to manage PDO database - * connections. - * - * Example of use: - * @code - * # get hold of the DBManager's singleton - * $manager = DBManager::getInstance(); - * - * # set PDO connections using a DSN - * $manager->setConnection('example', - * 'mysql:host=localhost;dbname=example', - * 'root', ''); - * # or an existing instance of PDO - * $manager->setConnection('example2', $existingPdo); - * - * # retrieve a PDO connection later in your code - * $db = $manager->getConnection("studip"); - * - * # or as a shortcut - * $db = DBManager::get("studip"); - * - * # or even shorter ("studip" is the default key) - * $db = DBManager::get(); - * - * # and use the connection - * $db->query('SELECT * FROM user_info'); - * - * # you may even alias connections - * $manager->aliasConnection("studip", "studip-slave"); - * - * # but this is just sugar for - * $studip = $manager->getConnection("studip"); - * $manager->setConnection("studip-slave", $studip); - * - * @endcode - */ -class DBManager -{ - - - /** - * the singleton instance - * - * @access private - * @var DBManager - */ - static private $instance; - - - /** - * an array of connections of the singleton instance - * - * @access private - * @var array - */ - private $connections; - - - /** - * @access private - * - * @return void - */ - private function __construct() - { - $this->connections = []; - } - - - /** - * This method returns the singleton instance of this class. - * - * @return DBManager the singleton instance - */ - static public function getInstance() - { - if (is_null(DBManager::$instance)) { - DBManager::$instance = new DBManager(); - } - return DBManager::$instance; - } - - - /** - * This method returns the database connection to the given key. Throws a - * DBManagerException if there is no such connection. - * - * Example usage: - * @code - * try { - * $db = DBManager::getInstance()->getConnection("foo"); - * $db->exec($sql); - * } catch (DBManagerException $e) { - * echo "oops"; - * } - * @endcode - * - * @param string the key - * - * @throw DBManagerException - * - * @return PDO the database connection - */ - public function getConnection($database) - { - - if (!isset($this->connections[$database])) { - throw new DBManagerException('Database connection: "'.$database. - '" does not exist.'); - } - - return $this->connections[$database]; - } - - - /** - * This method maps the specified key to the specified database connection. - * - * You can either use an instance of class PDO or specify a DSN (optionally - * with username/password). - * - * The (possibly newly created) connection is configured to throw exceptions - * and to buffer queries if it is a MySQL connection. - * - * Examples: - * @code - * $dbManager = DBManager::getInstance(); - * - * // using an existing PDO connection - * $existingPdo = new LoggingPDO($dsn); - * $dbManager->setConnection('studip', $pdo); - * - * // using a DSN with username and password - * $dbManager->setConnection('studip', $dsn , $username, $password); - * @endcode - * - * @param string the key - * @param string|PDO either a DSN or an existing PDO connection - * @param string (optional) the connection's username - * @param array (optional) the connection's password - * - * @return DBManager this instance, useful for cascading method calls - */ - public function setConnection($database, $dsnOrConnection, $user = NULL, - $pass = NULL) - { - $connection = $dsnOrConnection instanceof PDO - ? $dsnOrConnection - : new StudipPDO($dsnOrConnection, $user, $pass); - - $this->configureConnection($connection); - $this->connections[$database] = $connection; - - return $this; - } - - - // PDO connection should throw exceptions and use buffered queries - private function configureConnection($connection) - { - - $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - if ($connection->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') { - $connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); - // $connection->exec('SET CHARACTER SET latin1'); - } - } - - /** - * This method creates an alias for a database connection. - * - * This is useful if you want to use different keys but access the same - * database, e.g. if you want to use master-slave replication in the future - * - * @param string the old key of the database connection - * @param string the new key of the database connection - * - * @return DBManager this instance, useful for cascading method calls - */ - public function aliasConnection($old, $new) - { - - if (!isset($this->connections[$old])) { - throw new DBManagerException('No database found using key: ' . $old); - } - - $this->connections[$new] = $this->connections[$old]; - - return $this; - } - - - /** - * Shortcut static method to retrieve the database connection for a given - * key. - * - * Example usage: - * @code - * // instead of - * $db = DBManager::getInstance()->getConnection("studip"); - * - * // this can be shortened to - * $db = DBManager::get("studip"); - * - * // or in this case (as "studip" is the default key) - * $db = DBManager::get(); - * @endcode - * - * @param string the key - * - * @return StudipPDO the database connection - */ - static public function get($database = 'studip') - { - $manager = DBManager::getInstance(); - return $manager->getConnection($database); - } -} - - -/** - * The DBManager throws this exception if it cannot find a connection using - * a non existant key. - */ -class DBManagerException extends Exception -{ - - /** - * @param string the message of this exception - * - * @return void - */ - public function __construct($message) - { - parent::__construct($message); - } -} diff --git a/lib/classes/DBManager.php b/lib/classes/DBManager.php new file mode 100644 index 0000000..7854cff --- /dev/null +++ b/lib/classes/DBManager.php @@ -0,0 +1,250 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +/** + * This class provides a singleton instance that is used to manage PDO database + * connections. + * + * Example of use: + * @code + * # get hold of the DBManager's singleton + * $manager = DBManager::getInstance(); + * + * # set PDO connections using a DSN + * $manager->setConnection('example', + * 'mysql:host=localhost;dbname=example', + * 'root', ''); + * # or an existing instance of PDO + * $manager->setConnection('example2', $existingPdo); + * + * # retrieve a PDO connection later in your code + * $db = $manager->getConnection("studip"); + * + * # or as a shortcut + * $db = DBManager::get("studip"); + * + * # or even shorter ("studip" is the default key) + * $db = DBManager::get(); + * + * # and use the connection + * $db->query('SELECT * FROM user_info'); + * + * # you may even alias connections + * $manager->aliasConnection("studip", "studip-slave"); + * + * # but this is just sugar for + * $studip = $manager->getConnection("studip"); + * $manager->setConnection("studip-slave", $studip); + * + * @endcode + */ +class DBManager +{ + + + /** + * the singleton instance + * + * @access private + * @var DBManager + */ + static private $instance; + + + /** + * an array of connections of the singleton instance + * + * @access private + * @var array + */ + private $connections; + + + /** + * @access private + * + * @return void + */ + private function __construct() + { + $this->connections = []; + } + + + /** + * This method returns the singleton instance of this class. + * + * @return DBManager the singleton instance + */ + static public function getInstance() + { + if (is_null(DBManager::$instance)) { + DBManager::$instance = new DBManager(); + } + return DBManager::$instance; + } + + + /** + * This method returns the database connection to the given key. Throws a + * DBManagerException if there is no such connection. + * + * Example usage: + * @code + * try { + * $db = DBManager::getInstance()->getConnection("foo"); + * $db->exec($sql); + * } catch (DBManagerException $e) { + * echo "oops"; + * } + * @endcode + * + * @param string the key + * + * @throw DBManagerException + * + * @return PDO the database connection + */ + public function getConnection($database) + { + + if (!isset($this->connections[$database])) { + throw new DBManagerException('Database connection: "'.$database. + '" does not exist.'); + } + + return $this->connections[$database]; + } + + + /** + * This method maps the specified key to the specified database connection. + * + * You can either use an instance of class PDO or specify a DSN (optionally + * with username/password). + * + * The (possibly newly created) connection is configured to throw exceptions + * and to buffer queries if it is a MySQL connection. + * + * Examples: + * @code + * $dbManager = DBManager::getInstance(); + * + * // using an existing PDO connection + * $existingPdo = new LoggingPDO($dsn); + * $dbManager->setConnection('studip', $pdo); + * + * // using a DSN with username and password + * $dbManager->setConnection('studip', $dsn , $username, $password); + * @endcode + * + * @param string the key + * @param string|PDO either a DSN or an existing PDO connection + * @param string (optional) the connection's username + * @param array (optional) the connection's password + * + * @return DBManager this instance, useful for cascading method calls + */ + public function setConnection($database, $dsnOrConnection, $user = NULL, + $pass = NULL) + { + $connection = $dsnOrConnection instanceof PDO + ? $dsnOrConnection + : new StudipPDO($dsnOrConnection, $user, $pass); + + $this->configureConnection($connection); + $this->connections[$database] = $connection; + + return $this; + } + + + // PDO connection should throw exceptions and use buffered queries + private function configureConnection($connection) + { + + $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if ($connection->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') { + $connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); + // $connection->exec('SET CHARACTER SET latin1'); + } + } + + /** + * This method creates an alias for a database connection. + * + * This is useful if you want to use different keys but access the same + * database, e.g. if you want to use master-slave replication in the future + * + * @param string the old key of the database connection + * @param string the new key of the database connection + * + * @return DBManager this instance, useful for cascading method calls + */ + public function aliasConnection($old, $new) + { + + if (!isset($this->connections[$old])) { + throw new DBManagerException('No database found using key: ' . $old); + } + + $this->connections[$new] = $this->connections[$old]; + + return $this; + } + + + /** + * Shortcut static method to retrieve the database connection for a given + * key. + * + * Example usage: + * @code + * // instead of + * $db = DBManager::getInstance()->getConnection("studip"); + * + * // this can be shortened to + * $db = DBManager::get("studip"); + * + * // or in this case (as "studip" is the default key) + * $db = DBManager::get(); + * @endcode + * + * @param string the key + * + * @return StudipPDO the database connection + */ + static public function get($database = 'studip') + { + $manager = DBManager::getInstance(); + return $manager->getConnection($database); + } +} + + +/** + * The DBManager throws this exception if it cannot find a connection using + * a non existant key. + */ +class DBManagerException extends Exception +{ + + /** + * @param string the message of this exception + * + * @return void + */ + public function __construct($message) + { + parent::__construct($message); + } +} diff --git a/lib/classes/DataFieldBoolEntry.class.php b/lib/classes/DataFieldBoolEntry.class.php deleted file mode 100644 index da294b6..0000000 --- a/lib/classes/DataFieldBoolEntry.class.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldBoolEntry extends DataFieldEntry -{ - protected $template = 'bool.php'; - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - return $this->getValue() ? _('Ja') : _('Nein'); - } - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($submitted_value) - { - $this->setValue((int) $submitted_value); - } -} diff --git a/lib/classes/DataFieldBoolEntry.php b/lib/classes/DataFieldBoolEntry.php new file mode 100644 index 0000000..da294b6 --- /dev/null +++ b/lib/classes/DataFieldBoolEntry.php @@ -0,0 +1,35 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldBoolEntry extends DataFieldEntry +{ + protected $template = 'bool.php'; + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + return $this->getValue() ? _('Ja') : _('Nein'); + } + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($submitted_value) + { + $this->setValue((int) $submitted_value); + } +} diff --git a/lib/classes/DataFieldComboEntry.class.php b/lib/classes/DataFieldComboEntry.class.php deleted file mode 100644 index 6047050..0000000 --- a/lib/classes/DataFieldComboEntry.class.php +++ /dev/null @@ -1,79 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldComboEntry extends DataFieldEntry -{ - protected $template = 'combo.php'; - - /** - * Constructs this datafield - * - * @param DataField $datafield Underlying model - * @param String $rangeID Range id - * @param mixed $value Value - */ - public function __construct(DataField $struct, $range_id, $value) - { - parent::__construct($struct, $range_id, $value); - - if ($this->getValue() === null) { - $parameters = $this->getParameters(); - $this->setValue($parameters[0]); // first selectbox entry is default - } - } - - /** - * Returns the number of html fields this datafield uses for input. - * - * @return int representing the number of html fields - */ - public function numberOfHTMLFields() - { - return 2; - } - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($value) - { - $index = $value['combo']; - $value = $value[$index]; - parent::setValueFromSubmit($value); - } - - /** - * Returns the according input elements as html for this datafield - * - * @param String $name Name prefix of the associated input - * @param Array $variables Additional variables - * @return String containing the required html - */ - public function getHTML($name = '', $variables = []) - { - return parent::getHTML($name, $variables + [ - 'values' => $this->getParameters(), - ]); - } - - /** - * Returns the individual type parameters. - * - * @return array containing the individual type parameters - */ - protected function getParameters() - { - $parameters = explode("\n", rtrim($this->model->typeparam)); - $parameters = array_map('trim', $parameters); - return $parameters; - } -} diff --git a/lib/classes/DataFieldComboEntry.php b/lib/classes/DataFieldComboEntry.php new file mode 100644 index 0000000..6047050 --- /dev/null +++ b/lib/classes/DataFieldComboEntry.php @@ -0,0 +1,79 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldComboEntry extends DataFieldEntry +{ + protected $template = 'combo.php'; + + /** + * Constructs this datafield + * + * @param DataField $datafield Underlying model + * @param String $rangeID Range id + * @param mixed $value Value + */ + public function __construct(DataField $struct, $range_id, $value) + { + parent::__construct($struct, $range_id, $value); + + if ($this->getValue() === null) { + $parameters = $this->getParameters(); + $this->setValue($parameters[0]); // first selectbox entry is default + } + } + + /** + * Returns the number of html fields this datafield uses for input. + * + * @return int representing the number of html fields + */ + public function numberOfHTMLFields() + { + return 2; + } + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($value) + { + $index = $value['combo']; + $value = $value[$index]; + parent::setValueFromSubmit($value); + } + + /** + * Returns the according input elements as html for this datafield + * + * @param String $name Name prefix of the associated input + * @param Array $variables Additional variables + * @return String containing the required html + */ + public function getHTML($name = '', $variables = []) + { + return parent::getHTML($name, $variables + [ + 'values' => $this->getParameters(), + ]); + } + + /** + * Returns the individual type parameters. + * + * @return array containing the individual type parameters + */ + protected function getParameters() + { + $parameters = explode("\n", rtrim($this->model->typeparam)); + $parameters = array_map('trim', $parameters); + return $parameters; + } +} diff --git a/lib/classes/DataFieldDateEntry.class.php b/lib/classes/DataFieldDateEntry.class.php deleted file mode 100644 index bf7d518..0000000 --- a/lib/classes/DataFieldDateEntry.class.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldDateEntry extends DataFieldEntry -{ - protected $template = 'date.php'; - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($value) - { - if ($value) { - $value = trim($value); - $items = explode(".", $value); - $value = array_reverse($items); - $value = array_filter($value); - $date = implode('-', $value); - parent::setValueFromSubmit($date); - } - } - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entries = true) - { - if ($this->isValid()) { - $value = trim($this->value); - $value = explode('-', $value); - $value = array_reverse($value); - $value = implode('.', $value); - return $value; - } - - return ''; - } - - /** - * Returns the according input elements as html for this datafield - * - * @param String $name Name prefix of the associated input - * @param Array $variables Additional variables - * @return String containing the required html - */ - public function getHTML($name = '', $variables = []) - { - return parent::getHTML($name, $variables + [ - 'timestamp' => strtotime(trim($this->value)), - ]); - } - - /** - * Returns whether the datafield contents are valid - * - * @return boolean indicating whether the datafield contents are valid - */ - public function isValid() - { - $value = trim($this->value); - - if (!$value) { - return parent::isValid(); - } - - return parent::isValid() && strtotime($value) !== false; - } -} diff --git a/lib/classes/DataFieldDateEntry.php b/lib/classes/DataFieldDateEntry.php new file mode 100644 index 0000000..bf7d518 --- /dev/null +++ b/lib/classes/DataFieldDateEntry.php @@ -0,0 +1,80 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldDateEntry extends DataFieldEntry +{ + protected $template = 'date.php'; + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($value) + { + if ($value) { + $value = trim($value); + $items = explode(".", $value); + $value = array_reverse($items); + $value = array_filter($value); + $date = implode('-', $value); + parent::setValueFromSubmit($date); + } + } + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entries = true) + { + if ($this->isValid()) { + $value = trim($this->value); + $value = explode('-', $value); + $value = array_reverse($value); + $value = implode('.', $value); + return $value; + } + + return ''; + } + + /** + * Returns the according input elements as html for this datafield + * + * @param String $name Name prefix of the associated input + * @param Array $variables Additional variables + * @return String containing the required html + */ + public function getHTML($name = '', $variables = []) + { + return parent::getHTML($name, $variables + [ + 'timestamp' => strtotime(trim($this->value)), + ]); + } + + /** + * Returns whether the datafield contents are valid + * + * @return boolean indicating whether the datafield contents are valid + */ + public function isValid() + { + $value = trim($this->value); + + if (!$value) { + return parent::isValid(); + } + + return parent::isValid() && strtotime($value) !== false; + } +} diff --git a/lib/classes/DataFieldEmailEntry.class.php b/lib/classes/DataFieldEmailEntry.class.php deleted file mode 100644 index aafe7bb..0000000 --- a/lib/classes/DataFieldEmailEntry.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldEmailEntry extends DataFieldEntry -{ - protected $template = 'email.php'; - - /** - * Returns whether the datafield contents are valid - * - * @return boolean indicating whether the datafield contents are valid - */ - public function isValid() - { - return parent::isValid() - && (!$this->getValue() || filter_var($this->getValue(), FILTER_VALIDATE_EMAIL)); - } -} diff --git a/lib/classes/DataFieldEmailEntry.php b/lib/classes/DataFieldEmailEntry.php new file mode 100644 index 0000000..aafe7bb --- /dev/null +++ b/lib/classes/DataFieldEmailEntry.php @@ -0,0 +1,25 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldEmailEntry extends DataFieldEntry +{ + protected $template = 'email.php'; + + /** + * Returns whether the datafield contents are valid + * + * @return boolean indicating whether the datafield contents are valid + */ + public function isValid() + { + return parent::isValid() + && (!$this->getValue() || filter_var($this->getValue(), FILTER_VALIDATE_EMAIL)); + } +} diff --git a/lib/classes/DataFieldEntry.class.php b/lib/classes/DataFieldEntry.class.php deleted file mode 100644 index fd50a0b..0000000 --- a/lib/classes/DataFieldEntry.class.php +++ /dev/null @@ -1,588 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -abstract class DataFieldEntry -{ - protected static $supported_types = [ - 'bool', - 'textline', - 'textlinei18n', - 'textarea', - 'textareai18n', - 'textmarkup', - 'textmarkupi18n', - 'selectbox', - 'selectboxmultiple', - 'date', - 'time', - 'email', - 'phone', - 'radio', - 'combo', - 'link', - ]; - - protected $language = ''; - protected $template = null; - - /** - * Returns all supported datafield types. - * - * @param string Object type of the datafield. - * @return array of supported types - */ - public static function getSupportedTypes($object_type = null) - { - // no i18n fields for descriptors - if (in_array($object_type, ['moduldeskriptor', 'modulteildeskriptor'])) { - return array_diff( - self::$supported_types, - [ - 'textlinei18n', - 'textareai18n', - 'textmarkupi18n' - ]); - } - return self::$supported_types; - } - - /** - * Returns the according class for a given type. - * - * @param String $type Type of the datafield - * @return String class name - */ - private static function getClassForType($type) - { - if ($type === 'selectboxmultiple') { - return 'DataFieldSelectboxMultipleEntry'; - } - return 'DataField' . ucfirst($type) . 'Entry'; - } - - /** - * Factory method that returns the appropriate datafield object - * for the given parameters. - * - * @param DataField $datafield Underlying structure - * @param String $rangeID Range id - * @param mixed $value Value of the entry - * @return DataFieldEntry instance of appropriate type - */ - public static function createDataFieldEntry(DataField $datafield, $rangeID = '', $value = null) - { - $type = $datafield->type; - if (!in_array($type, self::getSupportedTypes())) { - return false; - } - - $entry_class = self::getClassForType($type); - $entry = new $entry_class($datafield, $rangeID, $value); - - return $entry; - } - - /** - * Enter description here... - * - * @param string $range_id - * @param string $object_type - * @param string $object_class_hint - * @return array - */ - public static function getDataFieldEntries($range_id, $object_type = '', $object_class_hint = '') - { - if (!$range_id) { - return []; // we necessarily need a range ID - } - $parameters = []; - $clause1 = ''; - $clause2 = ''; - $clause3 = ''; - if(is_array($range_id)) { - // rangeID may be an array ("classic" rangeID and second rangeID used for user roles) - $secRangeID = $range_id[1]; - $rangeID = $range_id[0]; // to keep compatible with following code - if('usersemdata' !== $object_type && 'roleinstdata' !== $object_type) { - $object_type = 'userinstrole'; - } - $clause1 = "AND sec_range_id= :sec_range_id"; - $parameters[':sec_range_id'] = $secRangeID; - } else { - $rangeID = $range_id; - } - if (!$object_type) { - $object_type = get_object_type($rangeID); - } - - if($object_type) { - switch ($object_type) { - case 'sem': - if ($object_class_hint) { - $object_class = SeminarCategories::GetByTypeId($object_class_hint); - } else { - $object_class = SeminarCategories::GetBySeminarId($rangeID); - } - - $clause2 = "object_class = :object_class OR object_class IS NULL"; - $parameters[':object_class'] = (int) $object_class->id; - $clause3 = 'a.institut_id IS NULL OR a.institut_id IN (:institut_ids)'; - $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = :seminar_id"; - $statement = DBManager::get()->prepare($query); - $statement->execute([':seminar_id' => $rangeID]); - $parameters[':institut_ids'] = array_keys($statement->fetchGrouped()); - break; - case 'inst': - case 'fak': - - if ($object_class_hint) { - $object_class = $object_class_hint; - } else { - $query = "SELECT type FROM Institute WHERE Institut_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$rangeID]); - $object_class = $statement->fetchColumn(); - } - $object_type = "inst"; - $clause2 = "object_class = :object_class OR object_class IS NULL"; - $parameters[':object_class'] = (int) $object_class; - $clause3 = 'a.institut_id IS NULL OR a.institut_id = :institut_id'; - $parameters[':institut_id'] = $rangeID; - break; - case 'roleinstdata': //hmm tja, vermutlich so - case 'moduldeskriptor': - case 'modulteildeskriptor': - case 'studycourse': - $clause2 = '1'; - $clause3 = '1'; - if (is_array($range_id) && isset($range_id[0])) { - $clause3 = 'a.institut_id IS NULL OR a.institut_id = :institut_id'; - $parameters[':institut_id'] = $range_id[0]; - } - break; - case 'user': - case 'userinstrole': - case 'usersemdata': - $object_class = is_object($GLOBALS['perm']) ? DataField::permMask($GLOBALS['perm']->get_perm($rangeID)) : 0; - $clause2 = "((object_class & :object_class) OR object_class IS NULL)"; - $parameters[':object_class'] = (int) $object_class; - - $clause3 = 'a.institut_id IS NULL OR a.institut_id IN (:institut_ids)'; - $query = "SELECT institut_id FROM user_inst WHERE user_id = :user_id"; - $statement = DBManager::get()->prepare($query); - $statement->execute([':user_id' => $rangeID]); - $parameters[':institut_ids'] = array_keys($statement->fetchGrouped()); - break; - } - $query = "SELECT a.*, content - FROM datafields AS a - LEFT JOIN datafields_entries AS b - ON (a.datafield_id = b.datafield_id AND range_id = :range_id {$clause1}) - WHERE object_type = :object_type AND ({$clause2}) AND ($clause3) - ORDER BY priority"; - $parameters[':range_id'] = $rangeID; - $parameters[':object_type'] = $object_type; - - $rs = DBManager::get()->prepare($query); - $rs->execute($parameters); - - $entries = []; - while ($data = $rs->fetch(PDO::FETCH_ASSOC)) { - $datafield = DataField::buildExisting($data); - $entries[$data['datafield_id']] = DataFieldEntry::createDataFieldEntry($datafield, $range_id, $data['content']); - } - } - return $entries ?: []; - } - - /** - * Removes all datafields from a given range_id (and secondary range - * id if passed as array) - * - * @param mixed $range_id Range id (or array with range id and secondary - * range id) - * @return int representing the number of deleted entries - */ - public static function removeAll($range_id) - { - if (is_array($range_id)) { - list ($rangeID, $secRangeID) = $range_id; - } else { - $rangeID = $range_id; - $secRangeID = ""; - } - - if (!$rangeID && !$secRangeID) { - return; - } - - $conditions = []; - $parameters = []; - - if ($rangeID) { - $conditions[] = 'range_id = ?'; - $parameters[] = $rangeID; - } - if ($secRangeID) { - $conditions[] = 'sec_range_id = ?'; - $parameters[] = $secRangeID; - } - - $where = implode(' AND ', $conditions); - - return DatafieldEntryModel::deleteBySQL($where, $parameters); - } - - public $value; - public $model; - public $rangeID; - - /** - * Constructs this datafield - * - * @param DataField $datafield Underlying model - * @param mixed $range_id Range id (or array with range id and secondary - * range id) - * @param mixed $value Value - */ - public function __construct(DataField $datafield = null, $rangeID = '', $value = null) - { - $this->model = $datafield; - $this->rangeID = $rangeID; - $this->value = isset($value) ? $value : $datafield->default_value; - } - - /** - * Stores this datafield entry - * - * @return int representing the number of changed entries - */ - public function store() - { - $id = [ - $this->model->id, - (string) $this->getRangeID(), - (string) $this->getSecondRangeID(), - (string) $this->language - ]; - $entry = new DatafieldEntryModel($id); - $entry->lang = (string) $this->language; - - $old_value = $entry->content; - $entry->content = $this->getValue(); - - if ($entry->content == $this->model->default_value) { - $result = $entry->isNew() ? 0 : $entry->delete(); - } else { - $result = $entry->store(); - } - - if ($result) { - NotificationCenter::postNotification('DatafieldDidUpdate', $this, [ - 'changed' => $result, - 'old_value' => $old_value, - ]); - } - - return $result; - } - - /** - * Returns whether this datafield is required - * - * @return bool indicating whether the datafield is required or not - */ - public function isRequired() - { - return $this->model->is_required; - } - - /** - * Returns the description of this datafield - * - * @return String containing the description - */ - public function getDescription() - { - return $this->model->description; - } - - /** - * Returns the type of this datafield - * - * @return string type of entry - */ - public function getType() - { - $class = mb_strtolower(get_class($this)); - return mb_substr($class, 9, mb_strpos($class, 'entry') - 9); - } - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - if ($entities) { - return htmlReady($this->getValue()); - } - return $this->getValue(); - } - - /** - * Returns the value of the datafield - * - * @return mixed containing the value - */ - public function getValue() - { - return $this->value; - } - - /** - * Returns the name of the datafield - * - * @return String containing the name - */ - public function getName() - { - return $this->model->name; - } - - /** - * Returns the id of the datafield - * - * @return String containing the id - */ - public function getId() - { - return $this->model->id; - } - - /** - * Returns the according input elements as html for this datafield - * - * @param String $name Name prefix of the associated input - * @param Array $variables Additional variables - * @return String containing the required html - */ - public function getHTML($name = '', $variables = []) - { - $variables = array_merge([ - 'name' => $name, - 'entry' => $this, - 'model' => $this->model, - 'value' => $this->getValue(), - ], $variables); - - return $GLOBALS['template_factory']->render('datafields/' . $this->template, $variables); - } - - /** - * Sets the value - * - * @param mixed $value The value - */ - public function setValue($value) - { - $this->value = $value; - } - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($submitted_value) - { - $this->setValue($submitted_value); - } - - /** - * Sets the range id - * - * @param String $range_id Range id - */ - public function setRangeID($range_id) - { - $this->rangeID = $range_id; - } - - /** - * Sets the secondary range id - * - * @param String $sec_range_id Secondary range id - */ - public function setSecondRangeID($sec_range_id) - { - $this->rangeID = [$this->getRangeID(), $sec_range_id]; - } - - /** - * Sets the prefered content language if this is an i18n datafield. - * - * @param string $language The prefered display language - */ - public function setContentLanguage($language) - { - if (!Config::get()->CONTENT_LANGUAGES[$language]) { - throw new InvalidArgumentException('Language not configured.'); - } - - $languages = array_keys(Config::get()->CONTENT_LANGUAGES); - if ($language == reset($languages)) { - $language = ''; - } - - $this->language = $language; - } - - /** - * Checks if datafield is empty (was not set) - * - * @return bool true if empty, else false - */ - public function isEmpty() - { - return $this->getValue() == ''; - } - - /** - * Returns whether the datafield contents are valid - * - * @return boolean indicating whether the datafield contents are valid - */ - public function isValid() - { - return trim($this->getValue()) - || !$this->model->is_required; - } - - /** - * Returns the number of html fields this datafield uses for input. - * - * @return int representing the number of html fields - */ - public function numberOfHTMLFields() - { - return 1; - } - - /** - * Returns the range id - * - * @return String containing the range id - */ - public function getRangeID() - { - if (is_array($this->rangeID)) { - return reset($this->rangeID); - } - return $this->rangeID; - } - - /** - * Returns the secondary range id - * - * @return String containing the secondary range id - */ - public function getSecondRangeID() - { - if (is_array($this->rangeID)) { - list (, $secRangeID) = $this->rangeID; - return $secRangeID; - } - return ''; - } - - /** - * Returns whether the datafield is visible for the current user - * - * @param String $perm Permissions to test against (optional, default to - * the current user's permissions) - * @param bool $test_ownership Defines whether the ownership of the field - * should be taken into account; a field may - * be invisible for a user according to the - * permissions but since the datafield belongs - * to the user, it is visible. - * @return boolean indicating whether the datafield is visible - */ - public function isVisible($perm = null, $test_ownership = true) - { - if ($test_ownership) { - return $this->model->accessAllowed($perm, - $GLOBALS['user']->id, - $this->getRangeID()); - } - return $this->model->accessAllowed($perm); - } - - /** - * Returns whether the datafield is editable for the current user - * - * @param mixed $perms Perms to test against (optional, defaults to logged - * in user's perms) - * @return boolean indicating whether the datafield is editable - */ - public function isEditable($perms = null) - { - return $this->model->editAllowed($perms ?: $GLOBALS['perm']->get_perm()); - } - - /** - * Returns a human readable string describing the view permissions - * - * @return String containing the descriptons of the view permissions - */ - public function getPermsDescription() - { - if ($this->model->view_perms === 'all') { - return _('sichtbar für alle'); - } - return sprintf(_('sichtbar nur für Sie und alle %s'), - $this->prettyPrintViewPerms()); - } - - /** - * Generates a full status description depending on the the perms - * - * @return string - */ - protected function prettyPrintViewPerms() - { - switch ($this->model->view_perms) { - case 'all': - return _('alle'); - break; - case 'root': - return _('Systemadministrator/-innen'); - break; - case 'admin': - return _('Administrator/-innen'); - break; - case 'dozent': - return _('Lehrenden'); - break; - case 'tutor': - return _('Tutor/-innen'); - break; - case 'autor': - return _('Studierenden'); - break; - case 'user': - return _('Nutzer/-innen'); - break; - } - return ''; - } - -} diff --git a/lib/classes/DataFieldEntry.php b/lib/classes/DataFieldEntry.php new file mode 100644 index 0000000..fd50a0b --- /dev/null +++ b/lib/classes/DataFieldEntry.php @@ -0,0 +1,588 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +abstract class DataFieldEntry +{ + protected static $supported_types = [ + 'bool', + 'textline', + 'textlinei18n', + 'textarea', + 'textareai18n', + 'textmarkup', + 'textmarkupi18n', + 'selectbox', + 'selectboxmultiple', + 'date', + 'time', + 'email', + 'phone', + 'radio', + 'combo', + 'link', + ]; + + protected $language = ''; + protected $template = null; + + /** + * Returns all supported datafield types. + * + * @param string Object type of the datafield. + * @return array of supported types + */ + public static function getSupportedTypes($object_type = null) + { + // no i18n fields for descriptors + if (in_array($object_type, ['moduldeskriptor', 'modulteildeskriptor'])) { + return array_diff( + self::$supported_types, + [ + 'textlinei18n', + 'textareai18n', + 'textmarkupi18n' + ]); + } + return self::$supported_types; + } + + /** + * Returns the according class for a given type. + * + * @param String $type Type of the datafield + * @return String class name + */ + private static function getClassForType($type) + { + if ($type === 'selectboxmultiple') { + return 'DataFieldSelectboxMultipleEntry'; + } + return 'DataField' . ucfirst($type) . 'Entry'; + } + + /** + * Factory method that returns the appropriate datafield object + * for the given parameters. + * + * @param DataField $datafield Underlying structure + * @param String $rangeID Range id + * @param mixed $value Value of the entry + * @return DataFieldEntry instance of appropriate type + */ + public static function createDataFieldEntry(DataField $datafield, $rangeID = '', $value = null) + { + $type = $datafield->type; + if (!in_array($type, self::getSupportedTypes())) { + return false; + } + + $entry_class = self::getClassForType($type); + $entry = new $entry_class($datafield, $rangeID, $value); + + return $entry; + } + + /** + * Enter description here... + * + * @param string $range_id + * @param string $object_type + * @param string $object_class_hint + * @return array + */ + public static function getDataFieldEntries($range_id, $object_type = '', $object_class_hint = '') + { + if (!$range_id) { + return []; // we necessarily need a range ID + } + $parameters = []; + $clause1 = ''; + $clause2 = ''; + $clause3 = ''; + if(is_array($range_id)) { + // rangeID may be an array ("classic" rangeID and second rangeID used for user roles) + $secRangeID = $range_id[1]; + $rangeID = $range_id[0]; // to keep compatible with following code + if('usersemdata' !== $object_type && 'roleinstdata' !== $object_type) { + $object_type = 'userinstrole'; + } + $clause1 = "AND sec_range_id= :sec_range_id"; + $parameters[':sec_range_id'] = $secRangeID; + } else { + $rangeID = $range_id; + } + if (!$object_type) { + $object_type = get_object_type($rangeID); + } + + if($object_type) { + switch ($object_type) { + case 'sem': + if ($object_class_hint) { + $object_class = SeminarCategories::GetByTypeId($object_class_hint); + } else { + $object_class = SeminarCategories::GetBySeminarId($rangeID); + } + + $clause2 = "object_class = :object_class OR object_class IS NULL"; + $parameters[':object_class'] = (int) $object_class->id; + $clause3 = 'a.institut_id IS NULL OR a.institut_id IN (:institut_ids)'; + $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = :seminar_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute([':seminar_id' => $rangeID]); + $parameters[':institut_ids'] = array_keys($statement->fetchGrouped()); + break; + case 'inst': + case 'fak': + + if ($object_class_hint) { + $object_class = $object_class_hint; + } else { + $query = "SELECT type FROM Institute WHERE Institut_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$rangeID]); + $object_class = $statement->fetchColumn(); + } + $object_type = "inst"; + $clause2 = "object_class = :object_class OR object_class IS NULL"; + $parameters[':object_class'] = (int) $object_class; + $clause3 = 'a.institut_id IS NULL OR a.institut_id = :institut_id'; + $parameters[':institut_id'] = $rangeID; + break; + case 'roleinstdata': //hmm tja, vermutlich so + case 'moduldeskriptor': + case 'modulteildeskriptor': + case 'studycourse': + $clause2 = '1'; + $clause3 = '1'; + if (is_array($range_id) && isset($range_id[0])) { + $clause3 = 'a.institut_id IS NULL OR a.institut_id = :institut_id'; + $parameters[':institut_id'] = $range_id[0]; + } + break; + case 'user': + case 'userinstrole': + case 'usersemdata': + $object_class = is_object($GLOBALS['perm']) ? DataField::permMask($GLOBALS['perm']->get_perm($rangeID)) : 0; + $clause2 = "((object_class & :object_class) OR object_class IS NULL)"; + $parameters[':object_class'] = (int) $object_class; + + $clause3 = 'a.institut_id IS NULL OR a.institut_id IN (:institut_ids)'; + $query = "SELECT institut_id FROM user_inst WHERE user_id = :user_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute([':user_id' => $rangeID]); + $parameters[':institut_ids'] = array_keys($statement->fetchGrouped()); + break; + } + $query = "SELECT a.*, content + FROM datafields AS a + LEFT JOIN datafields_entries AS b + ON (a.datafield_id = b.datafield_id AND range_id = :range_id {$clause1}) + WHERE object_type = :object_type AND ({$clause2}) AND ($clause3) + ORDER BY priority"; + $parameters[':range_id'] = $rangeID; + $parameters[':object_type'] = $object_type; + + $rs = DBManager::get()->prepare($query); + $rs->execute($parameters); + + $entries = []; + while ($data = $rs->fetch(PDO::FETCH_ASSOC)) { + $datafield = DataField::buildExisting($data); + $entries[$data['datafield_id']] = DataFieldEntry::createDataFieldEntry($datafield, $range_id, $data['content']); + } + } + return $entries ?: []; + } + + /** + * Removes all datafields from a given range_id (and secondary range + * id if passed as array) + * + * @param mixed $range_id Range id (or array with range id and secondary + * range id) + * @return int representing the number of deleted entries + */ + public static function removeAll($range_id) + { + if (is_array($range_id)) { + list ($rangeID, $secRangeID) = $range_id; + } else { + $rangeID = $range_id; + $secRangeID = ""; + } + + if (!$rangeID && !$secRangeID) { + return; + } + + $conditions = []; + $parameters = []; + + if ($rangeID) { + $conditions[] = 'range_id = ?'; + $parameters[] = $rangeID; + } + if ($secRangeID) { + $conditions[] = 'sec_range_id = ?'; + $parameters[] = $secRangeID; + } + + $where = implode(' AND ', $conditions); + + return DatafieldEntryModel::deleteBySQL($where, $parameters); + } + + public $value; + public $model; + public $rangeID; + + /** + * Constructs this datafield + * + * @param DataField $datafield Underlying model + * @param mixed $range_id Range id (or array with range id and secondary + * range id) + * @param mixed $value Value + */ + public function __construct(DataField $datafield = null, $rangeID = '', $value = null) + { + $this->model = $datafield; + $this->rangeID = $rangeID; + $this->value = isset($value) ? $value : $datafield->default_value; + } + + /** + * Stores this datafield entry + * + * @return int representing the number of changed entries + */ + public function store() + { + $id = [ + $this->model->id, + (string) $this->getRangeID(), + (string) $this->getSecondRangeID(), + (string) $this->language + ]; + $entry = new DatafieldEntryModel($id); + $entry->lang = (string) $this->language; + + $old_value = $entry->content; + $entry->content = $this->getValue(); + + if ($entry->content == $this->model->default_value) { + $result = $entry->isNew() ? 0 : $entry->delete(); + } else { + $result = $entry->store(); + } + + if ($result) { + NotificationCenter::postNotification('DatafieldDidUpdate', $this, [ + 'changed' => $result, + 'old_value' => $old_value, + ]); + } + + return $result; + } + + /** + * Returns whether this datafield is required + * + * @return bool indicating whether the datafield is required or not + */ + public function isRequired() + { + return $this->model->is_required; + } + + /** + * Returns the description of this datafield + * + * @return String containing the description + */ + public function getDescription() + { + return $this->model->description; + } + + /** + * Returns the type of this datafield + * + * @return string type of entry + */ + public function getType() + { + $class = mb_strtolower(get_class($this)); + return mb_substr($class, 9, mb_strpos($class, 'entry') - 9); + } + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + if ($entities) { + return htmlReady($this->getValue()); + } + return $this->getValue(); + } + + /** + * Returns the value of the datafield + * + * @return mixed containing the value + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the name of the datafield + * + * @return String containing the name + */ + public function getName() + { + return $this->model->name; + } + + /** + * Returns the id of the datafield + * + * @return String containing the id + */ + public function getId() + { + return $this->model->id; + } + + /** + * Returns the according input elements as html for this datafield + * + * @param String $name Name prefix of the associated input + * @param Array $variables Additional variables + * @return String containing the required html + */ + public function getHTML($name = '', $variables = []) + { + $variables = array_merge([ + 'name' => $name, + 'entry' => $this, + 'model' => $this->model, + 'value' => $this->getValue(), + ], $variables); + + return $GLOBALS['template_factory']->render('datafields/' . $this->template, $variables); + } + + /** + * Sets the value + * + * @param mixed $value The value + */ + public function setValue($value) + { + $this->value = $value; + } + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($submitted_value) + { + $this->setValue($submitted_value); + } + + /** + * Sets the range id + * + * @param String $range_id Range id + */ + public function setRangeID($range_id) + { + $this->rangeID = $range_id; + } + + /** + * Sets the secondary range id + * + * @param String $sec_range_id Secondary range id + */ + public function setSecondRangeID($sec_range_id) + { + $this->rangeID = [$this->getRangeID(), $sec_range_id]; + } + + /** + * Sets the prefered content language if this is an i18n datafield. + * + * @param string $language The prefered display language + */ + public function setContentLanguage($language) + { + if (!Config::get()->CONTENT_LANGUAGES[$language]) { + throw new InvalidArgumentException('Language not configured.'); + } + + $languages = array_keys(Config::get()->CONTENT_LANGUAGES); + if ($language == reset($languages)) { + $language = ''; + } + + $this->language = $language; + } + + /** + * Checks if datafield is empty (was not set) + * + * @return bool true if empty, else false + */ + public function isEmpty() + { + return $this->getValue() == ''; + } + + /** + * Returns whether the datafield contents are valid + * + * @return boolean indicating whether the datafield contents are valid + */ + public function isValid() + { + return trim($this->getValue()) + || !$this->model->is_required; + } + + /** + * Returns the number of html fields this datafield uses for input. + * + * @return int representing the number of html fields + */ + public function numberOfHTMLFields() + { + return 1; + } + + /** + * Returns the range id + * + * @return String containing the range id + */ + public function getRangeID() + { + if (is_array($this->rangeID)) { + return reset($this->rangeID); + } + return $this->rangeID; + } + + /** + * Returns the secondary range id + * + * @return String containing the secondary range id + */ + public function getSecondRangeID() + { + if (is_array($this->rangeID)) { + list (, $secRangeID) = $this->rangeID; + return $secRangeID; + } + return ''; + } + + /** + * Returns whether the datafield is visible for the current user + * + * @param String $perm Permissions to test against (optional, default to + * the current user's permissions) + * @param bool $test_ownership Defines whether the ownership of the field + * should be taken into account; a field may + * be invisible for a user according to the + * permissions but since the datafield belongs + * to the user, it is visible. + * @return boolean indicating whether the datafield is visible + */ + public function isVisible($perm = null, $test_ownership = true) + { + if ($test_ownership) { + return $this->model->accessAllowed($perm, + $GLOBALS['user']->id, + $this->getRangeID()); + } + return $this->model->accessAllowed($perm); + } + + /** + * Returns whether the datafield is editable for the current user + * + * @param mixed $perms Perms to test against (optional, defaults to logged + * in user's perms) + * @return boolean indicating whether the datafield is editable + */ + public function isEditable($perms = null) + { + return $this->model->editAllowed($perms ?: $GLOBALS['perm']->get_perm()); + } + + /** + * Returns a human readable string describing the view permissions + * + * @return String containing the descriptons of the view permissions + */ + public function getPermsDescription() + { + if ($this->model->view_perms === 'all') { + return _('sichtbar für alle'); + } + return sprintf(_('sichtbar nur für Sie und alle %s'), + $this->prettyPrintViewPerms()); + } + + /** + * Generates a full status description depending on the the perms + * + * @return string + */ + protected function prettyPrintViewPerms() + { + switch ($this->model->view_perms) { + case 'all': + return _('alle'); + break; + case 'root': + return _('Systemadministrator/-innen'); + break; + case 'admin': + return _('Administrator/-innen'); + break; + case 'dozent': + return _('Lehrenden'); + break; + case 'tutor': + return _('Tutor/-innen'); + break; + case 'autor': + return _('Studierenden'); + break; + case 'user': + return _('Nutzer/-innen'); + break; + } + return ''; + } + +} diff --git a/lib/classes/DataFieldLinkEntry.class.php b/lib/classes/DataFieldLinkEntry.class.php deleted file mode 100644 index 702907f..0000000 --- a/lib/classes/DataFieldLinkEntry.class.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldLinkEntry extends DataFieldEntry -{ - protected $template = 'link.php'; - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - if ($entities) { - return formatLinks($this->getValue()); - } - return $this->getValue(); - } - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($submitted_value) - { - if ($submitted_value === 'http://') { - $submitted_value = ''; - } - $this->setValue($submitted_value); - } - - /** - * Returns whether the datafield contents are valid - * - * @return boolean indicating whether the datafield contents are valid - */ - public function isValid() - { - return parent::isValid() - && (!$this->getValue() - || (filter_var($this->getValue(), FILTER_VALIDATE_URL) - && preg_match('%^(https?|ftp)://%', $this->getValue()))); - } -} diff --git a/lib/classes/DataFieldLinkEntry.php b/lib/classes/DataFieldLinkEntry.php new file mode 100644 index 0000000..702907f --- /dev/null +++ b/lib/classes/DataFieldLinkEntry.php @@ -0,0 +1,54 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldLinkEntry extends DataFieldEntry +{ + protected $template = 'link.php'; + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + if ($entities) { + return formatLinks($this->getValue()); + } + return $this->getValue(); + } + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($submitted_value) + { + if ($submitted_value === 'http://') { + $submitted_value = ''; + } + $this->setValue($submitted_value); + } + + /** + * Returns whether the datafield contents are valid + * + * @return boolean indicating whether the datafield contents are valid + */ + public function isValid() + { + return parent::isValid() + && (!$this->getValue() + || (filter_var($this->getValue(), FILTER_VALIDATE_URL) + && preg_match('%^(https?|ftp)://%', $this->getValue()))); + } +} diff --git a/lib/classes/DataFieldPhoneEntry.class.php b/lib/classes/DataFieldPhoneEntry.class.php deleted file mode 100644 index d3a0e70..0000000 --- a/lib/classes/DataFieldPhoneEntry.class.php +++ /dev/null @@ -1,122 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldPhoneEntry extends DataFieldEntry -{ - protected $template = 'phone.php'; - - /** - * Returns the number of html fields this datafield uses for input. - * - * @return int representing the number of html fields - */ - public function numberOfHTMLFields() - { - return 3; - } - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($value) - { - if (is_array($value)) { - $value = array_slice($value, 0, 3); - $value = implode("\n", $value); - $value = str_replace(' ', '', $value); - - parent::setValueFromSubmit($value); - } - } - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - list($country, $area, $phone) = $this->getNumberParts(); - - if ($country || $area || $phone) { - if ($country) { - $country = "+$country"; - } - - return "$country $area $phone"; - } - - return ''; - } - - /** - * Returns the according input elements as html for this datafield - * - * @param String $name Name prefix of the associated input - * @param Array $variables Additional variables - * @return String containing the required html - */ - public function getHTML($name = '', $variables = []) - { - return parent::getHTML($name, $variables + [ - 'values' => $this->getNumberParts() - ]); - } - - /** - * Checks if the datafield is empty (was not set) - * - * @return bool true if empty, else false - */ - public function isEmpty() - { - return $this->getValue() == "\n\n"; - } - - /** - * Returns whether the datafield contents are valid - * - * @return boolean indicating whether the datafield contents are valid - */ - public function isValid() - { - $value = trim($this->value); - - if (!$value) { - return parent::isValid(); - } - - return parent::isValid() - && preg_match('/^([1-9]\d*)?\n[1-9]\d+\n[1-9]\d+(-\d+)?$/', $value); - } - - /** - * Retturns the individual parts of the telephone number. - * The resulting array is always padded to contain at least - * three items. - * - * @return array containing the individual parts. - */ - protected function getNumberParts() - { - $values = explode("\n", $this->value); - - // pad values array to a size of 3 by inserting empty values from left - while (count($values) < 3) { - array_unshift($values, ''); - } - - return $values; - } -} - diff --git a/lib/classes/DataFieldPhoneEntry.php b/lib/classes/DataFieldPhoneEntry.php new file mode 100644 index 0000000..d3a0e70 --- /dev/null +++ b/lib/classes/DataFieldPhoneEntry.php @@ -0,0 +1,122 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldPhoneEntry extends DataFieldEntry +{ + protected $template = 'phone.php'; + + /** + * Returns the number of html fields this datafield uses for input. + * + * @return int representing the number of html fields + */ + public function numberOfHTMLFields() + { + return 3; + } + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($value) + { + if (is_array($value)) { + $value = array_slice($value, 0, 3); + $value = implode("\n", $value); + $value = str_replace(' ', '', $value); + + parent::setValueFromSubmit($value); + } + } + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + list($country, $area, $phone) = $this->getNumberParts(); + + if ($country || $area || $phone) { + if ($country) { + $country = "+$country"; + } + + return "$country $area $phone"; + } + + return ''; + } + + /** + * Returns the according input elements as html for this datafield + * + * @param String $name Name prefix of the associated input + * @param Array $variables Additional variables + * @return String containing the required html + */ + public function getHTML($name = '', $variables = []) + { + return parent::getHTML($name, $variables + [ + 'values' => $this->getNumberParts() + ]); + } + + /** + * Checks if the datafield is empty (was not set) + * + * @return bool true if empty, else false + */ + public function isEmpty() + { + return $this->getValue() == "\n\n"; + } + + /** + * Returns whether the datafield contents are valid + * + * @return boolean indicating whether the datafield contents are valid + */ + public function isValid() + { + $value = trim($this->value); + + if (!$value) { + return parent::isValid(); + } + + return parent::isValid() + && preg_match('/^([1-9]\d*)?\n[1-9]\d+\n[1-9]\d+(-\d+)?$/', $value); + } + + /** + * Retturns the individual parts of the telephone number. + * The resulting array is always padded to contain at least + * three items. + * + * @return array containing the individual parts. + */ + protected function getNumberParts() + { + $values = explode("\n", $this->value); + + // pad values array to a size of 3 by inserting empty values from left + while (count($values) < 3) { + array_unshift($values, ''); + } + + return $values; + } +} + diff --git a/lib/classes/DataFieldRadioEntry.class.php b/lib/classes/DataFieldRadioEntry.class.php deleted file mode 100644 index 8699b69..0000000 --- a/lib/classes/DataFieldRadioEntry.class.php +++ /dev/null @@ -1,39 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldRadioEntry extends DataFieldSelectboxEntry -{ - protected $template = 'radio.php'; - - /** - * Returns the number of html fields this datafield uses for input. - * - * @return int representing the number of html fields - */ - public function numberOfHTMLFields() - { - return count($this->type_param); - } - - /** - * Returns the according input elements as html for this datafield - * - * @param String $name Name prefix of the associated input - * @param Array $variables Additional variables - * @return String containing the required html - */ - public function getHTML($name = '', $variables = []) - { - return parent::getHTML($name, $variables + [ - 'type_param' => $this->type_param, - 'is_assoc' => $this->is_assoc_param - ]); - } -} diff --git a/lib/classes/DataFieldRadioEntry.php b/lib/classes/DataFieldRadioEntry.php new file mode 100644 index 0000000..8699b69 --- /dev/null +++ b/lib/classes/DataFieldRadioEntry.php @@ -0,0 +1,39 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldRadioEntry extends DataFieldSelectboxEntry +{ + protected $template = 'radio.php'; + + /** + * Returns the number of html fields this datafield uses for input. + * + * @return int representing the number of html fields + */ + public function numberOfHTMLFields() + { + return count($this->type_param); + } + + /** + * Returns the according input elements as html for this datafield + * + * @param String $name Name prefix of the associated input + * @param Array $variables Additional variables + * @return String containing the required html + */ + public function getHTML($name = '', $variables = []) + { + return parent::getHTML($name, $variables + [ + 'type_param' => $this->type_param, + 'is_assoc' => $this->is_assoc_param + ]); + } +} diff --git a/lib/classes/DataFieldSelectboxEntry.class.php b/lib/classes/DataFieldSelectboxEntry.class.php deleted file mode 100644 index 8ac1a3c..0000000 --- a/lib/classes/DataFieldSelectboxEntry.class.php +++ /dev/null @@ -1,100 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldSelectboxEntry extends DataFieldEntry -{ - protected $template = 'selectbox.php'; - protected $type_param; - protected $is_assoc_param = false; - - /** - * Constructs this datafield - * - * @param DataField $datafield Underlying model - * @param String $rangeID Range id - * @param mixed $value Value - */ - public function __construct(DataField $struct = null, $range_id = '', $value = null) - { - parent::__construct($struct, $range_id, $value); - - list($values, $is_assoc) = $this->getParameters(); - $this->is_assoc_param = $is_assoc; - $this->type_param = $values; - - if ($this->getValue() === null) { - reset($values); - - if ($is_assoc) { - $this->setValue((string)key($values)); - } else { - $this->setValue(current($values)); // first selectbox entry is default - } - } - } - - /** - * Returns the according input elements as html for this datafield - * - * @param String $name Name prefix of the associated input - * @param Array $variables Additional variables - * @return String containing the required html - */ - public function getHTML($name = '', $variables = []) - { - $variables = array_merge([ - 'multiple' => false, - 'type_param' => $this->type_param, - 'is_assoc' => $this->is_assoc_param, - ], $variables); - - return parent::getHTML($name, $variables); - } - - /** - * Returns the individual type parameters. - * - * @return array containing the individual type parameters - */ - public function getParameters() - { - $params = explode("\n", rtrim($this->model->typeparam)); - $params = array_map('trim', $params); - - $ret = []; - $is_assoc = false; - - foreach ($params as $i => $p) { - if (mb_strpos($p, '=>') !== false) { - $is_assoc = true; - - list($key, $value) = array_map('trim', explode('=>', $p, 2)); - $ret[$key] = $value; - } else { - $ret[$i] = $p; - } - } - return [$ret, $is_assoc]; - } - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - $value = $this->is_assoc_param - ? $this->type_param[$this->getValue()] - : $this->getValue(); - return $entities ? htmlReady($value) : $value; - } -} diff --git a/lib/classes/DataFieldSelectboxEntry.php b/lib/classes/DataFieldSelectboxEntry.php new file mode 100644 index 0000000..8ac1a3c --- /dev/null +++ b/lib/classes/DataFieldSelectboxEntry.php @@ -0,0 +1,100 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldSelectboxEntry extends DataFieldEntry +{ + protected $template = 'selectbox.php'; + protected $type_param; + protected $is_assoc_param = false; + + /** + * Constructs this datafield + * + * @param DataField $datafield Underlying model + * @param String $rangeID Range id + * @param mixed $value Value + */ + public function __construct(DataField $struct = null, $range_id = '', $value = null) + { + parent::__construct($struct, $range_id, $value); + + list($values, $is_assoc) = $this->getParameters(); + $this->is_assoc_param = $is_assoc; + $this->type_param = $values; + + if ($this->getValue() === null) { + reset($values); + + if ($is_assoc) { + $this->setValue((string)key($values)); + } else { + $this->setValue(current($values)); // first selectbox entry is default + } + } + } + + /** + * Returns the according input elements as html for this datafield + * + * @param String $name Name prefix of the associated input + * @param Array $variables Additional variables + * @return String containing the required html + */ + public function getHTML($name = '', $variables = []) + { + $variables = array_merge([ + 'multiple' => false, + 'type_param' => $this->type_param, + 'is_assoc' => $this->is_assoc_param, + ], $variables); + + return parent::getHTML($name, $variables); + } + + /** + * Returns the individual type parameters. + * + * @return array containing the individual type parameters + */ + public function getParameters() + { + $params = explode("\n", rtrim($this->model->typeparam)); + $params = array_map('trim', $params); + + $ret = []; + $is_assoc = false; + + foreach ($params as $i => $p) { + if (mb_strpos($p, '=>') !== false) { + $is_assoc = true; + + list($key, $value) = array_map('trim', explode('=>', $p, 2)); + $ret[$key] = $value; + } else { + $ret[$i] = $p; + } + } + return [$ret, $is_assoc]; + } + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + $value = $this->is_assoc_param + ? $this->type_param[$this->getValue()] + : $this->getValue(); + return $entities ? htmlReady($value) : $value; + } +} diff --git a/lib/classes/DataFieldSelectboxMultipleEntry.class.php b/lib/classes/DataFieldSelectboxMultipleEntry.class.php deleted file mode 100644 index 3abb373..0000000 --- a/lib/classes/DataFieldSelectboxMultipleEntry.class.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldSelectboxMultipleEntry extends DataFieldSelectboxEntry -{ - const SEPARATOR = '|'; - - /** - * Constructs this datafield - * - * @param DataField $datafield Underlying model - * @param String $rangeID Range id - * @param mixed $value Value - */ - public function __construct(DataField $datafield = null, $rangeID = '', $value = null) - { - parent::__construct($datafield, $rangeID, $value); - - if ($this->getValue() === null) { - $this->setValue(''); - } - } - - /** - * Returns the according input elements as html for this datafield - * - * @param String $name Name prefix of the associated input - * @param Array $variables Additional variables - * @return String containing the required html - */ - public function getHTML($name = '', $variables = []) - { - return parent::getHTML($name, $variables + [ - 'multiple' => true, - 'value' => explode(self::SEPARATOR, $this->value) - ]); - } - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - $value = $this->getValue(); - if ($value) { - $type_param = $this->type_param; - - $mapper = 'trim'; - if ($this->is_assoc_param) { - $mapper = function ($a) use ($type_param) { - $a = trim($a); - return $type_param[$a]; - }; - } - - $value = explode(self::SEPARATOR, $value); - $value = array_map($mapper, $value); - $value = implode('; ', $value); - } - return $entities - ? htmlReady($value) - : $value; - } - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($value) - { - if (is_array($value)) { - $value = array_map('trim', $value); - $value = array_filter($value); - $value = array_unique($value); - $value = implode(self::SEPARATOR, $value); - } else { - $value = ''; - } - - parent::setValueFromSubmit($value); - } -} diff --git a/lib/classes/DataFieldSelectboxMultipleEntry.php b/lib/classes/DataFieldSelectboxMultipleEntry.php new file mode 100644 index 0000000..3abb373 --- /dev/null +++ b/lib/classes/DataFieldSelectboxMultipleEntry.php @@ -0,0 +1,93 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldSelectboxMultipleEntry extends DataFieldSelectboxEntry +{ + const SEPARATOR = '|'; + + /** + * Constructs this datafield + * + * @param DataField $datafield Underlying model + * @param String $rangeID Range id + * @param mixed $value Value + */ + public function __construct(DataField $datafield = null, $rangeID = '', $value = null) + { + parent::__construct($datafield, $rangeID, $value); + + if ($this->getValue() === null) { + $this->setValue(''); + } + } + + /** + * Returns the according input elements as html for this datafield + * + * @param String $name Name prefix of the associated input + * @param Array $variables Additional variables + * @return String containing the required html + */ + public function getHTML($name = '', $variables = []) + { + return parent::getHTML($name, $variables + [ + 'multiple' => true, + 'value' => explode(self::SEPARATOR, $this->value) + ]); + } + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + $value = $this->getValue(); + if ($value) { + $type_param = $this->type_param; + + $mapper = 'trim'; + if ($this->is_assoc_param) { + $mapper = function ($a) use ($type_param) { + $a = trim($a); + return $type_param[$a]; + }; + } + + $value = explode(self::SEPARATOR, $value); + $value = array_map($mapper, $value); + $value = implode('; ', $value); + } + return $entities + ? htmlReady($value) + : $value; + } + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($value) + { + if (is_array($value)) { + $value = array_map('trim', $value); + $value = array_filter($value); + $value = array_unique($value); + $value = implode(self::SEPARATOR, $value); + } else { + $value = ''; + } + + parent::setValueFromSubmit($value); + } +} diff --git a/lib/classes/DataFieldTextareaEntry.class.php b/lib/classes/DataFieldTextareaEntry.class.php deleted file mode 100644 index a48be98..0000000 --- a/lib/classes/DataFieldTextareaEntry.class.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldTextareaEntry extends DataFieldEntry -{ - protected $template = 'textarea.php'; - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - if ($entities) { - return htmlReady($this->getValue(), true, true); - } - - return $this->getValue(); - } -} diff --git a/lib/classes/DataFieldTextareaEntry.php b/lib/classes/DataFieldTextareaEntry.php new file mode 100644 index 0000000..a48be98 --- /dev/null +++ b/lib/classes/DataFieldTextareaEntry.php @@ -0,0 +1,29 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldTextareaEntry extends DataFieldEntry +{ + protected $template = 'textarea.php'; + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + if ($entities) { + return htmlReady($this->getValue(), true, true); + } + + return $this->getValue(); + } +} diff --git a/lib/classes/DataFieldTextareai18nEntry.class.php b/lib/classes/DataFieldTextareai18nEntry.class.php deleted file mode 100644 index 9b6db40..0000000 --- a/lib/classes/DataFieldTextareai18nEntry.class.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @copyright 2017 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.1 - * - */ -class DataFieldTextareai18nEntry extends DataFieldI18NEntry -{ - protected $template = 'textareai18n.php'; -} diff --git a/lib/classes/DataFieldTextareai18nEntry.php b/lib/classes/DataFieldTextareai18nEntry.php new file mode 100644 index 0000000..9b6db40 --- /dev/null +++ b/lib/classes/DataFieldTextareai18nEntry.php @@ -0,0 +1,21 @@ + + * @copyright 2017 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.1 + * + */ +class DataFieldTextareai18nEntry extends DataFieldI18NEntry +{ + protected $template = 'textareai18n.php'; +} diff --git a/lib/classes/DataFieldTextlineEntry.class.php b/lib/classes/DataFieldTextlineEntry.class.php deleted file mode 100644 index 6062ed1..0000000 --- a/lib/classes/DataFieldTextlineEntry.class.php +++ /dev/null @@ -1,14 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldTextlineEntry extends DataFieldEntry -{ - protected $template = 'textline.php'; -} diff --git a/lib/classes/DataFieldTextlineEntry.php b/lib/classes/DataFieldTextlineEntry.php new file mode 100644 index 0000000..6062ed1 --- /dev/null +++ b/lib/classes/DataFieldTextlineEntry.php @@ -0,0 +1,14 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldTextlineEntry extends DataFieldEntry +{ + protected $template = 'textline.php'; +} diff --git a/lib/classes/DataFieldTextlinei18nEntry.class.php b/lib/classes/DataFieldTextlinei18nEntry.class.php deleted file mode 100644 index 9d1f344..0000000 --- a/lib/classes/DataFieldTextlinei18nEntry.class.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @copyright 2017 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.1 - * - */ -class DataFieldTextlinei18nEntry extends DataFieldI18NEntry -{ - protected $template = 'textlinei18n.php'; -} diff --git a/lib/classes/DataFieldTextlinei18nEntry.php b/lib/classes/DataFieldTextlinei18nEntry.php new file mode 100644 index 0000000..9d1f344 --- /dev/null +++ b/lib/classes/DataFieldTextlinei18nEntry.php @@ -0,0 +1,21 @@ + + * @copyright 2017 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.1 + * + */ +class DataFieldTextlinei18nEntry extends DataFieldI18NEntry +{ + protected $template = 'textlinei18n.php'; +} diff --git a/lib/classes/DataFieldTextmarkupEntry.class.php b/lib/classes/DataFieldTextmarkupEntry.class.php deleted file mode 100644 index 50115b9..0000000 --- a/lib/classes/DataFieldTextmarkupEntry.class.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @license GPL2 or any later version - */ -class DataFieldTextmarkupEntry extends DataFieldTextareaEntry -{ - protected $template = 'textmarkup.php'; - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($submitted_value) - { - $this->setValue(Studip\Markup::purifyHtml($submitted_value)); - } - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - if ($entities) { - return formatReady($this->getValue()); - } - - return $this->getValue(); - } -} diff --git a/lib/classes/DataFieldTextmarkupEntry.php b/lib/classes/DataFieldTextmarkupEntry.php new file mode 100644 index 0000000..50115b9 --- /dev/null +++ b/lib/classes/DataFieldTextmarkupEntry.php @@ -0,0 +1,35 @@ + + * @license GPL2 or any later version + */ +class DataFieldTextmarkupEntry extends DataFieldTextareaEntry +{ + protected $template = 'textmarkup.php'; + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($submitted_value) + { + $this->setValue(Studip\Markup::purifyHtml($submitted_value)); + } + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + if ($entities) { + return formatReady($this->getValue()); + } + + return $this->getValue(); + } +} diff --git a/lib/classes/DataFieldTextmarkupi18nEntry.class.php b/lib/classes/DataFieldTextmarkupi18nEntry.class.php deleted file mode 100644 index 0b3d7e6..0000000 --- a/lib/classes/DataFieldTextmarkupi18nEntry.class.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @copyright 2017 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.1 - * - */ -class DataFieldTextmarkupi18nEntry extends DataFieldTextareai18nEntry -{ - protected $template = 'textmarkupi18n.php'; - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($submitted_value) - { - array_walk($submitted_value, 'Studip\Markup::purifyHtml'); - parent::setValueFromSubmit($submitted_value); - } - - /** - * Returns the display/rendered value of this datafield - * - * @param bool $entities Should html entities be encoded (defaults to true) - * @return String containg the rendered value - */ - public function getDisplayValue($entities = true) - { - if ($entities) { - return formatReady($this->getValue()); - } - - return $this->getValue(); - } -} diff --git a/lib/classes/DataFieldTextmarkupi18nEntry.php b/lib/classes/DataFieldTextmarkupi18nEntry.php new file mode 100644 index 0000000..0b3d7e6 --- /dev/null +++ b/lib/classes/DataFieldTextmarkupi18nEntry.php @@ -0,0 +1,47 @@ + + * @copyright 2017 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.1 + * + */ +class DataFieldTextmarkupi18nEntry extends DataFieldTextareai18nEntry +{ + protected $template = 'textmarkupi18n.php'; + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($submitted_value) + { + array_walk($submitted_value, 'Studip\Markup::purifyHtml'); + parent::setValueFromSubmit($submitted_value); + } + + /** + * Returns the display/rendered value of this datafield + * + * @param bool $entities Should html entities be encoded (defaults to true) + * @return String containg the rendered value + */ + public function getDisplayValue($entities = true) + { + if ($entities) { + return formatReady($this->getValue()); + } + + return $this->getValue(); + } +} diff --git a/lib/classes/DataFieldTimeEntry.class.php b/lib/classes/DataFieldTimeEntry.class.php deleted file mode 100644 index 96e5835..0000000 --- a/lib/classes/DataFieldTimeEntry.class.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @author Marcus Lunzenauer - * @author Martin Gieseking - * @license GPL2 or any later version - */ -class DataFieldTimeEntry extends DataFieldEntry -{ - protected $template = 'time.php'; - - /** - * Sets the value from a post request - * - * @param mixed $submitted_value The value from request - */ - public function setValueFromSubmit($value) - { - if ($value) { - parent::setValueFromSubmit($value); - } - } - - /** - * Checks if the datafield is empty (was not set) - * - * @return bool true if empty, else false - */ - public function isEmpty() - { - return $this->getValue() == ':'; - } - - /** - * Returns whether the datafield contents are valid - * - * @return boolean indicating whether the datafield contents are valid - */ - public function isValid() - { - $parts = explode(':', $this->value); - - return parent::isValid() - && $parts[0] >= 0 && $parts[0] <= 24 - && $parts[1] >= 0 && $parts[1] <= 59; - } -} diff --git a/lib/classes/DataFieldTimeEntry.php b/lib/classes/DataFieldTimeEntry.php new file mode 100644 index 0000000..96e5835 --- /dev/null +++ b/lib/classes/DataFieldTimeEntry.php @@ -0,0 +1,50 @@ + + * @author Marcus Lunzenauer + * @author Martin Gieseking + * @license GPL2 or any later version + */ +class DataFieldTimeEntry extends DataFieldEntry +{ + protected $template = 'time.php'; + + /** + * Sets the value from a post request + * + * @param mixed $submitted_value The value from request + */ + public function setValueFromSubmit($value) + { + if ($value) { + parent::setValueFromSubmit($value); + } + } + + /** + * Checks if the datafield is empty (was not set) + * + * @return bool true if empty, else false + */ + public function isEmpty() + { + return $this->getValue() == ':'; + } + + /** + * Returns whether the datafield contents are valid + * + * @return boolean indicating whether the datafield contents are valid + */ + public function isValid() + { + $parts = explode(':', $this->value); + + return parent::isValid() + && $parts[0] >= 0 && $parts[0] <= 24 + && $parts[1] >= 0 && $parts[1] <= 59; + } +} diff --git a/lib/classes/DatabaseObject.class.php b/lib/classes/DatabaseObject.class.php deleted file mode 100644 index 2258adb..0000000 --- a/lib/classes/DatabaseObject.class.php +++ /dev/null @@ -1,149 +0,0 @@ - -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - -# Define all required constants ============================================= # -/** - * @const INSTANCEOF_STUDIPOBJECT Is instance of a studip object - * @access public - */ -define("INSTANCEOF_DATABASEOBJECT", "DatabaseObject"); -# =========================================================================== # - - -/** - * DatabaseObject.class.php - * - * Class to provide basic properties of an DatabaseObject in Stud.IP - * - * @author Alexander Willner - * @copyright 2003 Stud.IP-Project - * @access public - * @package studip_core - * @modulegroup core - */ -class DatabaseObject extends AuthorObject -{ - public $authorID; - public $objectID; - public $rangeID; - -# Define constructor and destructor ========================================= # - /** - * Constructor - * - * @access public - */ - public function __construct() - { - /* For good OOP: Call constructor ------------------------------------- */ - parent::__construct(); - $this->instanceof = INSTANCEOF_DATABASEOBJECT; - /* -------------------------------------------------------------------- */ - } -# =========================================================================== # - - -# Define public functions =================================================== # - /** - * Gets the objectID - * - * @access public - * @return string The objectID - */ - public function getObjectID() - { - return $this->objectID; - } - - /** - * Sets the objectID - * - * @access public - * - * @param String $objectID The object ID - */ - public function setObjectID($objectID) - { - if (empty ($objectID)) - $this->throwError(1, _("Die ObjectID darf nicht leer sein.")); - else - $this->objectID = $objectID; - } - - /** - * Gets the authorID - * - * @access public - * @return string The authorID - */ - public function getAuthorID() - { - return $this->authorID; - } - - /** - * Sets the authorID - * - * @access public - * - * @param String $authorID The author ID - */ - public function setAuthorID($authorID) - { - if (empty ($authorID)) - $this->throwError(1, _("Die AuthorID darf nicht leer sein.")); - else - $this->authorID = $authorID; - } - - /** - * Gets the rangeID - * - * @access public - * @return string The rangeID - */ - public function getRangeID() - { - return $this->objectID; - } - - /** - * Sets the rangeID - * - * @access public - * - * @param String $rangeID The range ID - */ - public function setRangeID($rangeID) - { - if (empty ($rangeID)) - $this->throwError(1, _("Die RangeID darf nicht leer sein.")); - else - $this->rangeID = $rangeID; - } - -} diff --git a/lib/classes/DatabaseObject.php b/lib/classes/DatabaseObject.php new file mode 100644 index 0000000..2258adb --- /dev/null +++ b/lib/classes/DatabaseObject.php @@ -0,0 +1,149 @@ + +// +--------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +--------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +--------------------------------------------------------------------------+ + +# Define all required constants ============================================= # +/** + * @const INSTANCEOF_STUDIPOBJECT Is instance of a studip object + * @access public + */ +define("INSTANCEOF_DATABASEOBJECT", "DatabaseObject"); +# =========================================================================== # + + +/** + * DatabaseObject.class.php + * + * Class to provide basic properties of an DatabaseObject in Stud.IP + * + * @author Alexander Willner + * @copyright 2003 Stud.IP-Project + * @access public + * @package studip_core + * @modulegroup core + */ +class DatabaseObject extends AuthorObject +{ + public $authorID; + public $objectID; + public $rangeID; + +# Define constructor and destructor ========================================= # + /** + * Constructor + * + * @access public + */ + public function __construct() + { + /* For good OOP: Call constructor ------------------------------------- */ + parent::__construct(); + $this->instanceof = INSTANCEOF_DATABASEOBJECT; + /* -------------------------------------------------------------------- */ + } +# =========================================================================== # + + +# Define public functions =================================================== # + /** + * Gets the objectID + * + * @access public + * @return string The objectID + */ + public function getObjectID() + { + return $this->objectID; + } + + /** + * Sets the objectID + * + * @access public + * + * @param String $objectID The object ID + */ + public function setObjectID($objectID) + { + if (empty ($objectID)) + $this->throwError(1, _("Die ObjectID darf nicht leer sein.")); + else + $this->objectID = $objectID; + } + + /** + * Gets the authorID + * + * @access public + * @return string The authorID + */ + public function getAuthorID() + { + return $this->authorID; + } + + /** + * Sets the authorID + * + * @access public + * + * @param String $authorID The author ID + */ + public function setAuthorID($authorID) + { + if (empty ($authorID)) + $this->throwError(1, _("Die AuthorID darf nicht leer sein.")); + else + $this->authorID = $authorID; + } + + /** + * Gets the rangeID + * + * @access public + * @return string The rangeID + */ + public function getRangeID() + { + return $this->objectID; + } + + /** + * Sets the rangeID + * + * @access public + * + * @param String $rangeID The range ID + */ + public function setRangeID($rangeID) + { + if (empty ($rangeID)) + $this->throwError(1, _("Die RangeID darf nicht leer sein.")); + else + $this->rangeID = $rangeID; + } + +} diff --git a/lib/classes/DateFormatter.class.php b/lib/classes/DateFormatter.class.php deleted file mode 100644 index 5f7c558..0000000 --- a/lib/classes/DateFormatter.class.php +++ /dev/null @@ -1,176 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * Formates one SingleDate object or a series of SingleDate objects into a nice format. - */ -class DateFormatter { - /** - * @var array holds the dates use for formatting - */ - private $dates; - - /** - * @var string holds the return-type, may be int or string - */ - private $return_mode; - - /** - * @param $dates an array with an array of SingleDate objects. - * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. - * @return void - */ - private function __construct($dates, $return_mode = 'string') - { - $this->dates = $dates; - $this->return_mode = $return_mode; - } - - /** - * Formats one single date into a nice format. - * @static - * @param $date SingleDate object - * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. - * @return string - */ - public static function formatDateAndRoom($date, $return_mode = 'string') - { - $dates = DateFormatter::wrapDateWithArray($date); - return DateFormatter::formatDateWithAllRooms($dates, $return_mode); - } - - /** - * Formats a series of SingleDate objects into a nice format. The dates parameter is an array of dates. - * The array has to have the key 'termin' with an array of SingleDate objects as value. - * @static - * @param $dates an array with an array of SingleDate objects - * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. - * @return string - */ - public static function formatDateWithAllRooms($dates, $return_mode = 'string') - { - $dateFormatter = new DateFormatter($dates, $return_mode); - return $dateFormatter->internalFormatDateWithAllRooms(); - } - - private static function wrapDateWithArray($date) - { - $dates = []; - $dates['termin'] = [$date]; - return $dates; - } - - private function internalFormatDateWithAllRooms() - { - $dateWithRooms = ''; - if ($this->dates['termin']) { - // if we have multiple rooms at the same time we display them all - foreach ($this->dates['termin'] as $num => $termin_id) { - $date = new SingleDate($termin_id); - - // if we want an int and format the date ourself - if ($this->return_mode == 'int') { - return $date->getStartTime(); - } - - $isFirstDate = ($num == 0); - if ($isFirstDate) { - $dateWithRooms = $this->internalFormatDateAndRoom($date); - } else { - $dateWithRooms .= ', ' . $this->formatRoom($date); - } - } - } - return $dateWithRooms; - } - - private function internalFormatDateAndRoom($date) - { - $ret = $this->formatDate($date); - - if ($this->return_mode != 'int') { - $formatedRooms = $this->formatRoom($date); - if ($formatedRooms) { - $ret .= ', '; - $ret .= _("Ort:") . ' '; - $ret .= $formatedRooms; - } - } - - return $ret; - } - - private function formatDate($date) - { - if ($this->return_mode == 'int') { - return $date->getStartTime(); - } - else { - return $date->toString(); - } - } - - private function formatRoom($date) - { - if ($this->return_mode == 'int') { - return ''; - } - else { - return $this->formatLocationText($date); - } - } - - private function formatLocationText($date) - { - if ($this->hasResource($date)) { - $resObj = Resource::find($date->getResourceID()); - return $this->generateLocationTextFromResourceObject($resObj); - } else if ($this->hasFreeRoomText($date)) { - return $this->generateLocationTextFromFreeRoomText($date); - } else { - return ''; - } - } - - private function generateLocationTextFromResourceObject($resObj) - { - if ($resObj) { - if ($this->return_mode == 'string') { - return '' - . htmlReady($resObj->name) - . ''; - } - else { - return htmlReady($resObj->name); - } - } - return ''; - } - - private function generateLocationTextFromFreeRoomText($date) - { - return '(' . htmlReady($date->getFreeRoomText()) . ')'; - } - - private function hasResource($date) - { - return $date && $date->getResourceID(); - } - - private function hasFreeRoomText($date) - { - return $date && $date->getFreeRoomText(); - } -} diff --git a/lib/classes/DateFormatter.php b/lib/classes/DateFormatter.php new file mode 100644 index 0000000..5f7c558 --- /dev/null +++ b/lib/classes/DateFormatter.php @@ -0,0 +1,176 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * Formates one SingleDate object or a series of SingleDate objects into a nice format. + */ +class DateFormatter { + /** + * @var array holds the dates use for formatting + */ + private $dates; + + /** + * @var string holds the return-type, may be int or string + */ + private $return_mode; + + /** + * @param $dates an array with an array of SingleDate objects. + * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. + * @return void + */ + private function __construct($dates, $return_mode = 'string') + { + $this->dates = $dates; + $this->return_mode = $return_mode; + } + + /** + * Formats one single date into a nice format. + * @static + * @param $date SingleDate object + * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. + * @return string + */ + public static function formatDateAndRoom($date, $return_mode = 'string') + { + $dates = DateFormatter::wrapDateWithArray($date); + return DateFormatter::formatDateWithAllRooms($dates, $return_mode); + } + + /** + * Formats a series of SingleDate objects into a nice format. The dates parameter is an array of dates. + * The array has to have the key 'termin' with an array of SingleDate objects as value. + * @static + * @param $dates an array with an array of SingleDate objects + * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. + * @return string + */ + public static function formatDateWithAllRooms($dates, $return_mode = 'string') + { + $dateFormatter = new DateFormatter($dates, $return_mode); + return $dateFormatter->internalFormatDateWithAllRooms(); + } + + private static function wrapDateWithArray($date) + { + $dates = []; + $dates['termin'] = [$date]; + return $dates; + } + + private function internalFormatDateWithAllRooms() + { + $dateWithRooms = ''; + if ($this->dates['termin']) { + // if we have multiple rooms at the same time we display them all + foreach ($this->dates['termin'] as $num => $termin_id) { + $date = new SingleDate($termin_id); + + // if we want an int and format the date ourself + if ($this->return_mode == 'int') { + return $date->getStartTime(); + } + + $isFirstDate = ($num == 0); + if ($isFirstDate) { + $dateWithRooms = $this->internalFormatDateAndRoom($date); + } else { + $dateWithRooms .= ', ' . $this->formatRoom($date); + } + } + } + return $dateWithRooms; + } + + private function internalFormatDateAndRoom($date) + { + $ret = $this->formatDate($date); + + if ($this->return_mode != 'int') { + $formatedRooms = $this->formatRoom($date); + if ($formatedRooms) { + $ret .= ', '; + $ret .= _("Ort:") . ' '; + $ret .= $formatedRooms; + } + } + + return $ret; + } + + private function formatDate($date) + { + if ($this->return_mode == 'int') { + return $date->getStartTime(); + } + else { + return $date->toString(); + } + } + + private function formatRoom($date) + { + if ($this->return_mode == 'int') { + return ''; + } + else { + return $this->formatLocationText($date); + } + } + + private function formatLocationText($date) + { + if ($this->hasResource($date)) { + $resObj = Resource::find($date->getResourceID()); + return $this->generateLocationTextFromResourceObject($resObj); + } else if ($this->hasFreeRoomText($date)) { + return $this->generateLocationTextFromFreeRoomText($date); + } else { + return ''; + } + } + + private function generateLocationTextFromResourceObject($resObj) + { + if ($resObj) { + if ($this->return_mode == 'string') { + return '' + . htmlReady($resObj->name) + . ''; + } + else { + return htmlReady($resObj->name); + } + } + return ''; + } + + private function generateLocationTextFromFreeRoomText($date) + { + return '(' . htmlReady($date->getFreeRoomText()) . ')'; + } + + private function hasResource($date) + { + return $date && $date->getResourceID(); + } + + private function hasFreeRoomText($date) + { + return $date && $date->getFreeRoomText(); + } +} diff --git a/lib/classes/DbSnapshot.class.php b/lib/classes/DbSnapshot.class.php deleted file mode 100644 index dfb4f89..0000000 --- a/lib/classes/DbSnapshot.class.php +++ /dev/null @@ -1,370 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - - -/** - * Class to provide snapshots of mysql result sets - * - * Uses DB abstraction layer of PHPLib - * - * @access public - * @author André Noack - * @package DBTools - **/ -class DbSnapshot -{ - - /** - * the used db abstraction class - * - * - * @access private - * @var string $DbClass - */ - var $DbClass = "DB_Sql"; - /** - * the used db result set - * - * - * @access private - * @var object DB_Sql $dbResult - */ - var $dbResult = null; - /** - * array to store the result set - * - * - * @access private - * @var array $result - */ - var $result = []; - /** - * array to store metadata oh the result set - * - * - * @access private - * @var array $metaData - */ - var $metaData = []; - /** - * the number of fields in the result set - * - * - * @access public - * @var integer $numFields - */ - var $numFields = 0; - /** - * the number of rows in the result set - * - * - * @access public - * @var integer $numRows - */ - var $numRows = 0; - /** - * the internal row pointer - * - * - * @access private - * @var mixed $pos - */ - var $pos = false; - /** - * turn on/off debugging - * - * - * @access public - * @var boolean $debug - */ - var $debug = false; - - /** - * Constructor - * - * Pass instance of DbClass or nothing to specify result set later - * - * @access public - * - * @param object DB_Sql $dbresult - */ - public function __construct($dbresult = null) - { - if (is_object($dbresult)) { - $this->dbResult = $dbresult; - $this->getSnapshot(); - } - - } - - function isDbResult() - { - if (!is_subclass_of($this->dbResult, $this->DbClass)) - $this->halt("Result set has wrong type!"); - if (!$this->dbResult->query_id()) - $this->halt("No result set (missing query?)"); - - return true; - } - - public function getSnapshot() - { - if ($this->isDbResult()) { - $this->numFields = $this->dbResult->num_fields(); - $this->numRows = $this->dbResult->num_rows(); - $this->metaData = $this->dbResult->metadata(); - $this->result = []; - while ($this->dbResult->next_record()) { - $this->result[] = $this->dbResult->Record; - } - unset($this->dbResult); - $this->pos = false; - - return true; - } - - return false; - } - - public function nextRow() - { - if (!$this->numRows) - $this->halt("No snapshot available or empty result!"); - if ($this->pos === false) { - $this->pos = 0; - - return true; - } - if (++$this->pos < $this->numRows) - return true; - else - return false; - } - - public function resetPos() - { - $this->pos = false; - } - - public function isField($name) - { - for ($i = 0; $i < $this->numFields; ++$i) { - if ($name == $this->metaData[$i]['name']) { - return true; - } - } - - return false; - } - - public function getRow($row = false) - { - if (!$row === false AND !$this->result[$row]) - $this->halt("Snapshot has only " . ($this->numRows - 1) . " rows!"); - - return ($row === false) ? $this->result[$this->pos] : $this->result[$row]; - } - - public function getFieldList() - { - if (!$this->numRows) - $this->halt("No snapshot available or empty result!"); - $ret = []; - for ($i = 0; $i < $this->numFields; ++$i) { - $ret[] = $this->metaData[$i]['name']; - } - - return $ret; - } - - public function getField($field = 0) - { - if (!$this->numRows) - $this->halt("No snapshot available or empty result!"); - - return ($this->pos === false) ? false : $this->result[$this->pos][$field]; - } - - public function getRows($fieldname = 0) - { - if (!$this->numRows) - $this->halt("No snapshot available or empty result!"); - $ret = []; - for ($i = 0; $i < $this->numRows; ++$i) { - $ret[] = $this->result[$i][$fieldname]; - } - - return $ret; - } - - public function getDistinctRows($fieldname) - { - if (!$this->isField($fieldname)) - $this->halt("Field: $fieldname not found in result set!"); - $ret = []; - for ($i = 0; $i < $this->numRows; ++$i) { - $ret[$this->result[$i][$fieldname]] = $this->result[$i]; - $ret[$this->result[$i][$fieldname]]['row'] = $i; - } - - return $ret; - } - - public function sortRows($fieldname = 0, $order = "ASC", $stype = false) - { - if (!$this->numRows) - $this->halt("No snapshot available or empty result!"); - $sortfields = $this->getRows($fieldname); - if ($stype !== false) { - $sortfunc = ($order == "ASC") ? "asort" : "arsort"; - $sortfunc($sortfields, $stype); - } else { - uasort($sortfields, function ($a,$b) { - $a = mb_strtolower($a); - $a = str_replace('ä', 'ae', $a); - $a = str_replace('ö', 'oe', $a); - $a = str_replace('ü', 'ue', $a); - - $b = mb_strtolower($b); - $b = str_replace('ä', 'ae', $b); - $b = str_replace('ö', 'oe', $b); - $b = str_replace('ü', 'ue', $b); - - return strnatcasecmp($a, $b); - }); - if ($order == "DESC") { - $sortfields = array_reverse($sortfields, true); - } - } - $sortresult = []; - foreach ($sortfields as $key => $value) { - $sortresult[] = $this->result[$key]; - } - $this->result = $sortresult; - $this->resetPos(); - - return true; - } - - public function searchFields($fieldname, $searchstr) - { - if (!$this->numRows) - $this->halt("No snapshot available or empty result!"); - $ret = false; - $sortfields = $this->getRows($fieldname); - foreach ($sortfields as $key => $value) { - if (preg_match($searchstr, $value)) { - $ret = true; - $this->pos = $key; - break; - } - } - - return $ret; - } - - public function getGroupedResult($group_by_field, $fields_to_group = null) - { - if (!$this->numRows) - $this->halt("No snapshot available or empty result!"); - $fieldlist = $this->getFieldList(); - if (!in_array($group_by_field, $fieldlist)) - $this->halt("group_by_field not found in result set!"); - if (is_array($fields_to_group)) - $fieldlist = $fields_to_group; - $num_fields = count($fieldlist); - $ret = []; - for ($i = 0; $i < $this->numRows; ++$i) { - for ($j = 0; $j < $num_fields; ++$j) { - if ($fieldlist[$j] != $group_by_field) { - if (empty($ret[$this->result[$i][$group_by_field]][$fieldlist[$j]][$this->result[$i][$fieldlist[$j]]])) { - $ret[$this->result[$i][$group_by_field]][$fieldlist[$j]][$this->result[$i][$fieldlist[$j]]] = 0; - } - ++$ret[$this->result[$i][$group_by_field]][$fieldlist[$j]][$this->result[$i][$fieldlist[$j]]]; - } - } - } - - return $ret; - } - - public function mergeSnapshot($m_snap, $key_field = false, $mode = "ADD") - { - if ($mode == "ADD") { - for ($i = 0; $i < $m_snap->numRows; ++$i) { - $this->result[] = $m_snap->result[$i]; - } - } elseif ($mode == "AND") { - if (!$this->numRows || !$m_snap->numRows) { - $this->result = []; - } elseif ($m_snap->numRows) { - $m_result = $m_snap->getDistinctRows($key_field); - for ($i = 0; $i < $this->numRows; ++$i) { - if (!($m_result[$this->result[$i][$key_field]] && $this->result[$i][$key_field])) { - unset($this->result[$i]); - } - } - } - } elseif ($mode == "OR") { - if (!$this->numRows) { - $this->result = $m_snap->result; - } elseif ($m_snap->numRows) { - $result = $this->getDistinctRows($key_field); - for ($i = 0; $i < $m_snap->numRows; ++$i) { - if (empty($result[$m_snap->result[$i][$key_field]])) { - $this->result[] = $m_snap->result[$i]; - } - } - } - } - $this->result = array_merge([], (array)$this->result); - $this->numRows = count($this->result); - $this->resetPos(); - - return $this->numRows; - } - - /** - * print error message and exit script - * - * @access private - * - * @param string $msg the message to print - */ - public function halt($msg) - { - echo "
$msg
"; - if ($this->debug) { - echo "
";
-            print_r($this);
-            echo "
"; - die; - } - - } -} - -?> diff --git a/lib/classes/DbSnapshot.php b/lib/classes/DbSnapshot.php new file mode 100644 index 0000000..dfb4f89 --- /dev/null +++ b/lib/classes/DbSnapshot.php @@ -0,0 +1,370 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + + +/** + * Class to provide snapshots of mysql result sets + * + * Uses DB abstraction layer of PHPLib + * + * @access public + * @author André Noack + * @package DBTools + **/ +class DbSnapshot +{ + + /** + * the used db abstraction class + * + * + * @access private + * @var string $DbClass + */ + var $DbClass = "DB_Sql"; + /** + * the used db result set + * + * + * @access private + * @var object DB_Sql $dbResult + */ + var $dbResult = null; + /** + * array to store the result set + * + * + * @access private + * @var array $result + */ + var $result = []; + /** + * array to store metadata oh the result set + * + * + * @access private + * @var array $metaData + */ + var $metaData = []; + /** + * the number of fields in the result set + * + * + * @access public + * @var integer $numFields + */ + var $numFields = 0; + /** + * the number of rows in the result set + * + * + * @access public + * @var integer $numRows + */ + var $numRows = 0; + /** + * the internal row pointer + * + * + * @access private + * @var mixed $pos + */ + var $pos = false; + /** + * turn on/off debugging + * + * + * @access public + * @var boolean $debug + */ + var $debug = false; + + /** + * Constructor + * + * Pass instance of DbClass or nothing to specify result set later + * + * @access public + * + * @param object DB_Sql $dbresult + */ + public function __construct($dbresult = null) + { + if (is_object($dbresult)) { + $this->dbResult = $dbresult; + $this->getSnapshot(); + } + + } + + function isDbResult() + { + if (!is_subclass_of($this->dbResult, $this->DbClass)) + $this->halt("Result set has wrong type!"); + if (!$this->dbResult->query_id()) + $this->halt("No result set (missing query?)"); + + return true; + } + + public function getSnapshot() + { + if ($this->isDbResult()) { + $this->numFields = $this->dbResult->num_fields(); + $this->numRows = $this->dbResult->num_rows(); + $this->metaData = $this->dbResult->metadata(); + $this->result = []; + while ($this->dbResult->next_record()) { + $this->result[] = $this->dbResult->Record; + } + unset($this->dbResult); + $this->pos = false; + + return true; + } + + return false; + } + + public function nextRow() + { + if (!$this->numRows) + $this->halt("No snapshot available or empty result!"); + if ($this->pos === false) { + $this->pos = 0; + + return true; + } + if (++$this->pos < $this->numRows) + return true; + else + return false; + } + + public function resetPos() + { + $this->pos = false; + } + + public function isField($name) + { + for ($i = 0; $i < $this->numFields; ++$i) { + if ($name == $this->metaData[$i]['name']) { + return true; + } + } + + return false; + } + + public function getRow($row = false) + { + if (!$row === false AND !$this->result[$row]) + $this->halt("Snapshot has only " . ($this->numRows - 1) . " rows!"); + + return ($row === false) ? $this->result[$this->pos] : $this->result[$row]; + } + + public function getFieldList() + { + if (!$this->numRows) + $this->halt("No snapshot available or empty result!"); + $ret = []; + for ($i = 0; $i < $this->numFields; ++$i) { + $ret[] = $this->metaData[$i]['name']; + } + + return $ret; + } + + public function getField($field = 0) + { + if (!$this->numRows) + $this->halt("No snapshot available or empty result!"); + + return ($this->pos === false) ? false : $this->result[$this->pos][$field]; + } + + public function getRows($fieldname = 0) + { + if (!$this->numRows) + $this->halt("No snapshot available or empty result!"); + $ret = []; + for ($i = 0; $i < $this->numRows; ++$i) { + $ret[] = $this->result[$i][$fieldname]; + } + + return $ret; + } + + public function getDistinctRows($fieldname) + { + if (!$this->isField($fieldname)) + $this->halt("Field: $fieldname not found in result set!"); + $ret = []; + for ($i = 0; $i < $this->numRows; ++$i) { + $ret[$this->result[$i][$fieldname]] = $this->result[$i]; + $ret[$this->result[$i][$fieldname]]['row'] = $i; + } + + return $ret; + } + + public function sortRows($fieldname = 0, $order = "ASC", $stype = false) + { + if (!$this->numRows) + $this->halt("No snapshot available or empty result!"); + $sortfields = $this->getRows($fieldname); + if ($stype !== false) { + $sortfunc = ($order == "ASC") ? "asort" : "arsort"; + $sortfunc($sortfields, $stype); + } else { + uasort($sortfields, function ($a,$b) { + $a = mb_strtolower($a); + $a = str_replace('ä', 'ae', $a); + $a = str_replace('ö', 'oe', $a); + $a = str_replace('ü', 'ue', $a); + + $b = mb_strtolower($b); + $b = str_replace('ä', 'ae', $b); + $b = str_replace('ö', 'oe', $b); + $b = str_replace('ü', 'ue', $b); + + return strnatcasecmp($a, $b); + }); + if ($order == "DESC") { + $sortfields = array_reverse($sortfields, true); + } + } + $sortresult = []; + foreach ($sortfields as $key => $value) { + $sortresult[] = $this->result[$key]; + } + $this->result = $sortresult; + $this->resetPos(); + + return true; + } + + public function searchFields($fieldname, $searchstr) + { + if (!$this->numRows) + $this->halt("No snapshot available or empty result!"); + $ret = false; + $sortfields = $this->getRows($fieldname); + foreach ($sortfields as $key => $value) { + if (preg_match($searchstr, $value)) { + $ret = true; + $this->pos = $key; + break; + } + } + + return $ret; + } + + public function getGroupedResult($group_by_field, $fields_to_group = null) + { + if (!$this->numRows) + $this->halt("No snapshot available or empty result!"); + $fieldlist = $this->getFieldList(); + if (!in_array($group_by_field, $fieldlist)) + $this->halt("group_by_field not found in result set!"); + if (is_array($fields_to_group)) + $fieldlist = $fields_to_group; + $num_fields = count($fieldlist); + $ret = []; + for ($i = 0; $i < $this->numRows; ++$i) { + for ($j = 0; $j < $num_fields; ++$j) { + if ($fieldlist[$j] != $group_by_field) { + if (empty($ret[$this->result[$i][$group_by_field]][$fieldlist[$j]][$this->result[$i][$fieldlist[$j]]])) { + $ret[$this->result[$i][$group_by_field]][$fieldlist[$j]][$this->result[$i][$fieldlist[$j]]] = 0; + } + ++$ret[$this->result[$i][$group_by_field]][$fieldlist[$j]][$this->result[$i][$fieldlist[$j]]]; + } + } + } + + return $ret; + } + + public function mergeSnapshot($m_snap, $key_field = false, $mode = "ADD") + { + if ($mode == "ADD") { + for ($i = 0; $i < $m_snap->numRows; ++$i) { + $this->result[] = $m_snap->result[$i]; + } + } elseif ($mode == "AND") { + if (!$this->numRows || !$m_snap->numRows) { + $this->result = []; + } elseif ($m_snap->numRows) { + $m_result = $m_snap->getDistinctRows($key_field); + for ($i = 0; $i < $this->numRows; ++$i) { + if (!($m_result[$this->result[$i][$key_field]] && $this->result[$i][$key_field])) { + unset($this->result[$i]); + } + } + } + } elseif ($mode == "OR") { + if (!$this->numRows) { + $this->result = $m_snap->result; + } elseif ($m_snap->numRows) { + $result = $this->getDistinctRows($key_field); + for ($i = 0; $i < $m_snap->numRows; ++$i) { + if (empty($result[$m_snap->result[$i][$key_field]])) { + $this->result[] = $m_snap->result[$i]; + } + } + } + } + $this->result = array_merge([], (array)$this->result); + $this->numRows = count($this->result); + $this->resetPos(); + + return $this->numRows; + } + + /** + * print error message and exit script + * + * @access private + * + * @param string $msg the message to print + */ + public function halt($msg) + { + echo "
$msg
"; + if ($this->debug) { + echo "
";
+            print_r($this);
+            echo "
"; + die; + } + + } +} + +?> diff --git a/lib/classes/DbView.class.php b/lib/classes/DbView.class.php deleted file mode 100644 index 81e9b91..0000000 --- a/lib/classes/DbView.class.php +++ /dev/null @@ -1,378 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - - -/** - * Class to provide simple Views and Prepared Statements - * - * Only tested with MySql, needs MySql >= 3.23 - * Uses DB abstraction layer of PHPLib - * - * @access public - * @author André Noack - * @package DBTools - */ -class DbView -{ - /** - * the processed list of queries - * - * - * @access private - * @var array $query_list - */ - private $query_list = []; - /** - * list of parameters - * - * - * @access public - * @var array $params - */ - public $params = []; - - /** - * Database Object - * - * - * @access private - * @var object $db - */ - private $db; - /** - * Database Object Type - * - * Use your subclass of db_mysql here, or pass existing object to constuctor - * - * @access private - * @var string $db_class_name - * @see DbView() - */ - private $db_class_name = "DB_Seminar"; - /** - * Temp Table Type - * - * MyISAM is always safe, HEAP may provide better performance - * - * @access private - * @var string $temp_table_type - */ - private $temp_table_type = "MyISAM"; - /** - * Primary Key used in Temp Table - * - * If none is set in your view, an auto_increment row is used - * - * @access private - * @var string $pk - * @see get_temp_table() - */ - private $pk = ""; - /** - * delete the params array after each query execution - * - * - * @access public - * @var boolean $auto_free_params - */ - public $auto_free_params = true; - /** - * turn on/off debugging - * - * - * @access public - * @var boolean $debug - */ - public $debug = false; - - static protected $dbviewfiles = []; - - static protected $dbviews = []; - - public static function addView($view) - { - $view = mb_strtolower($view); - if (!isset(self::$dbviewfiles[$view])) { - self::$dbviewfiles[$view] = 0; - } - } - - /** - * Convenience method that combines addView() and returns an instance. - * - * @param String $view Required view (at least this will be present in the - * returned instance) - * @param mixed $db classname of db abstraction or existing db object - * - * @return DbView Instance of self with at least the required view loaded - */ - public static function getView($view, $db = '') - { - self::addView($view); - - return new self($db); - } - - /** - * Constructor - * - * Pass nothing to use a new instance of db_class_name, the classname for a new instance, or existing instance - * - * @access public - * - * @param mixed $db classname of used db abstraction or existing db object - */ - public function __construct($db = "") - { - if (is_object($db)) { - $this->db = $db; - } else if ($db != "") { - $this->db = new $db; - $this->db_class_name = $db; - } else { - $this->db = new $this->db_class_name; - } - $this->init_views(); - } - - public function init_views() - { - foreach (self::$dbviewfiles as $view => $status) { - if ($status === 0) { - $views = include 'lib/dbviews/' . $view . '.view.php'; - self::$dbviews += $views; - - self::$dbviewfiles[$view] = 1; - } - } - } - - public function __get($view) - { - if (isset(self::$dbviews[$view])) { - return self::$dbviews[$view]; - } else { - return null; - } - } - - /** - * print error message and exit script - * - * @access private - * - * @param string $msg the message to print - */ - public function halt($msg) - { - echo "
$msg
"; - if ($this->debug) { - echo "
";
-            print_r($this);
-            echo "
"; - } - die; - } - - public function get_query() - { - $parsed_query = $this->get_parsed_query(func_get_args()); - $this->db->query($parsed_query); - return $this->db; - } - - public function get_parsed_query($query_list) - { - $parsed_query = ""; - $this->query_list = []; - (is_array($query_list)) ? $this->query_list = $query_list : $this->query_list[] = $query_list; - if (count($this->query_list) == 1) { - $spl = explode(":", $this->query_list[0]); - if ($spl[0] == "view") { - $this->query_list = $this->get_view(trim($spl[1])); - } - } - $this->parse_query($this->query_list); - if (is_array($this->query_list)) { - $parsed_query = $this->query_list[0]; - } else { - $parsed_query = $this->query_list; - } - - return $parsed_query; - } - - - public function parse_query(&$query) - { - if (is_array($query)) { - for ($i = (count($query) - 1); $i > 0; --$i) { - $spl = explode(":", $query[$i]); - if ($spl[0] == "view") { - $query[$i] = $this->get_view(trim($spl[1]), $spl[2]); - } - $query[$i] = $this->parse_query($query[$i]); - $repl_query = (is_array($query[$i])) ? $query[$i][0] : $query[$i]; - for ($j = 0; $j < $i; ++$j) { - $spl = mb_stristr($query[$j], "where"); - if (!$spl) - $spl = mb_stristr($query[$j], "having"); - if ($spl) { - $pos = mb_strpos($spl, "{" . $i . "}"); - if (!$pos === false) - $repl_query = $this->get_temp_values($repl_query); - } - if (!$spl OR $pos === false) { - $pos = mb_strpos($query[$j], "{" . $i . "}"); - if (!$pos === false) - $repl_query = $this->get_temp_table($repl_query); - } - $query[$j] = str_replace("{" . $i . "}", $repl_query, $query[$j]); - } - } - } - - return $query; - } - - - public function get_temp_table($sub_query) - { - $id = self::get_uniqid(); - $pk = $this->pk ? "PRIMARY KEY($this->pk)" : "auto_" . $id . " INT NOT NULL AUTO_INCREMENT PRIMARY KEY"; - $query = "CREATE TEMPORARY TABLE temp_$id ($pk) ENGINE=$this->temp_table_type $sub_query"; - $this->db->query($query); - - return " temp_" . $id . " "; - } - - - public function get_temp_values($sub_query) - { - $this->db->query($sub_query); - if (!$this->db->num_rows()) - $this->halt("Sub Query: $sub_query returns nothing!"); - else { - while ($this->db->next_record()) { - $result[] = $this->db->Record[0]; - } - $value_list = $this->get_value_list($result); - } - - return $value_list; - } - - public static function get_uniqid() - { - mt_srand((double)microtime() * 1000000); - - return md5(uniqid(mt_rand(), 1)); - } - - public function get_value_list($list) - { - $value_list = false; - if (count($list) == 1) - $value_list = "'$list[0]'"; - else - $value_list = "'" . join("','", $list) . "'"; - - return $value_list; - } - - public function get_view($name) - { - if (!empty(self::$dbviews[$name]["pk"])) { - $this->pk = self::$dbviews[$name]["pk"]; - } - if (!empty(self::$dbviews[$name]["temp_table_type"])) { - $this->temp_table_type = self::$dbviews[$name]["temp_table_type"]; - } - if (!$query_list = self::$dbviews[$name]["query"]) - $this->halt("View not found: $name"); - (is_array($query_list)) ? $query = $query_list[0] : $query = $query_list; - $tokens = preg_split("/[\?§\&]/u", $query); - if (count($tokens) > 1) { - $types = []; - $token = 0; - foreach (preg_split('//u', $query, null, PREG_SPLIT_NO_EMPTY) as $i => $c) { - switch ($c) { - case '?': - $types[$token++] = 1; - break; - case '§': - $types[$token++] = 2; - break; - case '&': - $types[$token++] = 3; - break; - } - } - if (count($this->params) != count($types)) - $this->halt("Wrong parameter count in view: $name"); - $query = ""; - for ($i = 0; $i < count($this->params); ++$i) { - $query .= $tokens[$i]; - if (is_null($this->params[$i])) { - $query .= 'NULL'; - } else { - switch ($types[$i]) { - case 1: - $query .= "'" . $this->params[$i] . "'"; - break; - case 2: - $query .= $this->params[$i]; - break; - case 3: - $query .= (is_array($this->params[$i])) ? "'" . join("','", $this->params[$i]) . "'" : "'" . $this->params[$i] . "'"; - break; - } - } - } - $query .= $tokens[$i]; - if ($this->auto_free_params) - $this->params = []; - } - (is_array($query_list)) ? $query_list[0] = $query : $query_list = $query; - return $query_list; - } - - public function Get_union() - { - $queries = func_get_args(); - $view = new DbView(); - $union_table = $view->get_temp_table($view->get_parsed_query($queries[0])); - if ($queries[1]) { - for ($i = 1; $i < count($queries); ++$i) { - $view->db->query("REPLACE INTO $union_table " . $view->get_parsed_query($queries[$i])); - } - } - - return $union_table; - } -} - -?> diff --git a/lib/classes/DbView.php b/lib/classes/DbView.php new file mode 100644 index 0000000..81e9b91 --- /dev/null +++ b/lib/classes/DbView.php @@ -0,0 +1,378 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + + +/** + * Class to provide simple Views and Prepared Statements + * + * Only tested with MySql, needs MySql >= 3.23 + * Uses DB abstraction layer of PHPLib + * + * @access public + * @author André Noack + * @package DBTools + */ +class DbView +{ + /** + * the processed list of queries + * + * + * @access private + * @var array $query_list + */ + private $query_list = []; + /** + * list of parameters + * + * + * @access public + * @var array $params + */ + public $params = []; + + /** + * Database Object + * + * + * @access private + * @var object $db + */ + private $db; + /** + * Database Object Type + * + * Use your subclass of db_mysql here, or pass existing object to constuctor + * + * @access private + * @var string $db_class_name + * @see DbView() + */ + private $db_class_name = "DB_Seminar"; + /** + * Temp Table Type + * + * MyISAM is always safe, HEAP may provide better performance + * + * @access private + * @var string $temp_table_type + */ + private $temp_table_type = "MyISAM"; + /** + * Primary Key used in Temp Table + * + * If none is set in your view, an auto_increment row is used + * + * @access private + * @var string $pk + * @see get_temp_table() + */ + private $pk = ""; + /** + * delete the params array after each query execution + * + * + * @access public + * @var boolean $auto_free_params + */ + public $auto_free_params = true; + /** + * turn on/off debugging + * + * + * @access public + * @var boolean $debug + */ + public $debug = false; + + static protected $dbviewfiles = []; + + static protected $dbviews = []; + + public static function addView($view) + { + $view = mb_strtolower($view); + if (!isset(self::$dbviewfiles[$view])) { + self::$dbviewfiles[$view] = 0; + } + } + + /** + * Convenience method that combines addView() and returns an instance. + * + * @param String $view Required view (at least this will be present in the + * returned instance) + * @param mixed $db classname of db abstraction or existing db object + * + * @return DbView Instance of self with at least the required view loaded + */ + public static function getView($view, $db = '') + { + self::addView($view); + + return new self($db); + } + + /** + * Constructor + * + * Pass nothing to use a new instance of db_class_name, the classname for a new instance, or existing instance + * + * @access public + * + * @param mixed $db classname of used db abstraction or existing db object + */ + public function __construct($db = "") + { + if (is_object($db)) { + $this->db = $db; + } else if ($db != "") { + $this->db = new $db; + $this->db_class_name = $db; + } else { + $this->db = new $this->db_class_name; + } + $this->init_views(); + } + + public function init_views() + { + foreach (self::$dbviewfiles as $view => $status) { + if ($status === 0) { + $views = include 'lib/dbviews/' . $view . '.view.php'; + self::$dbviews += $views; + + self::$dbviewfiles[$view] = 1; + } + } + } + + public function __get($view) + { + if (isset(self::$dbviews[$view])) { + return self::$dbviews[$view]; + } else { + return null; + } + } + + /** + * print error message and exit script + * + * @access private + * + * @param string $msg the message to print + */ + public function halt($msg) + { + echo "
$msg
"; + if ($this->debug) { + echo "
";
+            print_r($this);
+            echo "
"; + } + die; + } + + public function get_query() + { + $parsed_query = $this->get_parsed_query(func_get_args()); + $this->db->query($parsed_query); + return $this->db; + } + + public function get_parsed_query($query_list) + { + $parsed_query = ""; + $this->query_list = []; + (is_array($query_list)) ? $this->query_list = $query_list : $this->query_list[] = $query_list; + if (count($this->query_list) == 1) { + $spl = explode(":", $this->query_list[0]); + if ($spl[0] == "view") { + $this->query_list = $this->get_view(trim($spl[1])); + } + } + $this->parse_query($this->query_list); + if (is_array($this->query_list)) { + $parsed_query = $this->query_list[0]; + } else { + $parsed_query = $this->query_list; + } + + return $parsed_query; + } + + + public function parse_query(&$query) + { + if (is_array($query)) { + for ($i = (count($query) - 1); $i > 0; --$i) { + $spl = explode(":", $query[$i]); + if ($spl[0] == "view") { + $query[$i] = $this->get_view(trim($spl[1]), $spl[2]); + } + $query[$i] = $this->parse_query($query[$i]); + $repl_query = (is_array($query[$i])) ? $query[$i][0] : $query[$i]; + for ($j = 0; $j < $i; ++$j) { + $spl = mb_stristr($query[$j], "where"); + if (!$spl) + $spl = mb_stristr($query[$j], "having"); + if ($spl) { + $pos = mb_strpos($spl, "{" . $i . "}"); + if (!$pos === false) + $repl_query = $this->get_temp_values($repl_query); + } + if (!$spl OR $pos === false) { + $pos = mb_strpos($query[$j], "{" . $i . "}"); + if (!$pos === false) + $repl_query = $this->get_temp_table($repl_query); + } + $query[$j] = str_replace("{" . $i . "}", $repl_query, $query[$j]); + } + } + } + + return $query; + } + + + public function get_temp_table($sub_query) + { + $id = self::get_uniqid(); + $pk = $this->pk ? "PRIMARY KEY($this->pk)" : "auto_" . $id . " INT NOT NULL AUTO_INCREMENT PRIMARY KEY"; + $query = "CREATE TEMPORARY TABLE temp_$id ($pk) ENGINE=$this->temp_table_type $sub_query"; + $this->db->query($query); + + return " temp_" . $id . " "; + } + + + public function get_temp_values($sub_query) + { + $this->db->query($sub_query); + if (!$this->db->num_rows()) + $this->halt("Sub Query: $sub_query returns nothing!"); + else { + while ($this->db->next_record()) { + $result[] = $this->db->Record[0]; + } + $value_list = $this->get_value_list($result); + } + + return $value_list; + } + + public static function get_uniqid() + { + mt_srand((double)microtime() * 1000000); + + return md5(uniqid(mt_rand(), 1)); + } + + public function get_value_list($list) + { + $value_list = false; + if (count($list) == 1) + $value_list = "'$list[0]'"; + else + $value_list = "'" . join("','", $list) . "'"; + + return $value_list; + } + + public function get_view($name) + { + if (!empty(self::$dbviews[$name]["pk"])) { + $this->pk = self::$dbviews[$name]["pk"]; + } + if (!empty(self::$dbviews[$name]["temp_table_type"])) { + $this->temp_table_type = self::$dbviews[$name]["temp_table_type"]; + } + if (!$query_list = self::$dbviews[$name]["query"]) + $this->halt("View not found: $name"); + (is_array($query_list)) ? $query = $query_list[0] : $query = $query_list; + $tokens = preg_split("/[\?§\&]/u", $query); + if (count($tokens) > 1) { + $types = []; + $token = 0; + foreach (preg_split('//u', $query, null, PREG_SPLIT_NO_EMPTY) as $i => $c) { + switch ($c) { + case '?': + $types[$token++] = 1; + break; + case '§': + $types[$token++] = 2; + break; + case '&': + $types[$token++] = 3; + break; + } + } + if (count($this->params) != count($types)) + $this->halt("Wrong parameter count in view: $name"); + $query = ""; + for ($i = 0; $i < count($this->params); ++$i) { + $query .= $tokens[$i]; + if (is_null($this->params[$i])) { + $query .= 'NULL'; + } else { + switch ($types[$i]) { + case 1: + $query .= "'" . $this->params[$i] . "'"; + break; + case 2: + $query .= $this->params[$i]; + break; + case 3: + $query .= (is_array($this->params[$i])) ? "'" . join("','", $this->params[$i]) . "'" : "'" . $this->params[$i] . "'"; + break; + } + } + } + $query .= $tokens[$i]; + if ($this->auto_free_params) + $this->params = []; + } + (is_array($query_list)) ? $query_list[0] = $query : $query_list = $query; + return $query_list; + } + + public function Get_union() + { + $queries = func_get_args(); + $view = new DbView(); + $union_table = $view->get_temp_table($view->get_parsed_query($queries[0])); + if ($queries[1]) { + for ($i = 1; $i < count($queries); ++$i) { + $view->db->query("REPLACE INTO $union_table " . $view->get_parsed_query($queries[$i])); + } + } + + return $union_table; + } +} + +?> diff --git a/lib/classes/Event.interface.php b/lib/classes/Event.interface.php deleted file mode 100644 index 23f092b..0000000 --- a/lib/classes/Event.interface.php +++ /dev/null @@ -1,184 +0,0 @@ - - */ -class Feedback -{ - /** - * Returns the html code for feedback elements for a given range, if the module is activated within a course - * - * @return string - */ - public static function getHTML(string $range_id, string $range_type) - { - if (!$range_id) { - return null; - } - $course_id = null; - if (is_subclass_of($range_type, \FeedbackRange::class)) { - $range_object = $range_type::find($range_id); - if ($range_object) { - $course_id = $range_object->getRangeCourseId(); - } - } - if ($course_id && Feedback::isActivated($course_id) && Feedback::hasRangeAccess($range_id, $range_type)) { - return ''; - } else { - return null; - } - } - /** - * Returns activation status of the feedback module in currently active course - * - * @param string $course_id optional; use this course_id instead of the current context - * - * @return boolean - */ - public static function isActivated(string $course_id = null): bool - { - $course_id = $course_id ?? Context::getId(); - $plugin_manager = PluginManager::getInstance(); - $feedback_module = $plugin_manager->getPluginInfo('FeedbackModule'); - - return $plugin_manager->isPluginActivated($feedback_module['id'], $course_id) ?? false; - } - - /** - * Returns admin permission of current user within given course - * - * @param string $course_id the course - * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id - * - * @return boolean - * - * @SuppressWarnings(PHPMD.Superglobals) - */ - public static function hasAdminPerm($course_id, string $user_id = null): bool - { - $user_id = $user_id ?? $GLOBALS['user']->id; - $admin_perm_level = CourseConfig::get($course_id)->FEEDBACK_ADMIN_PERM; - $admin_perm = $GLOBALS['perm']->have_studip_perm($admin_perm_level, $course_id, $user_id); - - return $admin_perm; - } - - /** - * Returns create permission of current user within given course - * - * @param string $course_id the course - * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id - * - * @return boolean - * - * @SuppressWarnings(PHPMD.Superglobals) - */ - public static function hasCreatePerm($course_id, string $user_id = null): bool - { - $user_id = $user_id ?? $GLOBALS['user']->id; - $create_perm_level = CourseConfig::get($course_id)->FEEDBACK_CREATE_PERM; - $create_perm = $GLOBALS['perm']->have_studip_perm($create_perm_level, $course_id, $user_id); - - return $create_perm; - } - - /** - * Returns range access permission of current user for given range - * - * @param string $range_id - * @param string $range_type - * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id - * - * @return boolean - */ - public static function hasRangeAccess($range_id, $range_type, string $user_id = null): bool - { - $user_id = $user_id ?? $GLOBALS['user']->id; - $range = $range_type::find($range_id); - return $range->isRangeAccessible($user_id); - } - -} diff --git a/lib/classes/Feedback.php b/lib/classes/Feedback.php new file mode 100644 index 0000000..26dbf55 --- /dev/null +++ b/lib/classes/Feedback.php @@ -0,0 +1,101 @@ + + */ +class Feedback +{ + /** + * Returns the html code for feedback elements for a given range, if the module is activated within a course + * + * @return string + */ + public static function getHTML(string $range_id, string $range_type) + { + if (!$range_id) { + return null; + } + $course_id = null; + if (is_subclass_of($range_type, \FeedbackRange::class)) { + $range_object = $range_type::find($range_id); + if ($range_object) { + $course_id = $range_object->getRangeCourseId(); + } + } + if ($course_id && Feedback::isActivated($course_id) && Feedback::hasRangeAccess($range_id, $range_type)) { + return ''; + } else { + return null; + } + } + /** + * Returns activation status of the feedback module in currently active course + * + * @param string $course_id optional; use this course_id instead of the current context + * + * @return boolean + */ + public static function isActivated(string $course_id = null): bool + { + $course_id = $course_id ?? Context::getId(); + $plugin_manager = PluginManager::getInstance(); + $feedback_module = $plugin_manager->getPluginInfo('FeedbackModule'); + + return $plugin_manager->isPluginActivated($feedback_module['id'], $course_id) ?? false; + } + + /** + * Returns admin permission of current user within given course + * + * @param string $course_id the course + * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id + * + * @return boolean + * + * @SuppressWarnings(PHPMD.Superglobals) + */ + public static function hasAdminPerm($course_id, string $user_id = null): bool + { + $user_id = $user_id ?? $GLOBALS['user']->id; + $admin_perm_level = CourseConfig::get($course_id)->FEEDBACK_ADMIN_PERM; + $admin_perm = $GLOBALS['perm']->have_studip_perm($admin_perm_level, $course_id, $user_id); + + return $admin_perm; + } + + /** + * Returns create permission of current user within given course + * + * @param string $course_id the course + * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id + * + * @return boolean + * + * @SuppressWarnings(PHPMD.Superglobals) + */ + public static function hasCreatePerm($course_id, string $user_id = null): bool + { + $user_id = $user_id ?? $GLOBALS['user']->id; + $create_perm_level = CourseConfig::get($course_id)->FEEDBACK_CREATE_PERM; + $create_perm = $GLOBALS['perm']->have_studip_perm($create_perm_level, $course_id, $user_id); + + return $create_perm; + } + + /** + * Returns range access permission of current user for given range + * + * @param string $range_id + * @param string $range_type + * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id + * + * @return boolean + */ + public static function hasRangeAccess($range_id, $range_type, string $user_id = null): bool + { + $user_id = $user_id ?? $GLOBALS['user']->id; + $range = $range_type::find($range_id); + return $range->isRangeAccessible($user_id); + } + +} diff --git a/lib/classes/FeedbackRange.interface.php b/lib/classes/FeedbackRange.interface.php deleted file mode 100644 index 863c197..0000000 --- a/lib/classes/FeedbackRange.interface.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ - -interface FeedbackRange -{ - /** - * Returns the ID of this range. - * - * @return string|integer The ID of the range. - */ - public function getId(); - - /** - * Returns a human-friendly representation of the FeedbackRange object instance's name. - * - * @return string A human-friendly name for the FeedbackRange object instance. - */ - public function getRangeName(); - - /** - * Returns the icon object that shall be used with the FeedbackRange object instance. - * - * @param string $role role of icon - * @return Icon icon for the FeedbackRange object instance. - */ - public function getRangeIcon($role); - - /** - * Returns the URL of FeedbackRange view, where the object instance is visible - * together with the related feedback element(s). - * @return string Path that is usable with the url_for and link_for methods. - */ - public function getRangeUrl(); - - /** - * Returns the course id of FeedbackRange object instance - * @return string course_id - */ - public function getRangeCourseId(); - - /** - * Returns the accessebility of FeedbackRange object instance for current active user - * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id - * @return bool range object accessebility - */ - public function isRangeAccessible(string $user_id = null): bool; -} diff --git a/lib/classes/FeedbackRange.php b/lib/classes/FeedbackRange.php new file mode 100644 index 0000000..863c197 --- /dev/null +++ b/lib/classes/FeedbackRange.php @@ -0,0 +1,55 @@ + + */ + +interface FeedbackRange +{ + /** + * Returns the ID of this range. + * + * @return string|integer The ID of the range. + */ + public function getId(); + + /** + * Returns a human-friendly representation of the FeedbackRange object instance's name. + * + * @return string A human-friendly name for the FeedbackRange object instance. + */ + public function getRangeName(); + + /** + * Returns the icon object that shall be used with the FeedbackRange object instance. + * + * @param string $role role of icon + * @return Icon icon for the FeedbackRange object instance. + */ + public function getRangeIcon($role); + + /** + * Returns the URL of FeedbackRange view, where the object instance is visible + * together with the related feedback element(s). + * @return string Path that is usable with the url_for and link_for methods. + */ + public function getRangeUrl(); + + /** + * Returns the course id of FeedbackRange object instance + * @return string course_id + */ + public function getRangeCourseId(); + + /** + * Returns the accessebility of FeedbackRange object instance for current active user + * @param string $user_id optional; use this ID instead of $GLOBALS['user']->id + * @return bool range object accessebility + */ + public function isRangeAccessible(string $user_id = null): bool; +} diff --git a/lib/classes/FileLock.class.php b/lib/classes/FileLock.class.php deleted file mode 100644 index 064d41e..0000000 --- a/lib/classes/FileLock.class.php +++ /dev/null @@ -1,74 +0,0 @@ - - * @copyright 2013 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.4 - */ - -class FileLock -{ - protected $file; - - /** - * Constructs a new lock object with the provided id. - * - * @param String $id Identifier of the lock - */ - public function __construct($id) - { - $this->file = fopen("{$GLOBALS['TMP_PATH']}/$id.json", 'c+'); - - if (!$this->file) { - throw new RuntimeException('failed to create lock file.'); - } - } - - /** - * Try to aquire a file lock. The provided lock information will - * be stored with the lock. If the lock cannot be aquired, the - * lock information in $data is updated from the lock file. - * - * @param array $data additional data to be stored with the lock - * @return boolean true on success or false on failure - */ - public function tryLock(&$data = []) - { - rewind($this->file); - - if (flock($this->file, LOCK_EX | LOCK_NB)) { - ftruncate($this->file, 0); - fwrite($this->file, json_encode($data)); - fflush($this->file); - - return true; - } else { - $json = stream_get_contents($this->file); - $data = json_decode($json, true); - - return false; - } - } - - /** - * Releases a previously obtained lock - * - * @return boolean true on success or false on failure - */ - public function release() - { - return flock($this->file, LOCK_UN); - } -} diff --git a/lib/classes/FileLock.php b/lib/classes/FileLock.php new file mode 100644 index 0000000..064d41e --- /dev/null +++ b/lib/classes/FileLock.php @@ -0,0 +1,74 @@ + + * @copyright 2013 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.4 + */ + +class FileLock +{ + protected $file; + + /** + * Constructs a new lock object with the provided id. + * + * @param String $id Identifier of the lock + */ + public function __construct($id) + { + $this->file = fopen("{$GLOBALS['TMP_PATH']}/$id.json", 'c+'); + + if (!$this->file) { + throw new RuntimeException('failed to create lock file.'); + } + } + + /** + * Try to aquire a file lock. The provided lock information will + * be stored with the lock. If the lock cannot be aquired, the + * lock information in $data is updated from the lock file. + * + * @param array $data additional data to be stored with the lock + * @return boolean true on success or false on failure + */ + public function tryLock(&$data = []) + { + rewind($this->file); + + if (flock($this->file, LOCK_EX | LOCK_NB)) { + ftruncate($this->file, 0); + fwrite($this->file, json_encode($data)); + fflush($this->file); + + return true; + } else { + $json = stream_get_contents($this->file); + $data = json_decode($json, true); + + return false; + } + } + + /** + * Releases a previously obtained lock + * + * @return boolean true on success or false on failure + */ + public function release() + { + return flock($this->file, LOCK_UN); + } +} diff --git a/lib/classes/Fullcalendar.class.php b/lib/classes/Fullcalendar.class.php deleted file mode 100644 index db8364a..0000000 --- a/lib/classes/Fullcalendar.class.php +++ /dev/null @@ -1,109 +0,0 @@ -render(); - } - - - public function __construct( - $title = '', - $config = [], - $attributes = [], - $data_name = 'fullcalendar' - ) - { - $this->title = $title; - $this->config = $config; - $this->attributes = $attributes; - $this->data_name = $data_name; - } - - - public function render() - { - $factory = new \Flexi\Factory($GLOBALS['STUDIP_BASE_PATH'] . '/templates'); - $template = $factory->open('studip-fullcalendar.php'); - $real_data_name = sprintf('data-%s', $this->data_name); - return $template->render( - [ - 'title' => $this->title, - 'config' => $this->config, - 'attributes' => array_merge( - $this->attributes, - [$real_data_name => '1'] - ) - ] - ); - } - - - /** - * Creates an array with data for a Fullcalendar instance - * from Stud.IP objects that implement the EventSource interface. - */ - public static function createData($objects = [], $begin = null, $end = null) - { - if (!count($objects)) { - //No data means there is nothing to do. - return []; - } - - $data = []; - - foreach ($objects as $object) { - if ($object instanceof \Studip\Calendar\EventSource) { - $events = $object->getFilteredEventData( - $GLOBALS['user']->id, null, null, $begin, $end - ); - - foreach ($events as $event) { - $data[] = $event->toFullcalendarEvent(); - } - } - } - return $data; - } -} diff --git a/lib/classes/Fullcalendar.php b/lib/classes/Fullcalendar.php new file mode 100644 index 0000000..db8364a --- /dev/null +++ b/lib/classes/Fullcalendar.php @@ -0,0 +1,109 @@ +render(); + } + + + public function __construct( + $title = '', + $config = [], + $attributes = [], + $data_name = 'fullcalendar' + ) + { + $this->title = $title; + $this->config = $config; + $this->attributes = $attributes; + $this->data_name = $data_name; + } + + + public function render() + { + $factory = new \Flexi\Factory($GLOBALS['STUDIP_BASE_PATH'] . '/templates'); + $template = $factory->open('studip-fullcalendar.php'); + $real_data_name = sprintf('data-%s', $this->data_name); + return $template->render( + [ + 'title' => $this->title, + 'config' => $this->config, + 'attributes' => array_merge( + $this->attributes, + [$real_data_name => '1'] + ) + ] + ); + } + + + /** + * Creates an array with data for a Fullcalendar instance + * from Stud.IP objects that implement the EventSource interface. + */ + public static function createData($objects = [], $begin = null, $end = null) + { + if (!count($objects)) { + //No data means there is nothing to do. + return []; + } + + $data = []; + + foreach ($objects as $object) { + if ($object instanceof \Studip\Calendar\EventSource) { + $events = $object->getFilteredEventData( + $GLOBALS['user']->id, null, null, $begin, $end + ); + + foreach ($events as $event) { + $data[] = $event->toFullcalendarEvent(); + } + } + } + return $data; + } +} diff --git a/lib/classes/Icon.class.php b/lib/classes/Icon.class.php deleted file mode 100644 index 6c586a0..0000000 --- a/lib/classes/Icon.class.php +++ /dev/null @@ -1,395 +0,0 @@ - - * @copyright Stud.IP Core Group - * @license GPL2 or any later version - * @since 3.2 - */ -class Icon -{ - const SVG = 1; - const CSS_BACKGROUND = 4; - const INPUT = 256; - - const DEFAULT_SIZE = 16; - const DEFAULT_COLOR = 'blue'; - const DEFAULT_ROLE = 'clickable'; - - const ROLE_INFO = 'info'; - const ROLE_CLICKABLE = 'clickable'; - const ROLE_ACCEPT = 'accept'; - const ROLE_STATUS_GREEN = 'status-green'; - const ROLE_INACTIVE = 'inactive'; - const ROLE_NAVIGATION = 'navigation'; - const ROLE_NEW = 'new'; - const ROLE_ATTENTION = 'attention'; - const ROLE_STATUS_RED = 'status-red'; - const ROLE_INFO_ALT = 'info_alt'; - const ROLE_SORT = 'sort'; - const ROLE_STATUS_YELLOW = 'status-yellow'; - - - protected $shape; - protected $role; - protected $attributes = []; - - - /** - * This is the magical Role to Color mapping. - */ - private static $roles_to_colors = [ - self::ROLE_INFO => 'black', - self::ROLE_CLICKABLE => 'blue', - self::ROLE_ACCEPT => 'green', - self::ROLE_STATUS_GREEN => 'green', - self::ROLE_INACTIVE => 'grey', - self::ROLE_NAVIGATION => 'blue', - self::ROLE_NEW => 'red', - self::ROLE_ATTENTION => 'red', - self::ROLE_STATUS_RED => 'red', - self::ROLE_INFO_ALT => 'white', - self::ROLE_SORT => 'blue', - self::ROLE_STATUS_YELLOW => 'yellow' - ]; - - // return the color associated to a role - private static function roleToColor($role) - { - if (!isset(self::$roles_to_colors[$role])) { - throw new \InvalidArgumentException('Unknown role: "' . $role . '"'); - } - return self::$roles_to_colors[$role]; - } - - // return the roles! associated to a color - public static function colorToRoles($color) - { - static $colors_to_roles; - - if (!$colors_to_roles) { - foreach (self::$roles_to_colors as $r => $c) { - $colors_to_roles[$c][] = $r; - } - } - - if (!isset($colors_to_roles[$color])) { - throw new \InvalidArgumentException('Unknown color: "' . $color . '"'); - } - - return $colors_to_roles[$color]; - } - - /** - * Create a new Icon object. - * - * This is just a factory method. You could easily just call the - * constructor instead. - * - * @param String $shape Shape of the icon, may contain a mixed definition - * like 'seminar' - * @param String $role Role of the icon, defaults to Icon::DEFAULT_ROLE - * @param Array $attributes Additional attributes like 'title'; - * only use semantic ones describing - * this icon regardless of its later - * rendering in a view - * @return Icon object - */ - public static function create($shape, $role = Icon::DEFAULT_ROLE, $attributes = []) - { - // $role may be omitted - if (is_array($role)) { - $attributes = $role; - $role = Icon::DEFAULT_ROLE; - } - - return new self($shape, $role, $attributes); - } - - /** - * Constructor of the object. - * - * @param String $shape Shape of the icon, may contain a mixed definition - * like 'seminar' - * @param String $role Role of the icon, defaults to Icon::DEFAULT_ROLE - * @param Array $attributes Additional attributes like 'title'; - * only use semantic ones describing - * this icon regardless of its later - * rendering in a view - */ - public function __construct($shape, $role = Icon::DEFAULT_ROLE, array $attributes = []) - { - - // only defined roles - if (!isset(self::$roles_to_colors[$role])) { - throw new \InvalidArgumentException('Creating an Icon without proper role: "' . $role . '"'); - } - - // only semantic attributes - if ($non_semantic = array_filter(array_keys($attributes), function ($attr) { - return !in_array($attr, ['title']); - })) { - // DEPRECATED - // TODO starting with the v3.6 the following line should - // be enabled to prevent non-semantic attributes in this position - # throw new \InvalidArgumentException('Creating an Icon with non-semantic attributes:' . json_encode($non_semantic)); - } - - $this->shape = $shape; - $this->role = $role; - $this->attributes = $attributes; - } - - /** - * Returns the `shape` -- the string describing the shape of this instance. - * @return String the shape of this Icon - */ - public function getShape() - { - return $this->shapeToPath($this->shape); - } - - /** - * Returns the `role` -- the string describing the role of this instance. - * @return String the role of this Icon - */ - public function getRole() - { - return $this->role; - } - - /** - * Returns the semantic `attributes` of this instance, e.g. the title of this Icon - * @return Array the semantic attribiutes of the Icon - */ - public function getAttributes() - { - return $this->attributes; - } - - /** - * Returns whether this icon intends to signal attention. - * - * @todo This is currently just a heuristic based on the associated icon - * role. Although this is sufficient for the current requirements, - * it could probably in a better, more suitable way. - * - * @return bool - * @since Stud.IP 5.0 - */ - public function signalsAttention() - { - return $this->roleToColor($this->role) === 'red'; - } - - /** - * Function to be called whenever the object is converted to - * string. Internally the same as calling Icon::asImg - * - * @return String representation - */ - public function __toString() - { - return $this->asImg(); - } - - /** - * Renders the icon inside an img html tag. - * - * @param int $size Optional; Defines the dimension in px of the rendered icon; FALSE prevents any - * width or height attributes - * @param Array $view_attributes Optional; Additional attributes to pass - * into the rendered output - * @return String containing the html representation for the icon. - */ - public function asImg($size = null, $view_attributes = []) - { - if (is_array($size)) { - list($view_attributes, $size) = [$size, null]; - } - return sprintf( - '', - arrayToHtmlAttributes( - $this->prepareHTMLAttributes($size, $view_attributes) - ) - ); - } - - /** - * Renders the icon inside an input html tag. - * - * @param int $size Optional; Defines the dimension in px of the rendered icon; FALSE prevents any - * width or height attributes - * @param Array $view_attributes Optional; Additional attributes to pass - * into the rendered output - * @return String containing the html representation for the icon. - */ - public function asInput($size = null, $view_attributes = []) - { - if (is_array($size)) { - list($view_attributes, $size) = [$size, null]; - } - return sprintf( - '', - arrayToHtmlAttributes( - $this->prepareHTMLAttributes($size, $view_attributes) - ) - ); - } - - /** - * Renders the icon as a set of css background rules. - * - * @param int $size Optional; Defines the size in px of the rendered icon - * @return String containing the html representation for css backgrounds - */ - public function asCSS($size = null) - { - if (self::isStatic($this->shape)) { - return sprintf( - 'background-image:url(%1$s);background-size:%2$upx %2$upx;', - $this->shapeToPath($this->shape), - $this->get_size($size) - ); - } - - return sprintf( - 'background-image:url(%1$s);background-size:%2$upx %2$upx;', - $this->get_asset_svg(), - $this->get_size($size) - ); - } - - /** - * Returns a path to the SVG matching the icon. - * - * @return String containing the html representation for css backgrounds - */ - public function asImagePath() - { - return $this->prepareHTMLAttributes(false, [])['src']; - } - - /** - * Returns a new Icon with a changed shape - * @param mixed $shape New value of `shape` - * @return Icon A new Icon with a new `shape` - */ - public function copyWithShape($shape) - { - $clone = clone $this; - $clone->shape = $shape; - return $clone; - } - - /** - * Returns a new Icon with a changed role - * @param mixed $role New value of `role` - * @return Icon A new Icon with a new `role` - */ - public function copyWithRole($role) - { - $clone = clone $this; - $clone->role = $role; - return $clone; - } - - /** - * Returns a new Icon with new attributes - * @param mixed $attributes New value of `attributes` - * @return Icon A new Icon with a new `attributes` - */ - public function copyWithAttributes($attributes) - { - $clone = clone $this; - $clone->attributes = $attributes; - return $clone; - } - - /** - * Prepares the html attributes for use assembling HTML attributes - * from given shape, role, size, semantic and view attributes - * - * @param int $size Size of the icon - * @param array $attributes Additional attributes - * @return Array containing the merged attributes - */ - private function prepareHTMLAttributes($size, $attributes) - { - $dimensions = []; - if ($size !== false) { - $size = $this->get_size($size); - $dimensions = ['width' => $size, 'height' => $size]; - } - - $result = array_merge($this->attributes, $attributes, $dimensions, [ - 'src' => self::isStatic($this->shape) ? $this->shape : $this->get_asset_svg(), - ]); - - if (!isset($result['alt']) && !isset($result['title'])) { - //Add an empty alt attribute to prevent screen readers from - //reading the URL of the icon: - $result['alt'] = ''; - } - - $classNames = 'icon-role-' . $this->role; - - if (!self::isStatic($this->shape)) { - $classNames .= ' icon-shape-' . $this->shapeToPath($this->shape); - } - - $result['class'] = isset($result['class']) ? $result['class'] . ' ' . $classNames : $classNames; - - return $result; - } - - /** - * Get the correct asset for an SVG icon. - * - * @return String containing the url of the corresponding asset - */ - protected function get_asset_svg() - { - return Assets::url('images/icons/' . self::roleToColor($this->role) . '/' . $this->shapeToPath($this->shape) . '.svg'); - } - - /** - * Get the size of the icon. If a size was passed as a parameter and - * inside the attributes array during icon construction, the size from - * the attributes will be used. - * - * @param int $size size of the icon - * @return int Size of the icon in pixels - */ - protected function get_size($size) - { - $size = $size ?: Icon::DEFAULT_SIZE; - if (isset($this->attributes['size'])) { - $parts = explode('@', $this->attributes['size'], 2); - $size = $parts[0]; - $temp = $parts[1] ?? null; - unset($this->attributes['size']); - } - return (int)$size; - } - - // an icon is static if it starts with 'http' - private static function isStatic($shape) - { - return mb_strpos($shape, 'http') === 0; - } - - // transforms a shape w/ possible additions (`shape`) to a path `(addition/)?shape` - private function shapeToPath() - { - if (self::isStatic($this->shape)) { - return $this->shape; - } - $shape = array_reverse(explode('/', $this->shape))[0]; - $shape = explode('+', $shape)[0]; - return $shape; - } -} diff --git a/lib/classes/Icon.php b/lib/classes/Icon.php new file mode 100644 index 0000000..6c586a0 --- /dev/null +++ b/lib/classes/Icon.php @@ -0,0 +1,395 @@ + + * @copyright Stud.IP Core Group + * @license GPL2 or any later version + * @since 3.2 + */ +class Icon +{ + const SVG = 1; + const CSS_BACKGROUND = 4; + const INPUT = 256; + + const DEFAULT_SIZE = 16; + const DEFAULT_COLOR = 'blue'; + const DEFAULT_ROLE = 'clickable'; + + const ROLE_INFO = 'info'; + const ROLE_CLICKABLE = 'clickable'; + const ROLE_ACCEPT = 'accept'; + const ROLE_STATUS_GREEN = 'status-green'; + const ROLE_INACTIVE = 'inactive'; + const ROLE_NAVIGATION = 'navigation'; + const ROLE_NEW = 'new'; + const ROLE_ATTENTION = 'attention'; + const ROLE_STATUS_RED = 'status-red'; + const ROLE_INFO_ALT = 'info_alt'; + const ROLE_SORT = 'sort'; + const ROLE_STATUS_YELLOW = 'status-yellow'; + + + protected $shape; + protected $role; + protected $attributes = []; + + + /** + * This is the magical Role to Color mapping. + */ + private static $roles_to_colors = [ + self::ROLE_INFO => 'black', + self::ROLE_CLICKABLE => 'blue', + self::ROLE_ACCEPT => 'green', + self::ROLE_STATUS_GREEN => 'green', + self::ROLE_INACTIVE => 'grey', + self::ROLE_NAVIGATION => 'blue', + self::ROLE_NEW => 'red', + self::ROLE_ATTENTION => 'red', + self::ROLE_STATUS_RED => 'red', + self::ROLE_INFO_ALT => 'white', + self::ROLE_SORT => 'blue', + self::ROLE_STATUS_YELLOW => 'yellow' + ]; + + // return the color associated to a role + private static function roleToColor($role) + { + if (!isset(self::$roles_to_colors[$role])) { + throw new \InvalidArgumentException('Unknown role: "' . $role . '"'); + } + return self::$roles_to_colors[$role]; + } + + // return the roles! associated to a color + public static function colorToRoles($color) + { + static $colors_to_roles; + + if (!$colors_to_roles) { + foreach (self::$roles_to_colors as $r => $c) { + $colors_to_roles[$c][] = $r; + } + } + + if (!isset($colors_to_roles[$color])) { + throw new \InvalidArgumentException('Unknown color: "' . $color . '"'); + } + + return $colors_to_roles[$color]; + } + + /** + * Create a new Icon object. + * + * This is just a factory method. You could easily just call the + * constructor instead. + * + * @param String $shape Shape of the icon, may contain a mixed definition + * like 'seminar' + * @param String $role Role of the icon, defaults to Icon::DEFAULT_ROLE + * @param Array $attributes Additional attributes like 'title'; + * only use semantic ones describing + * this icon regardless of its later + * rendering in a view + * @return Icon object + */ + public static function create($shape, $role = Icon::DEFAULT_ROLE, $attributes = []) + { + // $role may be omitted + if (is_array($role)) { + $attributes = $role; + $role = Icon::DEFAULT_ROLE; + } + + return new self($shape, $role, $attributes); + } + + /** + * Constructor of the object. + * + * @param String $shape Shape of the icon, may contain a mixed definition + * like 'seminar' + * @param String $role Role of the icon, defaults to Icon::DEFAULT_ROLE + * @param Array $attributes Additional attributes like 'title'; + * only use semantic ones describing + * this icon regardless of its later + * rendering in a view + */ + public function __construct($shape, $role = Icon::DEFAULT_ROLE, array $attributes = []) + { + + // only defined roles + if (!isset(self::$roles_to_colors[$role])) { + throw new \InvalidArgumentException('Creating an Icon without proper role: "' . $role . '"'); + } + + // only semantic attributes + if ($non_semantic = array_filter(array_keys($attributes), function ($attr) { + return !in_array($attr, ['title']); + })) { + // DEPRECATED + // TODO starting with the v3.6 the following line should + // be enabled to prevent non-semantic attributes in this position + # throw new \InvalidArgumentException('Creating an Icon with non-semantic attributes:' . json_encode($non_semantic)); + } + + $this->shape = $shape; + $this->role = $role; + $this->attributes = $attributes; + } + + /** + * Returns the `shape` -- the string describing the shape of this instance. + * @return String the shape of this Icon + */ + public function getShape() + { + return $this->shapeToPath($this->shape); + } + + /** + * Returns the `role` -- the string describing the role of this instance. + * @return String the role of this Icon + */ + public function getRole() + { + return $this->role; + } + + /** + * Returns the semantic `attributes` of this instance, e.g. the title of this Icon + * @return Array the semantic attribiutes of the Icon + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Returns whether this icon intends to signal attention. + * + * @todo This is currently just a heuristic based on the associated icon + * role. Although this is sufficient for the current requirements, + * it could probably in a better, more suitable way. + * + * @return bool + * @since Stud.IP 5.0 + */ + public function signalsAttention() + { + return $this->roleToColor($this->role) === 'red'; + } + + /** + * Function to be called whenever the object is converted to + * string. Internally the same as calling Icon::asImg + * + * @return String representation + */ + public function __toString() + { + return $this->asImg(); + } + + /** + * Renders the icon inside an img html tag. + * + * @param int $size Optional; Defines the dimension in px of the rendered icon; FALSE prevents any + * width or height attributes + * @param Array $view_attributes Optional; Additional attributes to pass + * into the rendered output + * @return String containing the html representation for the icon. + */ + public function asImg($size = null, $view_attributes = []) + { + if (is_array($size)) { + list($view_attributes, $size) = [$size, null]; + } + return sprintf( + '', + arrayToHtmlAttributes( + $this->prepareHTMLAttributes($size, $view_attributes) + ) + ); + } + + /** + * Renders the icon inside an input html tag. + * + * @param int $size Optional; Defines the dimension in px of the rendered icon; FALSE prevents any + * width or height attributes + * @param Array $view_attributes Optional; Additional attributes to pass + * into the rendered output + * @return String containing the html representation for the icon. + */ + public function asInput($size = null, $view_attributes = []) + { + if (is_array($size)) { + list($view_attributes, $size) = [$size, null]; + } + return sprintf( + '', + arrayToHtmlAttributes( + $this->prepareHTMLAttributes($size, $view_attributes) + ) + ); + } + + /** + * Renders the icon as a set of css background rules. + * + * @param int $size Optional; Defines the size in px of the rendered icon + * @return String containing the html representation for css backgrounds + */ + public function asCSS($size = null) + { + if (self::isStatic($this->shape)) { + return sprintf( + 'background-image:url(%1$s);background-size:%2$upx %2$upx;', + $this->shapeToPath($this->shape), + $this->get_size($size) + ); + } + + return sprintf( + 'background-image:url(%1$s);background-size:%2$upx %2$upx;', + $this->get_asset_svg(), + $this->get_size($size) + ); + } + + /** + * Returns a path to the SVG matching the icon. + * + * @return String containing the html representation for css backgrounds + */ + public function asImagePath() + { + return $this->prepareHTMLAttributes(false, [])['src']; + } + + /** + * Returns a new Icon with a changed shape + * @param mixed $shape New value of `shape` + * @return Icon A new Icon with a new `shape` + */ + public function copyWithShape($shape) + { + $clone = clone $this; + $clone->shape = $shape; + return $clone; + } + + /** + * Returns a new Icon with a changed role + * @param mixed $role New value of `role` + * @return Icon A new Icon with a new `role` + */ + public function copyWithRole($role) + { + $clone = clone $this; + $clone->role = $role; + return $clone; + } + + /** + * Returns a new Icon with new attributes + * @param mixed $attributes New value of `attributes` + * @return Icon A new Icon with a new `attributes` + */ + public function copyWithAttributes($attributes) + { + $clone = clone $this; + $clone->attributes = $attributes; + return $clone; + } + + /** + * Prepares the html attributes for use assembling HTML attributes + * from given shape, role, size, semantic and view attributes + * + * @param int $size Size of the icon + * @param array $attributes Additional attributes + * @return Array containing the merged attributes + */ + private function prepareHTMLAttributes($size, $attributes) + { + $dimensions = []; + if ($size !== false) { + $size = $this->get_size($size); + $dimensions = ['width' => $size, 'height' => $size]; + } + + $result = array_merge($this->attributes, $attributes, $dimensions, [ + 'src' => self::isStatic($this->shape) ? $this->shape : $this->get_asset_svg(), + ]); + + if (!isset($result['alt']) && !isset($result['title'])) { + //Add an empty alt attribute to prevent screen readers from + //reading the URL of the icon: + $result['alt'] = ''; + } + + $classNames = 'icon-role-' . $this->role; + + if (!self::isStatic($this->shape)) { + $classNames .= ' icon-shape-' . $this->shapeToPath($this->shape); + } + + $result['class'] = isset($result['class']) ? $result['class'] . ' ' . $classNames : $classNames; + + return $result; + } + + /** + * Get the correct asset for an SVG icon. + * + * @return String containing the url of the corresponding asset + */ + protected function get_asset_svg() + { + return Assets::url('images/icons/' . self::roleToColor($this->role) . '/' . $this->shapeToPath($this->shape) . '.svg'); + } + + /** + * Get the size of the icon. If a size was passed as a parameter and + * inside the attributes array during icon construction, the size from + * the attributes will be used. + * + * @param int $size size of the icon + * @return int Size of the icon in pixels + */ + protected function get_size($size) + { + $size = $size ?: Icon::DEFAULT_SIZE; + if (isset($this->attributes['size'])) { + $parts = explode('@', $this->attributes['size'], 2); + $size = $parts[0]; + $temp = $parts[1] ?? null; + unset($this->attributes['size']); + } + return (int)$size; + } + + // an icon is static if it starts with 'http' + private static function isStatic($shape) + { + return mb_strpos($shape, 'http') === 0; + } + + // transforms a shape w/ possible additions (`shape`) to a path `(addition/)?shape` + private function shapeToPath() + { + if (self::isStatic($this->shape)) { + return $this->shape; + } + $shape = array_reverse(explode('/', $this->shape))[0]; + $shape = explode('+', $shape)[0]; + return $shape; + } +} diff --git a/lib/classes/InstituteAvatar.class.php b/lib/classes/InstituteAvatar.class.php deleted file mode 100644 index 8adbfba..0000000 --- a/lib/classes/InstituteAvatar.class.php +++ /dev/null @@ -1,45 +0,0 @@ - - * @author Marcus Lunzenauer - * @copyright (c) Authors - * @license GPL2 or any later version - * @since 1.10 - */ -class InstituteAvatar extends CourseAvatar -{ - public const AVATAR_TYPE = 'institute'; - - /** - * Returns the CSS class to use for this avatar image. - * - * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) - * @return string CSS class to use for the avatar - */ - protected function getCssClass($size) - { - return "institute-avatar-{$size} institute-{$this->user_id}"; - } - - /** - * Return the default title of the avatar. - * @return string the default title - */ - public function getDefaultTitle() - { - $institute = Institute::find($this->user_id); - return $institute ? (string) $institute->name : self::NOBODY; - } - - /** - * Return if avatar is visible to the current user. - * @return boolean: true if visible - */ - protected function checkAvatarVisibility() - { - //no special conditions for visibility of course-avatars yet - return true; - } -} diff --git a/lib/classes/InstituteAvatar.php b/lib/classes/InstituteAvatar.php new file mode 100644 index 0000000..8adbfba --- /dev/null +++ b/lib/classes/InstituteAvatar.php @@ -0,0 +1,45 @@ + + * @author Marcus Lunzenauer + * @copyright (c) Authors + * @license GPL2 or any later version + * @since 1.10 + */ +class InstituteAvatar extends CourseAvatar +{ + public const AVATAR_TYPE = 'institute'; + + /** + * Returns the CSS class to use for this avatar image. + * + * @param string $size one of the constants Avatar::(NORMAL|MEDIUM|SMALL) + * @return string CSS class to use for the avatar + */ + protected function getCssClass($size) + { + return "institute-avatar-{$size} institute-{$this->user_id}"; + } + + /** + * Return the default title of the avatar. + * @return string the default title + */ + public function getDefaultTitle() + { + $institute = Institute::find($this->user_id); + return $institute ? (string) $institute->name : self::NOBODY; + } + + /** + * Return if avatar is visible to the current user. + * @return boolean: true if visible + */ + protected function checkAvatarVisibility() + { + //no special conditions for visibility of course-avatars yet + return true; + } +} diff --git a/lib/classes/InstituteCalendarHelper.class.php b/lib/classes/InstituteCalendarHelper.class.php deleted file mode 100644 index 4302cf1..0000000 --- a/lib/classes/InstituteCalendarHelper.class.php +++ /dev/null @@ -1,810 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - */ - -class InstituteCalendarHelper -{ - const COLUMN_DATAFIELD_ID = '69f6485f3c937766866a03d9d642ecbb'; - const COLOR_DATAFIELD_ID = '41cda2be71fe9efd6e28b853fc0681f3'; - const INST_DEFAULT_COLOR_DATAFIELD_ID = '0c63321a8e93b3ccc927611709248e07'; - const DEFAULT_EVENT_COLOR = '#899ab9'; - - /** - * Returns the default calendar columns. - * - * @return array default column names - */ - private static function getDefaultColumns() - { - return [ - 1 => ['Spalte 1', 1], - 2 => ['Spalte 2', 1], - 3 => ['Spalte 3', 1], - 4 => ['Spalte 4', 1], - 5 => ['Spalte 5', 1], - 6 => ['Spalte 6', 1] - ]; - } - - /** - * Fetches the stores columns merged with defaults. - * - * @param string $institut_id - * @param boolean $only_visible only return visible columns - * - * @return array column array in a fullcalender expected format - */ - public static function getResourceColumns($institut_id, $only_visible = false) - { - $columns = []; - $inst_columns = [ - 0 => ['Sammelspalte', 1] - ]; - - $db_inst_columns = InstitutePlanColumn::findByInstitute($institut_id); - - if ($db_inst_columns) { - foreach ($db_inst_columns as $col_info) { - $inst_columns[$col_info['column']] = [$col_info['name'], $col_info['visible']]; - } - } else { - $inst_columns = array_merge($inst_columns, self::getDefaultColumns()); - } - foreach ($inst_columns as $id => $info) { - if ($only_visible && !$info[1]) continue; - $columns[] = ['id' => $id, 'title' => $info[0], 'visible' => $info[1]]; - } - return $columns; - } - - /** - * Adds the default resource columns - * - * @param string $institut_id - * - * @return int last resource column number - */ - public static function addDefaultResourceColumns($institut_id) - { - $max_col = 0; - foreach (self::getDefaultColumns() as $col_id => $col_info) { - $new_col = new InstitutePlanColumn([$institut_id, $col_id]); - $new_col->name = $col_info[0]; - $new_col->visible = $col_info[1]; - if ($new_col->store()) { - $max_col = $col_id; - } - } - return $max_col; - } - - /** - * Adds a resource column - * - * @param string $institut_id - * @param string $name - * @param int $specific_column_number - * - * @return int number of affected rows - */ - public static function addResourceColumn($institut_id, $name, $specific_column_number = 0) - { - $last_col = InstitutePlanColumn::getLastColumnOfInstitute($institut_id); - if ($last_col !== null) { - $max_col = $last_col->column; - } else { - $max_col = self::addDefaultResourceColumns($institut_id); - } - $column_number = $specific_column_number>0?$specific_column_number:intval($max_col)+1; - $new_col = new InstitutePlanColumn([$institut_id, $column_number]); - $new_col->name = $name; - $new_col->visible = 1; - return $new_col->store(); - } - - /** - * Looks up column id for course events - * - * @param Course $course - * - * @return array course events with column id - */ - public static function getCourseEventcolumns(Course $course) - { - $df = DatafieldEntryModel::findByModel($course, self::COLUMN_DATAFIELD_ID); - if ($df[0] && $df[0]->content) { - $event_columns = unserialize($df[0]->content); - } else { - $event_columns = []; - } - return $event_columns; - } - - /** - * Sets the column id for course events - * - * @param Course $course - * @param string $event_id SeminarCycleDate id - * @param string $institut_id - * @param string $column number of the column - * - * @return bool stored - */ - public static function setCourseEventcolumn($course, $event_id, $institut_id, $column) - { - $df = DatafieldEntryModel::findByModel($course, self::COLUMN_DATAFIELD_ID); - if ($df[0]) { - $event_columns = self::getCourseEventcolumns($course); - if (!is_array($event_columns[$event_id])) { - unset($event_columns[$event_id]); - } - $event_columns[$event_id][$institut_id] = $column; - $df[0]->content = serialize($event_columns); - return $df[0]->store(); - } - return false; - } - - /** - * Looks up color value for course events - * - * @param Course $course - * - * @return array course events with color value - */ - public static function getCourseEventcolors($course) - { - $df = DatafieldEntryModel::findByModel($course, self::COLOR_DATAFIELD_ID); - if ($df[0] && $df[0]->content) { - $event_colors = unserialize($df[0]->content); - } else { - $event_colors = []; - } - return $event_colors; - } - - /** - * Sets color value for course events - * - * @param Course $course - * @param string $event_id SeminarCycleDate id - * @param string $institut_id - * @param string $color colorcode - * - * @return bool stored - */ - public static function setCourseEventcolor($course, $event_id, $institut_id, $color) - { - $df = DatafieldEntryModel::findByModel($course, self::COLOR_DATAFIELD_ID); - if ($df[0]) { - $event_colors = self::getCourseEventcolors($course); - if (!is_array($event_colors[$event_id])) { - unset($event_colors[$event_id]); - } - $event_colors[$event_id][$institut_id] = $color; - $df[0]->content = serialize($event_colors); - return $df[0]->store(); - } - return false; - } - - /** - * Looks up default color value for institute course events - * - * @param SimpleORMap $context - * - * @return array course events with color value - */ - public static function getInstituteDefaultEventcolors($context) - { - $df = DatafieldEntryModel::findByModel($context, self::INST_DEFAULT_COLOR_DATAFIELD_ID); - if ($df && $df[0]->content) { - $event_colors = unserialize($df[0]->content); - } else { - $event_colors = []; - } - return $event_colors; - } - - /** - * Sets default institute course events color value for semtypes - * - * @param SimpleORMap $context - * @param string $semtype - * @param string $color colorcode - * - * @return bool stored - */ - public static function setInstituteDefaultEventcolor($context, $semtype, $color) - { - $df = DatafieldEntryModel::findByModel($context, self::INST_DEFAULT_COLOR_DATAFIELD_ID); - if ($df[0]) { - $event_colors = self::getInstituteDefaultEventcolors($context); - $event_colors[$semtype['name']] = $color; - $df[0]->content = serialize($event_colors); - return $df[0]->store(); - } - return false; - } - - /** - * Sets color value for every course events of given courses semtype - * - * @param Course $course - * @param string $institut_id - * @param string $color colorcode - * - * @return bool stored - */ - public static function setSemtypeEventcolor($course, $institut_id, $color) - { - $semtype = $course->getSemType(); - $institut = Institute::find($institut_id); - if ($institut) { - self::setInstituteDefaultEventcolor($institut, $semtype, $color); - } - $courses = Course::findBySQL('status =? AND Institut_id=?', [$semtype['id'], $course->institut_id]); - if ($courses) { - foreach ($courses as $semtype_course) { - foreach (SeminarCycleDate::findBySeminar($semtype_course->seminar_id) as $cycle_date) { - self::setCourseEventcolor($semtype_course, $cycle_date->id, $institut_id, $color); - } - } - return true; - } - return false; - } - - /** - * Prepares an array of course id and names for creation of dropable calendar events - * - * @param array $courses Array of courses - * @param array $semester Semester - * - * @return array prepared array - */ - public static function getEventlessCourses($courses, $semester = null) - { - $eventless = []; - foreach (array_keys($courses) as $cid) { - $course = Course::find($cid); - $cycle_dates = SeminarCycleDate::findBySeminar($course->seminar_id); - if (count($cycle_dates) < 1) { - $eventless[$cid] = $course->getFullName('number-name'); - } elseif ($semester) { - $has_date_in_semester = false; - foreach ($cycle_dates as $cycle_date) { - foreach ($cycle_date->getAllDates() as $course_date) { - if ($course_date->date >= $semester->beginn && $course_date->date <= $semester->ende) { - $has_date_in_semester = true; - break; - } - } - if ($has_date_in_semester) break; - } - if (!$has_date_in_semester) { - $eventless[$cid] = $course->getFullName('number-name'); - } - } - } - return $eventless; - } - - /** - * Creates FullCalendar event date of course events - * - * @param array $courses Array of courses - * @param string $institut_id - * @param array $semester Semester - * @param array $specific_weekday fetch only events for specific weekday - * - * @return array fullcalendar events - */ - public static function getEvents($courses, $institut_id, $semester = null, $specific_weekday = null) - { - $today = date('w'); - - $user_insts = array_map(function ($arr) { - return $arr['Institut_id']; - }, Institute::getMyInstitutes($GLOBALS['user']->id)); - - $min_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_START_HOUR); - $minbigtime = (int) $min_time[0]; - $max_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR); - $maxbigtime = (int) $max_time[0]; - - $institut = Institute::find($institut_id); - - if (!$institut) { - return []; - } - - $inst_default_colors = self::getInstituteDefaultEventcolors($institut); - - $events = []; - Course::findEachMany(function ($course) use ( - $courses, - &$events, - $today, - $minbigtime, - $maxbigtime, - $user_insts, - $institut_id, - $inst_default_colors, - $semester, - $specific_weekday - ) { - $semtype = $course->getSemType(); - - $event_columns = self::getCourseEventcolumns($course) ?: []; - $event_colors = self::getCourseEventcolors($course) ?: []; - - if (in_array($course->institut_id, $user_insts)) { - $is_editable = true; - $is_start_editable = !LockRules::Check($course->id, 'room_time'); - $is_duration_editable = false; - } else { - $is_editable = false; - $is_start_editable = false; - $is_duration_editable = false; - } - - foreach (SeminarCycleDate::findBySeminar($course->seminar_id) as $cycle_date) { - if ($semester) { - $has_date_in_semester = false; - foreach ($cycle_date->getAllDates() as $course_date) { - if ($course_date->date >= $semester->beginn && $course_date->date <= $semester->ende) { - $has_date_in_semester = true; - break; - } - } - if (!$has_date_in_semester) { - continue; - } - } - - if (is_numeric($specific_weekday) && $specific_weekday != $cycle_date['weekday']) { - continue; - } - - $conform = true; - - if ($cycle_date['weekday'] == 0) { - $day_offset = 7 - $today; - } else { - $day_offset = $cycle_date['weekday'] - $today; - } - - $start_time = explode(':', $cycle_date['start_time']); - $bigtime = (int) $start_time[0]; - if ($bigtime > $maxbigtime || $bigtime < $minbigtime) { - $conform = false; - } elseif ($bigtime % 2) { - $bigtime--; - } - $start_time = $bigtime . ':00:00'; - - $end_time = explode(':', $start_time); - $end_time[0] += 2; - $end_time = implode(':', $end_time); - - $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event'); - $name = $course->getFullName('number-name'); - - if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) { - $start = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['start_time']); - $end = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['end_time']); - $backgroundcolor = '#6c737a'; - $textcolor = '#ffffff'; - } else { - $start = self::iso8601date(strtotime($day_offset . ' days'), $start_time); - $end = self::iso8601date(strtotime($day_offset . ' days'), $end_time); - - $backgroundcolor = null; - if (array_key_exists($cycle_date->id, $event_colors)) { - if (is_array($event_colors[$cycle_date->id]) && array_key_exists($institut_id, $event_colors[$cycle_date->id])) { - $backgroundcolor = $event_colors[$cycle_date->id][$institut_id]; - } - } - if (!$backgroundcolor) { - $backgroundcolor = array_key_exists($semtype['name'], $inst_default_colors) - ? $inst_default_colors[$semtype['name']] - : self::DEFAULT_EVENT_COLOR; - } - - $textcolor = '#ffffff'; - if (self::calculateLuminosityRatio($backgroundcolor, $textcolor) < 3) { - $textcolor = '#000000'; - } - } - if (!$is_editable) { - $backgroundcolor = '#c4c7c9'; - $textcolor = '#000000'; - } - - $resource_column = '0'; - if (array_key_exists($cycle_date->id, $event_columns)) { - if (is_array($event_columns[$cycle_date->id]) && array_key_exists($institut_id, $event_columns[$cycle_date->id])) { - $resource_column = $event_columns[$cycle_date->id][$institut_id]; - } - } - - $events[] = [ - 'resourceId' => $resource_column, - 'id' => $cycle_date->id, - 'title' => $name, - 'start' => $start, - 'end' => $end, - 'textColor' => $textcolor, - 'backgroundColor' => $backgroundcolor, - 'borderColor' => '#000', - 'editable' => $is_editable, - 'startEditable' => $is_start_editable, - 'durationEditable' => $is_duration_editable, - 'resourceEditable' => true, - 'studip_api_urls' => ['move' => $move_url], - 'studip_view_urls' => ['edit' => URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id)], - 'metadate_id' => $cycle_date->metadate_id, - 'course_id' => $cycle_date->seminar_id, - 'tooltip' => self::getCycleInfos($course, $cycle_date), - 'icon' => $is_start_editable ? '' : 'lock-locked', - 'conform' => $conform, - ]; - } - }, array_keys($courses)); - - return $events; - } - - /** - * Creates a fullcalendar event of given SeminarCycleDate - * - * @param SeminarCycleDate $cycle_date - * @param string $institut_id - * - * @return array enriched course info string for tooltip - */ - public static function getCycleEvent($cycle_date, $institut_id) - { - $course = Course::find($cycle_date->seminar_id); - $semtype = $course->getSemType(); - $institut = Institute::find($institut_id); - $inst_default_colors = self::getInstituteDefaultEventcolors($institut); - - $today = date('w'); - $user_insts = array_map(function ($arr) { - return $arr['Institut_id']; - }, Institute::getMyInstitutes($GLOBALS['user']->id)); - - $min_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_START_HOUR); - $minbigtime = (int) $min_time[0]; - $max_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR); - $maxbigtime = (int) $max_time[0]; - - $start_time = explode(':', $cycle_date['start_time']); - $bigtime = (int) $start_time[0]; - - if ($bigtime > $maxbigtime || $bigtime < $minbigtime) { - return null; - } elseif ($bigtime % 2) { - $bigtime--; - } - $start_time = $bigtime . ':00:00'; - - $end_time = explode(':', $start_time); - $end_time[0] += 2; - $end_time = implode(':', $end_time); - - if (in_array($course->institut_id, $user_insts)) { - $is_editable = true; - $is_start_editable = !LockRules::Check($cycle_date->seminar_id, 'room_time'); - $is_duration_editable = false; - } else { - $is_editable = false; - $is_start_editable = false; - $is_duration_editable = false; - } - - if ($cycle_date['weekday'] == 0) { - $day_offset = (7 - $today); - } else { - $day_offset = ($cycle_date['weekday'] - $today); - } - - $event_columns = self::getCourseEventcolumns($course); - $event_colors = self::getCourseEventcolors($course); - - $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event'); - $name = $course->getFullName('number-name'); - - if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) { - $start = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['start_time']); - $end = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['end_time']); - $backgroundcolor = '#6c737a'; - $textcolor = '#ffffff'; - } else { - $start = self::iso8601date(strtotime($day_offset . ' days'), $start_time); - $end = self::iso8601date(strtotime($day_offset . ' days'), $end_time); - - $backgroundcolor = null; - if (array_key_exists($cycle_date->id, $event_colors)) { - if (is_array($event_colors[$cycle_date->id]) && array_key_exists($institut_id, $event_colors[$cycle_date->id])) { - $backgroundcolor = $event_colors[$cycle_date->id][$institut_id]; - } - } - if (!$backgroundcolor) { - $backgroundcolor = array_key_exists($semtype['name'], $inst_default_colors)?$inst_default_colors[$semtype['name']]:self::DEFAULT_EVENT_COLOR; - } - - $textcolor = '#ffffff'; - if (self::calculateLuminosityRatio($backgroundcolor, $textcolor) < 3) { - $textcolor = '#000000'; - } - } - if (!$is_editable) { - $backgroundcolor = '#c4c7c9'; - $textcolor = '#000000'; - } - - $resource_column = '0'; - if (array_key_exists($cycle_date->id, $event_columns)) { - if (is_array($event_columns[$cycle_date->id]) && array_key_exists($institut_id, $event_columns[$cycle_date->id])) { - $resource_column = $event_columns[$cycle_date->id][$institut_id]; - } - } - - return [ - 'resourceId' => $resource_column, - 'id' => $cycle_date->id, - 'title' => $name, - 'start' => $start, - 'end' => $end, - 'textColor' => $textcolor, - 'backgroundColor' => $backgroundcolor, - 'borderColor' => '#000', - 'editable' => $is_editable, - 'startEditable' => $is_start_editable, - 'durationEditable' => $is_duration_editable, - 'resourceEditable' => true, - 'studip_api_urls' => ['move' => $move_url], - 'studip_view_urls' => ['edit' => URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id)], - 'metadate_id' => $cycle_date->metadate_id, - 'course_id' => $cycle_date->seminar_id, - 'tooltip' => self::getCycleInfos($course, $cycle_date), - 'icon' => $is_start_editable ? '' : 'lock-locked' - ]; - } - - /** - * Creates a string with course infos to be displayed as a tooltip in calendar events - * - * @param SeminarCycleDate $cycle_date - * - * @return string enriched course info string for tooltip - */ - private static function getCycleInfos($course, $cycle_date) - { - - $info_string = $course->getFullName('number-name') . "\n"; - - $dozenten = []; - foreach (CourseMember::findByCourseAndStatus($course->id, 'dozent') as $cmember) { - $dozenten[$cmember->user->user_id] = $cmember->user->getFullName(); - } - if ($dozenten) { - $info_string .= implode(', ', $dozenten) . "\n"; - } - - $rooms = []; - foreach ($cycle_date->getAllDates() as $course_date) { - $room = $course_date->getRoom(); - if ($room) { - $rooms[$room->id] = $room->name; - } - } - if ($rooms) { - $info_string .= implode(', ', $rooms) . "\n"; - } - - if ($course->getSemClass()->offsetGet('module')) { - $mvv_pathes = []; - $course_start = $course->start_time; - $course_end = ($course->end_time < 0 || is_null($course->end_time)) - ? PHP_INT_MAX - : $course->end_time; - // set filter to show only pathes with valid semester data - ModuleManagementModelTreeItem::setObjectFilter('Modul', - function ($modul) use ($course_start, $course_end) { - // check for public status - if (!$GLOBALS['MVV_MODUL']['STATUS']['values'][$modul->stat]['public']) { - return false; - } - $modul_start = Semester::find($modul->start)->beginn ?: 0; - $modul_end = Semester::find($modul->end)->ende ?: PHP_INT_MAX; - return ($modul_start <= $course_end && $modul_end >= $course_start); - } - ); - - ModuleManagementModelTreeItem::setObjectFilter('StgteilVersion', - function ($version) { - return (bool) $GLOBALS['MVV_STGTEILVERSION']['STATUS']['values'][$version->stat]['public']; - } - ); - - $trail_classes = ['Modulteil', 'StgteilabschnittModul', 'StgteilAbschnitt', 'StgteilVersion']; - $mvv_object_pathes = MvvCourse::get($course->getId())->getTrails($trail_classes); - if ($mvv_object_pathes) { - if (Config::get()->COURSE_SEM_TREE_DISPLAY) { - $mvv_tree = []; - foreach ($mvv_object_pathes as $mvv_object_path) { - // show only complete pathes - if (count($mvv_object_path) == 4) { - // flatten the pathes to a linked list - $stg = reset($mvv_object_path); - $parent_id = 'root'; - foreach ($mvv_object_path as $mvv_object) { - $mvv_object_id = $mvv_object instanceof StgteilabschnittModul - ? $mvv_object->modul_id - : $mvv_object->id; - $mvv_tree[$parent_id][$mvv_object_id] = [ - 'id' => $mvv_object_id, - 'name' => $mvv_object->getDisplayName(), - 'class' => get_class($mvv_object), - ]; - $parent_id = $mvv_object_id; - } - } - } - if (count($mvv_tree)) { - // add the root node - $mvv_tree['start'][] = [ - 'id' => 'root', - 'name' => Config::get()->UNI_NAME_CLEAN, - 'class' => '' - ]; - } - } else { - foreach ($mvv_object_pathes as $mvv_object_path) { - // show only complete pathes - if (count($mvv_object_path) == 4) { - $mvv_object_names = []; - $modul_id = ''; - foreach ($mvv_object_path as $mvv_object) { - if ($mvv_object instanceof StgteilabschnittModul) { - $modul_id = $mvv_object->modul_id; - } - $mvv_object_names[] = $mvv_object->getDisplayName(); - } - $mvv_pathes[] = [$modul_id => $mvv_object_names]; - } - } - } - // to prevent collisions of object ids in the tree - // in the case of same objects listed in more than one part - // of the tree - $id_sfx = new stdClass(); - $id_sfx->c = 1; - } - foreach ($mvv_pathes as $mvv_path) { - foreach ($mvv_path as $mvv_path_content) { - $info_string .= implode(' > ', $mvv_path_content) . "\n"; - } - } - } - - return $info_string; - } - - public static function getBackgroundEvents($start = null) - { - $datetime = new DateTime(); - $slot_duration = 1; - $start_time = 8; - $end_time = 16; - $events = []; - $day_interval = 1; - - if ($start == null) { - $datetime->modify('monday this week'); - $datetime->add(new DateInterval('PT' . $start_time . 'H')); - $day_interval = 7; - } - - for ($i = 1; $i <= $day_interval; $i++) { - for ($slot = $start_time; $slot < $end_time; $slot += $slot_duration) { - if ($slot % 2) { - $datetime->setTime($slot, 0); - $events[] = [ - 'start' => self::iso8601date($datetime, $slot), - 'end' => self::iso8601date($datetime, $slot + $slot_duration), - 'rendering' => 'background', - ]; - - } - } - $datetime->setTime($start_time, 0); - $datetime->add(new DateInterval('P1D')); - }; - return $events; - } - - private static function iso8601date($date, $time = '00:00:00', $timezone = '+00:00') - { - // If only date parameter is passed and is a DateTimeInterface object or - // unix timestamp, assume time from date - if (func_num_args() === 1 && ($date instanceof DateTimeInterface || ctype_digit($date))) { - $time = $date; - } - - // Get time - if ($time instanceof DateTimeInterface) { - $time = $time->format('H:i:s'); - } elseif (ctype_digit($time)) { - $time = date('H:i:s', $time); - } elseif (sscanf($time, '%u:%u:%u', $hours, $minutes, $seconds)) { - $time = sprintf('%02u:%02u:%02u', $hours, $minutes, $seconds); - } - - // Get date - if ($date instanceof DateTimeInterface) { - $date = $date->format('Y-m-d'); - } elseif (ctype_digit($date)) { - $date = date('Y-m-d', $date); - } - - return "{$date}T{$time}{$timezone}"; - } - - - // calculates the luminosity of an given RGB color - // the color code must be in the format of RRGGBB - // the luminosity equations are from the WCAG 2 requirements - // http://www.w3.org/TR/WCAG20/#relativeluminancedef - private static function calculateLuminosity($color) - { - $r = hexdec(substr($color, 0, 2)) / 255; // red value - $g = hexdec(substr($color, 2, 2)) / 255; // green value - $b = hexdec(substr($color, 4, 2)) / 255; // blue value - if ($r <= 0.03928) { - $r = $r / 12.92; - } else { - $r = pow(($r + 0.055) / 1.055, 2.4); - } - if ($g <= 0.03928) { - $g = $g / 12.92; - } else { - $g = pow(($g + 0.055) / 1.055, 2.4); - } - if ($b <= 0.03928) { - $b = $b / 12.92; - } else { - $b = pow(($b + 0.055) / 1.055, 2.4); - } - $luminosity = 0.2126 * $r + 0.7152 * $g + 0.0722 * $b; - return $luminosity; - } - - // calculates the luminosity ratio of two colors - // the luminosity ratio equations are from the WCAG 2 requirements - // http://www.w3.org/TR/WCAG20/#contrast-ratiodef - private static function calculateLuminosityRatio($color1, $color2) - { - $c1 = ltrim($color1, '#'); - $c2 = ltrim($color2, '#'); - $l1 = self::calculateLuminosity($c1); - $l2 = self::calculateLuminosity($c2); - if ($l1 > $l2) { - $ratio = ($l1 + 0.05) / ($l2 + 0.05); - } else { - $ratio = ($l2 + 0.05) / ($l1 + 0.05); - } - return $ratio; - } -} diff --git a/lib/classes/InstituteCalendarHelper.php b/lib/classes/InstituteCalendarHelper.php new file mode 100644 index 0000000..4302cf1 --- /dev/null +++ b/lib/classes/InstituteCalendarHelper.php @@ -0,0 +1,810 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + */ + +class InstituteCalendarHelper +{ + const COLUMN_DATAFIELD_ID = '69f6485f3c937766866a03d9d642ecbb'; + const COLOR_DATAFIELD_ID = '41cda2be71fe9efd6e28b853fc0681f3'; + const INST_DEFAULT_COLOR_DATAFIELD_ID = '0c63321a8e93b3ccc927611709248e07'; + const DEFAULT_EVENT_COLOR = '#899ab9'; + + /** + * Returns the default calendar columns. + * + * @return array default column names + */ + private static function getDefaultColumns() + { + return [ + 1 => ['Spalte 1', 1], + 2 => ['Spalte 2', 1], + 3 => ['Spalte 3', 1], + 4 => ['Spalte 4', 1], + 5 => ['Spalte 5', 1], + 6 => ['Spalte 6', 1] + ]; + } + + /** + * Fetches the stores columns merged with defaults. + * + * @param string $institut_id + * @param boolean $only_visible only return visible columns + * + * @return array column array in a fullcalender expected format + */ + public static function getResourceColumns($institut_id, $only_visible = false) + { + $columns = []; + $inst_columns = [ + 0 => ['Sammelspalte', 1] + ]; + + $db_inst_columns = InstitutePlanColumn::findByInstitute($institut_id); + + if ($db_inst_columns) { + foreach ($db_inst_columns as $col_info) { + $inst_columns[$col_info['column']] = [$col_info['name'], $col_info['visible']]; + } + } else { + $inst_columns = array_merge($inst_columns, self::getDefaultColumns()); + } + foreach ($inst_columns as $id => $info) { + if ($only_visible && !$info[1]) continue; + $columns[] = ['id' => $id, 'title' => $info[0], 'visible' => $info[1]]; + } + return $columns; + } + + /** + * Adds the default resource columns + * + * @param string $institut_id + * + * @return int last resource column number + */ + public static function addDefaultResourceColumns($institut_id) + { + $max_col = 0; + foreach (self::getDefaultColumns() as $col_id => $col_info) { + $new_col = new InstitutePlanColumn([$institut_id, $col_id]); + $new_col->name = $col_info[0]; + $new_col->visible = $col_info[1]; + if ($new_col->store()) { + $max_col = $col_id; + } + } + return $max_col; + } + + /** + * Adds a resource column + * + * @param string $institut_id + * @param string $name + * @param int $specific_column_number + * + * @return int number of affected rows + */ + public static function addResourceColumn($institut_id, $name, $specific_column_number = 0) + { + $last_col = InstitutePlanColumn::getLastColumnOfInstitute($institut_id); + if ($last_col !== null) { + $max_col = $last_col->column; + } else { + $max_col = self::addDefaultResourceColumns($institut_id); + } + $column_number = $specific_column_number>0?$specific_column_number:intval($max_col)+1; + $new_col = new InstitutePlanColumn([$institut_id, $column_number]); + $new_col->name = $name; + $new_col->visible = 1; + return $new_col->store(); + } + + /** + * Looks up column id for course events + * + * @param Course $course + * + * @return array course events with column id + */ + public static function getCourseEventcolumns(Course $course) + { + $df = DatafieldEntryModel::findByModel($course, self::COLUMN_DATAFIELD_ID); + if ($df[0] && $df[0]->content) { + $event_columns = unserialize($df[0]->content); + } else { + $event_columns = []; + } + return $event_columns; + } + + /** + * Sets the column id for course events + * + * @param Course $course + * @param string $event_id SeminarCycleDate id + * @param string $institut_id + * @param string $column number of the column + * + * @return bool stored + */ + public static function setCourseEventcolumn($course, $event_id, $institut_id, $column) + { + $df = DatafieldEntryModel::findByModel($course, self::COLUMN_DATAFIELD_ID); + if ($df[0]) { + $event_columns = self::getCourseEventcolumns($course); + if (!is_array($event_columns[$event_id])) { + unset($event_columns[$event_id]); + } + $event_columns[$event_id][$institut_id] = $column; + $df[0]->content = serialize($event_columns); + return $df[0]->store(); + } + return false; + } + + /** + * Looks up color value for course events + * + * @param Course $course + * + * @return array course events with color value + */ + public static function getCourseEventcolors($course) + { + $df = DatafieldEntryModel::findByModel($course, self::COLOR_DATAFIELD_ID); + if ($df[0] && $df[0]->content) { + $event_colors = unserialize($df[0]->content); + } else { + $event_colors = []; + } + return $event_colors; + } + + /** + * Sets color value for course events + * + * @param Course $course + * @param string $event_id SeminarCycleDate id + * @param string $institut_id + * @param string $color colorcode + * + * @return bool stored + */ + public static function setCourseEventcolor($course, $event_id, $institut_id, $color) + { + $df = DatafieldEntryModel::findByModel($course, self::COLOR_DATAFIELD_ID); + if ($df[0]) { + $event_colors = self::getCourseEventcolors($course); + if (!is_array($event_colors[$event_id])) { + unset($event_colors[$event_id]); + } + $event_colors[$event_id][$institut_id] = $color; + $df[0]->content = serialize($event_colors); + return $df[0]->store(); + } + return false; + } + + /** + * Looks up default color value for institute course events + * + * @param SimpleORMap $context + * + * @return array course events with color value + */ + public static function getInstituteDefaultEventcolors($context) + { + $df = DatafieldEntryModel::findByModel($context, self::INST_DEFAULT_COLOR_DATAFIELD_ID); + if ($df && $df[0]->content) { + $event_colors = unserialize($df[0]->content); + } else { + $event_colors = []; + } + return $event_colors; + } + + /** + * Sets default institute course events color value for semtypes + * + * @param SimpleORMap $context + * @param string $semtype + * @param string $color colorcode + * + * @return bool stored + */ + public static function setInstituteDefaultEventcolor($context, $semtype, $color) + { + $df = DatafieldEntryModel::findByModel($context, self::INST_DEFAULT_COLOR_DATAFIELD_ID); + if ($df[0]) { + $event_colors = self::getInstituteDefaultEventcolors($context); + $event_colors[$semtype['name']] = $color; + $df[0]->content = serialize($event_colors); + return $df[0]->store(); + } + return false; + } + + /** + * Sets color value for every course events of given courses semtype + * + * @param Course $course + * @param string $institut_id + * @param string $color colorcode + * + * @return bool stored + */ + public static function setSemtypeEventcolor($course, $institut_id, $color) + { + $semtype = $course->getSemType(); + $institut = Institute::find($institut_id); + if ($institut) { + self::setInstituteDefaultEventcolor($institut, $semtype, $color); + } + $courses = Course::findBySQL('status =? AND Institut_id=?', [$semtype['id'], $course->institut_id]); + if ($courses) { + foreach ($courses as $semtype_course) { + foreach (SeminarCycleDate::findBySeminar($semtype_course->seminar_id) as $cycle_date) { + self::setCourseEventcolor($semtype_course, $cycle_date->id, $institut_id, $color); + } + } + return true; + } + return false; + } + + /** + * Prepares an array of course id and names for creation of dropable calendar events + * + * @param array $courses Array of courses + * @param array $semester Semester + * + * @return array prepared array + */ + public static function getEventlessCourses($courses, $semester = null) + { + $eventless = []; + foreach (array_keys($courses) as $cid) { + $course = Course::find($cid); + $cycle_dates = SeminarCycleDate::findBySeminar($course->seminar_id); + if (count($cycle_dates) < 1) { + $eventless[$cid] = $course->getFullName('number-name'); + } elseif ($semester) { + $has_date_in_semester = false; + foreach ($cycle_dates as $cycle_date) { + foreach ($cycle_date->getAllDates() as $course_date) { + if ($course_date->date >= $semester->beginn && $course_date->date <= $semester->ende) { + $has_date_in_semester = true; + break; + } + } + if ($has_date_in_semester) break; + } + if (!$has_date_in_semester) { + $eventless[$cid] = $course->getFullName('number-name'); + } + } + } + return $eventless; + } + + /** + * Creates FullCalendar event date of course events + * + * @param array $courses Array of courses + * @param string $institut_id + * @param array $semester Semester + * @param array $specific_weekday fetch only events for specific weekday + * + * @return array fullcalendar events + */ + public static function getEvents($courses, $institut_id, $semester = null, $specific_weekday = null) + { + $today = date('w'); + + $user_insts = array_map(function ($arr) { + return $arr['Institut_id']; + }, Institute::getMyInstitutes($GLOBALS['user']->id)); + + $min_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_START_HOUR); + $minbigtime = (int) $min_time[0]; + $max_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR); + $maxbigtime = (int) $max_time[0]; + + $institut = Institute::find($institut_id); + + if (!$institut) { + return []; + } + + $inst_default_colors = self::getInstituteDefaultEventcolors($institut); + + $events = []; + Course::findEachMany(function ($course) use ( + $courses, + &$events, + $today, + $minbigtime, + $maxbigtime, + $user_insts, + $institut_id, + $inst_default_colors, + $semester, + $specific_weekday + ) { + $semtype = $course->getSemType(); + + $event_columns = self::getCourseEventcolumns($course) ?: []; + $event_colors = self::getCourseEventcolors($course) ?: []; + + if (in_array($course->institut_id, $user_insts)) { + $is_editable = true; + $is_start_editable = !LockRules::Check($course->id, 'room_time'); + $is_duration_editable = false; + } else { + $is_editable = false; + $is_start_editable = false; + $is_duration_editable = false; + } + + foreach (SeminarCycleDate::findBySeminar($course->seminar_id) as $cycle_date) { + if ($semester) { + $has_date_in_semester = false; + foreach ($cycle_date->getAllDates() as $course_date) { + if ($course_date->date >= $semester->beginn && $course_date->date <= $semester->ende) { + $has_date_in_semester = true; + break; + } + } + if (!$has_date_in_semester) { + continue; + } + } + + if (is_numeric($specific_weekday) && $specific_weekday != $cycle_date['weekday']) { + continue; + } + + $conform = true; + + if ($cycle_date['weekday'] == 0) { + $day_offset = 7 - $today; + } else { + $day_offset = $cycle_date['weekday'] - $today; + } + + $start_time = explode(':', $cycle_date['start_time']); + $bigtime = (int) $start_time[0]; + if ($bigtime > $maxbigtime || $bigtime < $minbigtime) { + $conform = false; + } elseif ($bigtime % 2) { + $bigtime--; + } + $start_time = $bigtime . ':00:00'; + + $end_time = explode(':', $start_time); + $end_time[0] += 2; + $end_time = implode(':', $end_time); + + $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event'); + $name = $course->getFullName('number-name'); + + if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) { + $start = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['start_time']); + $end = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['end_time']); + $backgroundcolor = '#6c737a'; + $textcolor = '#ffffff'; + } else { + $start = self::iso8601date(strtotime($day_offset . ' days'), $start_time); + $end = self::iso8601date(strtotime($day_offset . ' days'), $end_time); + + $backgroundcolor = null; + if (array_key_exists($cycle_date->id, $event_colors)) { + if (is_array($event_colors[$cycle_date->id]) && array_key_exists($institut_id, $event_colors[$cycle_date->id])) { + $backgroundcolor = $event_colors[$cycle_date->id][$institut_id]; + } + } + if (!$backgroundcolor) { + $backgroundcolor = array_key_exists($semtype['name'], $inst_default_colors) + ? $inst_default_colors[$semtype['name']] + : self::DEFAULT_EVENT_COLOR; + } + + $textcolor = '#ffffff'; + if (self::calculateLuminosityRatio($backgroundcolor, $textcolor) < 3) { + $textcolor = '#000000'; + } + } + if (!$is_editable) { + $backgroundcolor = '#c4c7c9'; + $textcolor = '#000000'; + } + + $resource_column = '0'; + if (array_key_exists($cycle_date->id, $event_columns)) { + if (is_array($event_columns[$cycle_date->id]) && array_key_exists($institut_id, $event_columns[$cycle_date->id])) { + $resource_column = $event_columns[$cycle_date->id][$institut_id]; + } + } + + $events[] = [ + 'resourceId' => $resource_column, + 'id' => $cycle_date->id, + 'title' => $name, + 'start' => $start, + 'end' => $end, + 'textColor' => $textcolor, + 'backgroundColor' => $backgroundcolor, + 'borderColor' => '#000', + 'editable' => $is_editable, + 'startEditable' => $is_start_editable, + 'durationEditable' => $is_duration_editable, + 'resourceEditable' => true, + 'studip_api_urls' => ['move' => $move_url], + 'studip_view_urls' => ['edit' => URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id)], + 'metadate_id' => $cycle_date->metadate_id, + 'course_id' => $cycle_date->seminar_id, + 'tooltip' => self::getCycleInfos($course, $cycle_date), + 'icon' => $is_start_editable ? '' : 'lock-locked', + 'conform' => $conform, + ]; + } + }, array_keys($courses)); + + return $events; + } + + /** + * Creates a fullcalendar event of given SeminarCycleDate + * + * @param SeminarCycleDate $cycle_date + * @param string $institut_id + * + * @return array enriched course info string for tooltip + */ + public static function getCycleEvent($cycle_date, $institut_id) + { + $course = Course::find($cycle_date->seminar_id); + $semtype = $course->getSemType(); + $institut = Institute::find($institut_id); + $inst_default_colors = self::getInstituteDefaultEventcolors($institut); + + $today = date('w'); + $user_insts = array_map(function ($arr) { + return $arr['Institut_id']; + }, Institute::getMyInstitutes($GLOBALS['user']->id)); + + $min_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_START_HOUR); + $minbigtime = (int) $min_time[0]; + $max_time = explode(':', Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR); + $maxbigtime = (int) $max_time[0]; + + $start_time = explode(':', $cycle_date['start_time']); + $bigtime = (int) $start_time[0]; + + if ($bigtime > $maxbigtime || $bigtime < $minbigtime) { + return null; + } elseif ($bigtime % 2) { + $bigtime--; + } + $start_time = $bigtime . ':00:00'; + + $end_time = explode(':', $start_time); + $end_time[0] += 2; + $end_time = implode(':', $end_time); + + if (in_array($course->institut_id, $user_insts)) { + $is_editable = true; + $is_start_editable = !LockRules::Check($cycle_date->seminar_id, 'room_time'); + $is_duration_editable = false; + } else { + $is_editable = false; + $is_start_editable = false; + $is_duration_editable = false; + } + + if ($cycle_date['weekday'] == 0) { + $day_offset = (7 - $today); + } else { + $day_offset = ($cycle_date['weekday'] - $today); + } + + $event_columns = self::getCourseEventcolumns($course); + $event_colors = self::getCourseEventcolors($course); + + $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event'); + $name = $course->getFullName('number-name'); + + if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) { + $start = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['start_time']); + $end = self::iso8601date(strtotime($day_offset . ' days'), $cycle_date['end_time']); + $backgroundcolor = '#6c737a'; + $textcolor = '#ffffff'; + } else { + $start = self::iso8601date(strtotime($day_offset . ' days'), $start_time); + $end = self::iso8601date(strtotime($day_offset . ' days'), $end_time); + + $backgroundcolor = null; + if (array_key_exists($cycle_date->id, $event_colors)) { + if (is_array($event_colors[$cycle_date->id]) && array_key_exists($institut_id, $event_colors[$cycle_date->id])) { + $backgroundcolor = $event_colors[$cycle_date->id][$institut_id]; + } + } + if (!$backgroundcolor) { + $backgroundcolor = array_key_exists($semtype['name'], $inst_default_colors)?$inst_default_colors[$semtype['name']]:self::DEFAULT_EVENT_COLOR; + } + + $textcolor = '#ffffff'; + if (self::calculateLuminosityRatio($backgroundcolor, $textcolor) < 3) { + $textcolor = '#000000'; + } + } + if (!$is_editable) { + $backgroundcolor = '#c4c7c9'; + $textcolor = '#000000'; + } + + $resource_column = '0'; + if (array_key_exists($cycle_date->id, $event_columns)) { + if (is_array($event_columns[$cycle_date->id]) && array_key_exists($institut_id, $event_columns[$cycle_date->id])) { + $resource_column = $event_columns[$cycle_date->id][$institut_id]; + } + } + + return [ + 'resourceId' => $resource_column, + 'id' => $cycle_date->id, + 'title' => $name, + 'start' => $start, + 'end' => $end, + 'textColor' => $textcolor, + 'backgroundColor' => $backgroundcolor, + 'borderColor' => '#000', + 'editable' => $is_editable, + 'startEditable' => $is_start_editable, + 'durationEditable' => $is_duration_editable, + 'resourceEditable' => true, + 'studip_api_urls' => ['move' => $move_url], + 'studip_view_urls' => ['edit' => URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id)], + 'metadate_id' => $cycle_date->metadate_id, + 'course_id' => $cycle_date->seminar_id, + 'tooltip' => self::getCycleInfos($course, $cycle_date), + 'icon' => $is_start_editable ? '' : 'lock-locked' + ]; + } + + /** + * Creates a string with course infos to be displayed as a tooltip in calendar events + * + * @param SeminarCycleDate $cycle_date + * + * @return string enriched course info string for tooltip + */ + private static function getCycleInfos($course, $cycle_date) + { + + $info_string = $course->getFullName('number-name') . "\n"; + + $dozenten = []; + foreach (CourseMember::findByCourseAndStatus($course->id, 'dozent') as $cmember) { + $dozenten[$cmember->user->user_id] = $cmember->user->getFullName(); + } + if ($dozenten) { + $info_string .= implode(', ', $dozenten) . "\n"; + } + + $rooms = []; + foreach ($cycle_date->getAllDates() as $course_date) { + $room = $course_date->getRoom(); + if ($room) { + $rooms[$room->id] = $room->name; + } + } + if ($rooms) { + $info_string .= implode(', ', $rooms) . "\n"; + } + + if ($course->getSemClass()->offsetGet('module')) { + $mvv_pathes = []; + $course_start = $course->start_time; + $course_end = ($course->end_time < 0 || is_null($course->end_time)) + ? PHP_INT_MAX + : $course->end_time; + // set filter to show only pathes with valid semester data + ModuleManagementModelTreeItem::setObjectFilter('Modul', + function ($modul) use ($course_start, $course_end) { + // check for public status + if (!$GLOBALS['MVV_MODUL']['STATUS']['values'][$modul->stat]['public']) { + return false; + } + $modul_start = Semester::find($modul->start)->beginn ?: 0; + $modul_end = Semester::find($modul->end)->ende ?: PHP_INT_MAX; + return ($modul_start <= $course_end && $modul_end >= $course_start); + } + ); + + ModuleManagementModelTreeItem::setObjectFilter('StgteilVersion', + function ($version) { + return (bool) $GLOBALS['MVV_STGTEILVERSION']['STATUS']['values'][$version->stat]['public']; + } + ); + + $trail_classes = ['Modulteil', 'StgteilabschnittModul', 'StgteilAbschnitt', 'StgteilVersion']; + $mvv_object_pathes = MvvCourse::get($course->getId())->getTrails($trail_classes); + if ($mvv_object_pathes) { + if (Config::get()->COURSE_SEM_TREE_DISPLAY) { + $mvv_tree = []; + foreach ($mvv_object_pathes as $mvv_object_path) { + // show only complete pathes + if (count($mvv_object_path) == 4) { + // flatten the pathes to a linked list + $stg = reset($mvv_object_path); + $parent_id = 'root'; + foreach ($mvv_object_path as $mvv_object) { + $mvv_object_id = $mvv_object instanceof StgteilabschnittModul + ? $mvv_object->modul_id + : $mvv_object->id; + $mvv_tree[$parent_id][$mvv_object_id] = [ + 'id' => $mvv_object_id, + 'name' => $mvv_object->getDisplayName(), + 'class' => get_class($mvv_object), + ]; + $parent_id = $mvv_object_id; + } + } + } + if (count($mvv_tree)) { + // add the root node + $mvv_tree['start'][] = [ + 'id' => 'root', + 'name' => Config::get()->UNI_NAME_CLEAN, + 'class' => '' + ]; + } + } else { + foreach ($mvv_object_pathes as $mvv_object_path) { + // show only complete pathes + if (count($mvv_object_path) == 4) { + $mvv_object_names = []; + $modul_id = ''; + foreach ($mvv_object_path as $mvv_object) { + if ($mvv_object instanceof StgteilabschnittModul) { + $modul_id = $mvv_object->modul_id; + } + $mvv_object_names[] = $mvv_object->getDisplayName(); + } + $mvv_pathes[] = [$modul_id => $mvv_object_names]; + } + } + } + // to prevent collisions of object ids in the tree + // in the case of same objects listed in more than one part + // of the tree + $id_sfx = new stdClass(); + $id_sfx->c = 1; + } + foreach ($mvv_pathes as $mvv_path) { + foreach ($mvv_path as $mvv_path_content) { + $info_string .= implode(' > ', $mvv_path_content) . "\n"; + } + } + } + + return $info_string; + } + + public static function getBackgroundEvents($start = null) + { + $datetime = new DateTime(); + $slot_duration = 1; + $start_time = 8; + $end_time = 16; + $events = []; + $day_interval = 1; + + if ($start == null) { + $datetime->modify('monday this week'); + $datetime->add(new DateInterval('PT' . $start_time . 'H')); + $day_interval = 7; + } + + for ($i = 1; $i <= $day_interval; $i++) { + for ($slot = $start_time; $slot < $end_time; $slot += $slot_duration) { + if ($slot % 2) { + $datetime->setTime($slot, 0); + $events[] = [ + 'start' => self::iso8601date($datetime, $slot), + 'end' => self::iso8601date($datetime, $slot + $slot_duration), + 'rendering' => 'background', + ]; + + } + } + $datetime->setTime($start_time, 0); + $datetime->add(new DateInterval('P1D')); + }; + return $events; + } + + private static function iso8601date($date, $time = '00:00:00', $timezone = '+00:00') + { + // If only date parameter is passed and is a DateTimeInterface object or + // unix timestamp, assume time from date + if (func_num_args() === 1 && ($date instanceof DateTimeInterface || ctype_digit($date))) { + $time = $date; + } + + // Get time + if ($time instanceof DateTimeInterface) { + $time = $time->format('H:i:s'); + } elseif (ctype_digit($time)) { + $time = date('H:i:s', $time); + } elseif (sscanf($time, '%u:%u:%u', $hours, $minutes, $seconds)) { + $time = sprintf('%02u:%02u:%02u', $hours, $minutes, $seconds); + } + + // Get date + if ($date instanceof DateTimeInterface) { + $date = $date->format('Y-m-d'); + } elseif (ctype_digit($date)) { + $date = date('Y-m-d', $date); + } + + return "{$date}T{$time}{$timezone}"; + } + + + // calculates the luminosity of an given RGB color + // the color code must be in the format of RRGGBB + // the luminosity equations are from the WCAG 2 requirements + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + private static function calculateLuminosity($color) + { + $r = hexdec(substr($color, 0, 2)) / 255; // red value + $g = hexdec(substr($color, 2, 2)) / 255; // green value + $b = hexdec(substr($color, 4, 2)) / 255; // blue value + if ($r <= 0.03928) { + $r = $r / 12.92; + } else { + $r = pow(($r + 0.055) / 1.055, 2.4); + } + if ($g <= 0.03928) { + $g = $g / 12.92; + } else { + $g = pow(($g + 0.055) / 1.055, 2.4); + } + if ($b <= 0.03928) { + $b = $b / 12.92; + } else { + $b = pow(($b + 0.055) / 1.055, 2.4); + } + $luminosity = 0.2126 * $r + 0.7152 * $g + 0.0722 * $b; + return $luminosity; + } + + // calculates the luminosity ratio of two colors + // the luminosity ratio equations are from the WCAG 2 requirements + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + private static function calculateLuminosityRatio($color1, $color2) + { + $c1 = ltrim($color1, '#'); + $c2 = ltrim($color2, '#'); + $l1 = self::calculateLuminosity($c1); + $l2 = self::calculateLuminosity($c2); + if ($l1 > $l2) { + $ratio = ($l1 + 0.05) / ($l2 + 0.05); + } else { + $ratio = ($l2 + 0.05) / ($l1 + 0.05); + } + return $ratio; + } +} diff --git a/lib/classes/InstituteConfig.class.php b/lib/classes/InstituteConfig.class.php deleted file mode 100644 index d1a51cb..0000000 --- a/lib/classes/InstituteConfig.class.php +++ /dev/null @@ -1,15 +0,0 @@ - - * @license GPL2 or any later version - */ -class InstituteConfig extends RangeConfig -{ - /** - * range type - */ - const RANGE_TYPE = 'institute'; -} diff --git a/lib/classes/InstituteConfig.php b/lib/classes/InstituteConfig.php new file mode 100644 index 0000000..d1a51cb --- /dev/null +++ b/lib/classes/InstituteConfig.php @@ -0,0 +1,15 @@ + + * @license GPL2 or any later version + */ +class InstituteConfig extends RangeConfig +{ + /** + * range type + */ + const RANGE_TYPE = 'institute'; +} diff --git a/lib/classes/Interactable.class.php b/lib/classes/Interactable.class.php deleted file mode 100644 index 9796f91..0000000 --- a/lib/classes/Interactable.class.php +++ /dev/null @@ -1,204 +0,0 @@ -label = $label; - $this->attributes = $attributes; - } - - /** - * Magic method (triggered when invoking inaccessible methods in a static - * context) used to dynamically create an interactable element with an - * additional CSSclass. This works for every static method call matching: - * /^create(.+)/ The matched group is used as CSS class for the - * interactable element. - * - * @code - * echo Button::createSubmit(); - * - * # => - * - * echo Button::create('Yes') - * # => - * - * echo Button::create('Yes', 'aName') - * # => - * - * echo Button::create('Yes', array('a' => 1, 'b' => 2)) - * # => - * - * echo Button::create('Yes', 'aName', array('a' => 1, 'b' => 2)), - * # => - * @endcode - * - * @param string $label the label of the current element - * @param string $trait the specific trait of the current element - * @param array $attributes the attributes of the button element - * - * @return Interactable element - */ - public static function create($label = NULL, $trait = NULL, $attributes = []) - { - $argc = func_num_args(); - - // if label is empty, use default - $label = $label ?: _('ok'); - - // if there are two parameters, there are two cases: - // 1.) label and trait OR - // 2.) label and attributes - // - // in the latter case, use parameter $trait as attributes - // and use the default for name - if ($argc === 2 && is_array($trait)) { - list($attributes, $trait) = [$trait, NULL]; - } - - $interactable = new static($label, $attributes); - $interactable->initialize($label, $trait, $attributes); - - return $interactable; - } - - /** - * Initialize an interactable element. - * The parameters to create are handed over to enable subclass - * specific customization. - * - * @param string $label the label of the current element - * @param string $trait the specific trait of the current element - * @param array $attributes the attributes of the button element - */ - abstract protected function initialize($label, $trait, $attributes); - - /** - * Convenience method used for autocompletion hints by your - * editor. - * - * Without this method #__callStatic would do the same. - * - * @param string $label the label of the current element - * @param string $trait the specific trait of the current element - * @param array $attributes the attributes of the button element - */ - public static function createAccept($label = NULL, $trait = NULL, $attributes = []) - { - $args = func_num_args() ? func_get_args() : [_('Übernehmen')]; - return self::__callStatic(__FUNCTION__, $args); - } - - /** - * Convenience method used for autocompletion hints by your - * editor. - * - * Without this method #__callStatic would do the same. - * - * @param string $label the label of the current element - * @param string $trait the specific trait of the current element - * @param array $attributes the attributes of the button element - */ - public static function createEdit($label = NULL, $trait = NULL, $attributes = []) - { - $args = func_num_args() ? func_get_args() : [_('Bearbeiten')]; - return self::__callStatic(__FUNCTION__, $args); - } - - /** - * Convenience method used for autocompletion hints by your - * editor. - * - * Without this method #__callStatic would do the same. - * - * @param string $label the label of the current element - * @param string $trait the specific trait of the current element - * @param array $attributes the attributes of the button element - */ - public static function createCancel($label = NULL, $trait = NULL, $attributes = []) - { - $args = func_num_args() ? func_get_args() : [_('Abbrechen')]; - return self::__callStatic(__FUNCTION__, $args); - } - - /** - * Hyphenates the passed word. - * - * @param string $word word to be hyphenated - * - * @return string hyphenated word - */ - private static function hyphenate($word) - { - return mb_strtolower(preg_replace('/(?<=\w)([A-Z])/', '-\\1', $word)); - } -} diff --git a/lib/classes/Interactable.php b/lib/classes/Interactable.php new file mode 100644 index 0000000..9796f91 --- /dev/null +++ b/lib/classes/Interactable.php @@ -0,0 +1,204 @@ +label = $label; + $this->attributes = $attributes; + } + + /** + * Magic method (triggered when invoking inaccessible methods in a static + * context) used to dynamically create an interactable element with an + * additional CSSclass. This works for every static method call matching: + * /^create(.+)/ The matched group is used as CSS class for the + * interactable element. + * + * @code + * echo Button::createSubmit(); + * + * # => + * + * echo Button::create('Yes') + * # => + * + * echo Button::create('Yes', 'aName') + * # => + * + * echo Button::create('Yes', array('a' => 1, 'b' => 2)) + * # => + * + * echo Button::create('Yes', 'aName', array('a' => 1, 'b' => 2)), + * # => + * @endcode + * + * @param string $label the label of the current element + * @param string $trait the specific trait of the current element + * @param array $attributes the attributes of the button element + * + * @return Interactable element + */ + public static function create($label = NULL, $trait = NULL, $attributes = []) + { + $argc = func_num_args(); + + // if label is empty, use default + $label = $label ?: _('ok'); + + // if there are two parameters, there are two cases: + // 1.) label and trait OR + // 2.) label and attributes + // + // in the latter case, use parameter $trait as attributes + // and use the default for name + if ($argc === 2 && is_array($trait)) { + list($attributes, $trait) = [$trait, NULL]; + } + + $interactable = new static($label, $attributes); + $interactable->initialize($label, $trait, $attributes); + + return $interactable; + } + + /** + * Initialize an interactable element. + * The parameters to create are handed over to enable subclass + * specific customization. + * + * @param string $label the label of the current element + * @param string $trait the specific trait of the current element + * @param array $attributes the attributes of the button element + */ + abstract protected function initialize($label, $trait, $attributes); + + /** + * Convenience method used for autocompletion hints by your + * editor. + * + * Without this method #__callStatic would do the same. + * + * @param string $label the label of the current element + * @param string $trait the specific trait of the current element + * @param array $attributes the attributes of the button element + */ + public static function createAccept($label = NULL, $trait = NULL, $attributes = []) + { + $args = func_num_args() ? func_get_args() : [_('Übernehmen')]; + return self::__callStatic(__FUNCTION__, $args); + } + + /** + * Convenience method used for autocompletion hints by your + * editor. + * + * Without this method #__callStatic would do the same. + * + * @param string $label the label of the current element + * @param string $trait the specific trait of the current element + * @param array $attributes the attributes of the button element + */ + public static function createEdit($label = NULL, $trait = NULL, $attributes = []) + { + $args = func_num_args() ? func_get_args() : [_('Bearbeiten')]; + return self::__callStatic(__FUNCTION__, $args); + } + + /** + * Convenience method used for autocompletion hints by your + * editor. + * + * Without this method #__callStatic would do the same. + * + * @param string $label the label of the current element + * @param string $trait the specific trait of the current element + * @param array $attributes the attributes of the button element + */ + public static function createCancel($label = NULL, $trait = NULL, $attributes = []) + { + $args = func_num_args() ? func_get_args() : [_('Abbrechen')]; + return self::__callStatic(__FUNCTION__, $args); + } + + /** + * Hyphenates the passed word. + * + * @param string $word word to be hyphenated + * + * @return string hyphenated word + */ + private static function hyphenate($word) + { + return mb_strtolower(preg_replace('/(?<=\w)([A-Z])/', '-\\1', $word)); + } +} diff --git a/lib/classes/JSONArrayObject.class.php b/lib/classes/JSONArrayObject.class.php deleted file mode 100644 index 896be3e..0000000 --- a/lib/classes/JSONArrayObject.class.php +++ /dev/null @@ -1,40 +0,0 @@ - - * @link http://www.php.net/manual/en/class.arrayobject.php - */ -class JSONArrayObject extends MultiDimArrayObject -{ - /** - * Construct an array object from a json string - * - * @param string $input a json string - */ - function __construct($input) - { - if (is_string($input)) { - $input = (array)json_decode($input, true); - } - parent::__construct($input); - } - - /** - * magic method for use of object in string context - * - * @return string internal array converted to json - */ - function __toString() - { - return json_encode($this->getArrayCopy()); - } -} diff --git a/lib/classes/JSONArrayObject.php b/lib/classes/JSONArrayObject.php new file mode 100644 index 0000000..896be3e --- /dev/null +++ b/lib/classes/JSONArrayObject.php @@ -0,0 +1,40 @@ + + * @link http://www.php.net/manual/en/class.arrayobject.php + */ +class JSONArrayObject extends MultiDimArrayObject +{ + /** + * Construct an array object from a json string + * + * @param string $input a json string + */ + function __construct($input) + { + if (is_string($input)) { + $input = (array)json_decode($input, true); + } + parent::__construct($input); + } + + /** + * magic method for use of object in string context + * + * @return string internal array converted to json + */ + function __toString() + { + return json_encode($this->getArrayCopy()); + } +} diff --git a/lib/classes/LayoutMessage.interface.php b/lib/classes/LayoutMessage.interface.php deleted file mode 100644 index 7072788..0000000 --- a/lib/classes/LayoutMessage.interface.php +++ /dev/null @@ -1,18 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 4.2 - */ -interface LayoutMessage -{ - /** - * Renders the message as html. - * - * @return string - */ - public function __toString(); -} diff --git a/lib/classes/LayoutMessage.php b/lib/classes/LayoutMessage.php new file mode 100644 index 0000000..7072788 --- /dev/null +++ b/lib/classes/LayoutMessage.php @@ -0,0 +1,18 @@ + + * @license GPL2 or any later version + * @since Stud.IP 4.2 + */ +interface LayoutMessage +{ + /** + * Renders the message as html. + * + * @return string + */ + public function __toString(); +} diff --git a/lib/classes/LinkButton.class.php b/lib/classes/LinkButton.class.php deleted file mode 100644 index 40044b6..0000000 --- a/lib/classes/LinkButton.class.php +++ /dev/null @@ -1,58 +0,0 @@ - HTML element. - * - * @param string $label the label of the element - * @param string $url the @href element of the element - * @param array $attributes the attributes of the element - */ - protected function initialize($label, $url, $attributes) - { - $this->attributes['href'] = $url ?: \URLHelper::getURL(); - } - - /** - * @return string returns a HTML representation of this hyperlink. - */ - public function __toString() - { - if ( - isset($this->attributes['disabled']) - && $this->attributes['disabled'] !== false - ) { - return (string) Button::create($this->label, 'none', $this->attributes); - } - - // add "button" to attribute @class - if (empty($this->attributes['class'])) { - $this->attributes['class'] = ''; - } - $this->attributes['class'] .= ' button'; - - // TODO: URLHelper...?! - return sprintf( - '%s', - arrayToHtmlAttributes($this->attributes), - htmlReady($this->label) - ); - } -} diff --git a/lib/classes/LinkButton.php b/lib/classes/LinkButton.php new file mode 100644 index 0000000..40044b6 --- /dev/null +++ b/lib/classes/LinkButton.php @@ -0,0 +1,58 @@ + HTML element. + * + * @param string $label the label of the element + * @param string $url the @href element of the element + * @param array $attributes the attributes of the element + */ + protected function initialize($label, $url, $attributes) + { + $this->attributes['href'] = $url ?: \URLHelper::getURL(); + } + + /** + * @return string returns a HTML representation of this hyperlink. + */ + public function __toString() + { + if ( + isset($this->attributes['disabled']) + && $this->attributes['disabled'] !== false + ) { + return (string) Button::create($this->label, 'none', $this->attributes); + } + + // add "button" to attribute @class + if (empty($this->attributes['class'])) { + $this->attributes['class'] = ''; + } + $this->attributes['class'] .= ' button'; + + // TODO: URLHelper...?! + return sprintf( + '%s', + arrayToHtmlAttributes($this->attributes), + htmlReady($this->label) + ); + } +} diff --git a/lib/classes/LockRules.class.php b/lib/classes/LockRules.class.php deleted file mode 100644 index 59679db..0000000 --- a/lib/classes/LockRules.class.php +++ /dev/null @@ -1,247 +0,0 @@ - - * @author André Noack - * @copyright 2011 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -/** -* LockRules.class.php -* -* This class contains only static methods dealing with lock rules -* -*/ - -class LockRules { - - private static $lockmap = []; - private static $lockrules = []; - - /** - * get lockrule object for given id - * from static object pool - * - * @param string $lock_id id of lockrule - * @return LockRule - */ - public static function get($lock_id) - { - if(!array_key_exists($lock_id, self::$lockrules)) { - self::$lockrules[$lock_id] = LockRule::find($lock_id); - } - return self::$lockrules[$lock_id]; - } - - /** - * returns a list of lockrules that can be administrated - * with the given user id - * - * @param string $user_id id of user - * @return array of LockRule objects - */ - public static function getAdministrableSeminarRules($user_id) - { - return array_filter(LockRule::findAllByType('sem'), function ($rule) use ($user_id) { - return $GLOBALS['perm']->get_perm($user_id) === 'root' - || ( - $rule->user_id === $user_id - && !in_array($rule->permission, ['root', 'admin']) - ); - }); - } - - /** - * returns a list of lockrules that can be applied to a course - * with the given user id - * - * @param string $user_id id of user - * @return array of LockRule objects - */ - public static function getAvailableSeminarRules($user_id) - { - return array_filter(LockRule::findAllByType('sem'), function ($rule) use ($user_id) { - return $GLOBALS['perm']->get_perm($user_id) === 'root' - || !in_array($rule->permission, ['root', 'admin']); - }); - } - - /** - * returns the lock rule object for the given id, else null - * - * @param string $object_id id of course, institute or user - * @param bool $renew if true, reloads the rule from database - * @param string|null $object_type : The type of object you want to check: "user", "sem" or "inst" - * @return LockRule - */ - public static function getObjectRule($object_id, $renew = false, $object_type = null) - { - if (!array_key_exists($object_id, self::$lockmap) || $renew) { - self::$lockmap[$object_id] = null; - if ($object_type === null) { - $object_type = get_object_type($object_id, words('sem inst user')); - } - if ($object_type) { - $methodmap = ['sem' => 'Seminar', - 'inst' => 'Institute', - 'fak' => 'Institute', - 'user' => 'User']; - $lr = call_user_func(['LockRule', 'FindBy' . $methodmap[$object_type]], $object_id); - if ($lr) { - self::$lockmap[$object_id] = $lr->getId(); - self::$lockrules[$lr->getId()] = $lr; - } - } - } - return self::$lockrules[self::$lockmap[$object_id]] ?? null; - } - - /** - * checks if an attribute of an entity is locked for the current user - * see self::getLockRuleConfig() for the list of attributes - * - * @param string $object_id id of course, institute or user - * @param string $attribute the name of an lockable attribute - * @param string|null $object_type : The type of object you want to check: "user", "sem" or "inst" - * @return boolean true if attribute is locked for the current user - */ - public static function Check($object_id, $attribute, $object_type = null) - { - $lr = self::getObjectRule($object_id, false, $object_type); - if ($lr) { - return $lr['attributes'][mb_strtolower($attribute)] == 1 && self::CheckLockRulePermission($object_id); - } else { - return false; - } - } - - /** - * checks if given entity is locked for the current user - * - * @param string $object_id id of course, institute or user - * @return boolean true if given entity is locked fpr the current user - */ - public static function CheckLockRulePermission($object_id) - { - $perms = ['autor','tutor','dozent','admin','root','god']; - $lr = self::getObjectRule($object_id); - - if ($lr) { - $pk = array_search($lr->permission, $perms); - $check_perm = $perms[$pk + 1]; - if ($lr->object_type == 'sem') { - return ($lr->permission == 'root' || !$GLOBALS['perm']->have_studip_perm($check_perm, $object_id)); - } - if ($lr->object_type == 'inst') { - return ($lr->permission == 'root' || !$GLOBALS['perm']->have_perm('root')); - } - if ($lr->object_type == 'user') { - return ($lr->permission == 'root' || !$GLOBALS['perm']->have_perm($check_perm)); - } - } - return false; - } - - /** - * returns an array containing all lockable attributes for - * given entity type - * - * @param string $type entity type, one of [sem,inst,user] - * @return array - */ - public static function getLockRuleConfig($type) - { - $groups['basic'] = _("Grunddaten"); - $groups['personnel'] = _("Personen und Einordnung"); - $groups['misc'] = _("weitere Daten"); - $groups['room_time'] = _("Zeiten/Räume"); - $groups['access'] = _("Zugangsberechtigungen"); - $groups['actions'] = _("spezielle Aktionen"); - - $attributes['sem']['veranstaltungsnummer'] = ['name' => _("Veranstaltungsnummer"), 'group' => 'basic']; - $attributes['sem']['seminar_inst'] = ['name' => _("beteiligte Einrichtungen"), 'group' => 'basic']; - $attributes['sem']['name'] = ['name' => _("Name"), 'group' => 'basic']; - $attributes['sem']['untertitel'] = ['name' => _("Untertitel"), 'group' => 'basic']; - $attributes['sem']['status'] = ['name' => _("Status"), 'group' => 'basic']; - $attributes['sem']['beschreibung'] = ['name' => _("Beschreibung"), 'group' => 'basic']; - $attributes['sem']['ort'] = ['name' => _("Ort"), 'group' => 'basic']; - $attributes['sem']['art'] = ['name' => _("Veranstaltungstyp"), 'group' => 'basic']; - $attributes['sem']['ects'] = ['name' => _("ECTS-Punkte"), 'group' => 'basic']; - $attributes['sem']['admission_turnout'] = ['name' => _("Teilnehmendenzahl"), 'group' => 'basic']; - $attributes['sem']['dozent'] = ['name' => _("Lehrende"), 'group' => 'personnel']; - $attributes['sem']['tutor'] = ['name' => _("Tutor/-innen"), 'group' => 'personnel']; - $attributes['sem']['institut_id'] = ['name' => _("Heimateinrichtung"), 'group' => 'personnel']; - $attributes['sem']['sem_tree'] = ['name' => _("Studienbereiche"), 'group' => 'personnel']; - $attributes['sem']['mvv_lvgruppe'] = ['name' => _("Modulzuordnung"), 'group' => 'personnel']; - $attributes['sem']['participants'] = ['name' => _("Personen hinzufügen/löschen"), 'group' => 'personnel']; - $attributes['sem']['groups'] = ['name' => _("Gruppen hinzufügen/löschen"), 'group' => 'personnel']; - $attributes['sem']['sonstiges'] = ['name' => _("Sonstiges"), 'group' => 'misc']; - $attributes['sem']['teilnehmer'] = ['name' => _("Beschreibung des Teilnehmendenkreises"), 'group' => 'misc']; - $attributes['sem']['voraussetzungen'] = ['name' => _("Teilnahmevoraussetzungen"), 'group' => 'misc']; - $attributes['sem']['lernorga'] = ['name' => _("Lernorganisation"), 'group' => 'misc']; - $attributes['sem']['leistungsnachweis'] = ['name' => _("Leistungsnachweis"), 'group' => 'misc']; - $attributes['sem']['room_time'] = ['name' => _("Zeiten/Räume"), 'group' => 'room_time']; - $attributes['sem']['cancelled_dates'] = ['name' => _("Termine ausfallen lassen"), 'group' => 'room_time']; - $attributes['sem']['edit_dates_in_schedule'] = ['name' => _("Erweiterte Termindaten im Ablaufplan ändern"), 'group' => 'room_time']; - $attributes['sem']['admission_endtime'] = ['name' => _("Zeit/Datum der Platzverteilung/Kontingentierung"), 'group' => 'access']; - $attributes['sem']['admission_disable_waitlist'] = ['name' => _("Aktivieren/Deaktivieren der Warteliste"), 'group' => 'access']; - $attributes['sem']['admission_binding'] = ['name' => _("Verbindlichkeit der Anmeldung"), 'group' => 'access']; - $attributes['sem']['admission_type'] = ['name' => _("Typ des Anmeldeverfahrens"), 'group' => 'access']; - $attributes['sem']['admission_prelim'] = ['name' => _("zugelassenene Studiengänge"), 'group' => 'access']; - $attributes['sem']['admission_prelim_txt'] = ['name' => _("Vorläufigkeit der Anmeldungen"), 'group' => 'access']; - $attributes['sem']['admission_disable_waitlist'] = ['name' => _("Hinweistext bei Anmeldungen"), 'group' => 'access']; - $attributes['sem']['admission_starttime'] = ['name' => _("Startzeitpunkt der Anmeldemöglichkeit"), 'group' => 'access']; - $attributes['sem']['admission_endtime_sem'] = ['name' => _("Endzeitpunkt der Anmeldemöglichkeit"), 'group' => 'access']; - $attributes['sem']['lesezugriff'] = ['name' => _("Lesezugriff"), 'group' => 'access']; - $attributes['sem']['schreibzugriff'] = ['name' => _("Schreibzugriff"), 'group' => 'access']; - $attributes['sem']['passwort'] = ['name' => _("Passwort"), 'group' => 'access']; - $attributes['sem']['user_domain'] = ['name' => _("Nutzerdomänen zuordnen"), 'group' => 'access']; - $attributes['sem']['seminar_copy'] = ['name' => _("Veranstaltung kopieren"), 'group' => 'actions']; - $attributes['sem']['seminar_archive'] = ['name' => _("Veranstaltung archivieren"), 'group' => 'actions']; - $attributes['sem']['seminar_visibility'] = ['name' => _("Veranstaltung sichtbar/unsichtbar schalten"), 'group' => 'actions']; - - $attributes['inst']['name'] = ['name' => _("Name"), 'group' => 'basic']; - $attributes['inst']['fakultaets_id'] = ['name' => _("Fakultät"), 'group' => 'basic']; - $attributes['inst']['type'] = ['name' => _("Bezeichnung"), 'group' => 'basic']; - $attributes['inst']['strasse'] = ['name' => _("Straße"), 'group' => 'basic']; - $attributes['inst']['plz'] = ['name' => _("Ort"), 'group' => 'basic']; - $attributes['inst']['telefon'] = ['name' => _("Telefonnummer"), 'group' => 'basic']; - $attributes['inst']['fax'] = ['name' => _("Faxnummer"), 'group' => 'basic']; - $attributes['inst']['email'] = ['name' => _("E-Mail-Adresse"), 'group' => 'basic']; - $attributes['inst']['url'] = ['name' => _("Homepage"), 'group' => 'basic']; - $attributes['inst']['participants'] = ['name' => _("Mitarbeiter hinzufügen/löschen"), 'group' => 'personnel']; - $attributes['inst']['groups'] = ['name' => _("Gruppen hinzufügen/löschen"), 'group' => 'personnel']; - - $attributes['user']['name'] = ['name' => _("Vor- und Nachname"), 'group' => 'basic']; - $attributes['user']['username'] = ['name' => _("Nutzername"), 'group' => 'basic']; - $attributes['user']['password'] = ['name' => _("Passwort"), 'group' => 'basic']; - $attributes['user']['email'] = ['name' => _("E-Mail"), 'group' => 'basic']; - $attributes['user']['title'] = ['name' => _("Titel"), 'group' => 'basic']; - $attributes['user']['gender'] = ['name' => _("Geschlecht"), 'group' => 'basic']; - $attributes['user']['privatnr'] = ['name' => _("Telefon (privat)"), 'group' => 'basic']; - $attributes['user']['privatcell'] = ['name' => _("Mobiltelefon"), 'group' => 'basic']; - $attributes['user']['privadr'] = ['name' => _("Adresse (privat)"), 'group' => 'basic']; - $attributes['user']['hobby'] = ['name' => _("Hobbys"), 'group' => 'basic']; - $attributes['user']['lebenslauf'] = ['name' => _("Lebenslauf"), 'group' => 'basic']; - $attributes['user']['home'] = ['name' => _("Homepage"), 'group' => 'basic']; - $attributes['user']['publi'] = ['name' => _("Schwerpunkte"), 'group' => 'misc']; - $attributes['user']['schwerp'] = ['name' => _("Publikationen"), 'group' => 'misc']; - $attributes['user']['institute_data'] = ['name' => _("Einrichtungsdaten"), 'group' => 'misc']; - - foreach(DataField::getDataFields($type) as $df) { - $attributes[$type][$df->datafield_id] = ['name' => $df->name, 'group' => 'misc']; - } - - return ['groups' => $groups,'attributes' => $attributes[$type]]; - } - -} diff --git a/lib/classes/LockRules.php b/lib/classes/LockRules.php new file mode 100644 index 0000000..59679db --- /dev/null +++ b/lib/classes/LockRules.php @@ -0,0 +1,247 @@ + + * @author André Noack + * @copyright 2011 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +/** +* LockRules.class.php +* +* This class contains only static methods dealing with lock rules +* +*/ + +class LockRules { + + private static $lockmap = []; + private static $lockrules = []; + + /** + * get lockrule object for given id + * from static object pool + * + * @param string $lock_id id of lockrule + * @return LockRule + */ + public static function get($lock_id) + { + if(!array_key_exists($lock_id, self::$lockrules)) { + self::$lockrules[$lock_id] = LockRule::find($lock_id); + } + return self::$lockrules[$lock_id]; + } + + /** + * returns a list of lockrules that can be administrated + * with the given user id + * + * @param string $user_id id of user + * @return array of LockRule objects + */ + public static function getAdministrableSeminarRules($user_id) + { + return array_filter(LockRule::findAllByType('sem'), function ($rule) use ($user_id) { + return $GLOBALS['perm']->get_perm($user_id) === 'root' + || ( + $rule->user_id === $user_id + && !in_array($rule->permission, ['root', 'admin']) + ); + }); + } + + /** + * returns a list of lockrules that can be applied to a course + * with the given user id + * + * @param string $user_id id of user + * @return array of LockRule objects + */ + public static function getAvailableSeminarRules($user_id) + { + return array_filter(LockRule::findAllByType('sem'), function ($rule) use ($user_id) { + return $GLOBALS['perm']->get_perm($user_id) === 'root' + || !in_array($rule->permission, ['root', 'admin']); + }); + } + + /** + * returns the lock rule object for the given id, else null + * + * @param string $object_id id of course, institute or user + * @param bool $renew if true, reloads the rule from database + * @param string|null $object_type : The type of object you want to check: "user", "sem" or "inst" + * @return LockRule + */ + public static function getObjectRule($object_id, $renew = false, $object_type = null) + { + if (!array_key_exists($object_id, self::$lockmap) || $renew) { + self::$lockmap[$object_id] = null; + if ($object_type === null) { + $object_type = get_object_type($object_id, words('sem inst user')); + } + if ($object_type) { + $methodmap = ['sem' => 'Seminar', + 'inst' => 'Institute', + 'fak' => 'Institute', + 'user' => 'User']; + $lr = call_user_func(['LockRule', 'FindBy' . $methodmap[$object_type]], $object_id); + if ($lr) { + self::$lockmap[$object_id] = $lr->getId(); + self::$lockrules[$lr->getId()] = $lr; + } + } + } + return self::$lockrules[self::$lockmap[$object_id]] ?? null; + } + + /** + * checks if an attribute of an entity is locked for the current user + * see self::getLockRuleConfig() for the list of attributes + * + * @param string $object_id id of course, institute or user + * @param string $attribute the name of an lockable attribute + * @param string|null $object_type : The type of object you want to check: "user", "sem" or "inst" + * @return boolean true if attribute is locked for the current user + */ + public static function Check($object_id, $attribute, $object_type = null) + { + $lr = self::getObjectRule($object_id, false, $object_type); + if ($lr) { + return $lr['attributes'][mb_strtolower($attribute)] == 1 && self::CheckLockRulePermission($object_id); + } else { + return false; + } + } + + /** + * checks if given entity is locked for the current user + * + * @param string $object_id id of course, institute or user + * @return boolean true if given entity is locked fpr the current user + */ + public static function CheckLockRulePermission($object_id) + { + $perms = ['autor','tutor','dozent','admin','root','god']; + $lr = self::getObjectRule($object_id); + + if ($lr) { + $pk = array_search($lr->permission, $perms); + $check_perm = $perms[$pk + 1]; + if ($lr->object_type == 'sem') { + return ($lr->permission == 'root' || !$GLOBALS['perm']->have_studip_perm($check_perm, $object_id)); + } + if ($lr->object_type == 'inst') { + return ($lr->permission == 'root' || !$GLOBALS['perm']->have_perm('root')); + } + if ($lr->object_type == 'user') { + return ($lr->permission == 'root' || !$GLOBALS['perm']->have_perm($check_perm)); + } + } + return false; + } + + /** + * returns an array containing all lockable attributes for + * given entity type + * + * @param string $type entity type, one of [sem,inst,user] + * @return array + */ + public static function getLockRuleConfig($type) + { + $groups['basic'] = _("Grunddaten"); + $groups['personnel'] = _("Personen und Einordnung"); + $groups['misc'] = _("weitere Daten"); + $groups['room_time'] = _("Zeiten/Räume"); + $groups['access'] = _("Zugangsberechtigungen"); + $groups['actions'] = _("spezielle Aktionen"); + + $attributes['sem']['veranstaltungsnummer'] = ['name' => _("Veranstaltungsnummer"), 'group' => 'basic']; + $attributes['sem']['seminar_inst'] = ['name' => _("beteiligte Einrichtungen"), 'group' => 'basic']; + $attributes['sem']['name'] = ['name' => _("Name"), 'group' => 'basic']; + $attributes['sem']['untertitel'] = ['name' => _("Untertitel"), 'group' => 'basic']; + $attributes['sem']['status'] = ['name' => _("Status"), 'group' => 'basic']; + $attributes['sem']['beschreibung'] = ['name' => _("Beschreibung"), 'group' => 'basic']; + $attributes['sem']['ort'] = ['name' => _("Ort"), 'group' => 'basic']; + $attributes['sem']['art'] = ['name' => _("Veranstaltungstyp"), 'group' => 'basic']; + $attributes['sem']['ects'] = ['name' => _("ECTS-Punkte"), 'group' => 'basic']; + $attributes['sem']['admission_turnout'] = ['name' => _("Teilnehmendenzahl"), 'group' => 'basic']; + $attributes['sem']['dozent'] = ['name' => _("Lehrende"), 'group' => 'personnel']; + $attributes['sem']['tutor'] = ['name' => _("Tutor/-innen"), 'group' => 'personnel']; + $attributes['sem']['institut_id'] = ['name' => _("Heimateinrichtung"), 'group' => 'personnel']; + $attributes['sem']['sem_tree'] = ['name' => _("Studienbereiche"), 'group' => 'personnel']; + $attributes['sem']['mvv_lvgruppe'] = ['name' => _("Modulzuordnung"), 'group' => 'personnel']; + $attributes['sem']['participants'] = ['name' => _("Personen hinzufügen/löschen"), 'group' => 'personnel']; + $attributes['sem']['groups'] = ['name' => _("Gruppen hinzufügen/löschen"), 'group' => 'personnel']; + $attributes['sem']['sonstiges'] = ['name' => _("Sonstiges"), 'group' => 'misc']; + $attributes['sem']['teilnehmer'] = ['name' => _("Beschreibung des Teilnehmendenkreises"), 'group' => 'misc']; + $attributes['sem']['voraussetzungen'] = ['name' => _("Teilnahmevoraussetzungen"), 'group' => 'misc']; + $attributes['sem']['lernorga'] = ['name' => _("Lernorganisation"), 'group' => 'misc']; + $attributes['sem']['leistungsnachweis'] = ['name' => _("Leistungsnachweis"), 'group' => 'misc']; + $attributes['sem']['room_time'] = ['name' => _("Zeiten/Räume"), 'group' => 'room_time']; + $attributes['sem']['cancelled_dates'] = ['name' => _("Termine ausfallen lassen"), 'group' => 'room_time']; + $attributes['sem']['edit_dates_in_schedule'] = ['name' => _("Erweiterte Termindaten im Ablaufplan ändern"), 'group' => 'room_time']; + $attributes['sem']['admission_endtime'] = ['name' => _("Zeit/Datum der Platzverteilung/Kontingentierung"), 'group' => 'access']; + $attributes['sem']['admission_disable_waitlist'] = ['name' => _("Aktivieren/Deaktivieren der Warteliste"), 'group' => 'access']; + $attributes['sem']['admission_binding'] = ['name' => _("Verbindlichkeit der Anmeldung"), 'group' => 'access']; + $attributes['sem']['admission_type'] = ['name' => _("Typ des Anmeldeverfahrens"), 'group' => 'access']; + $attributes['sem']['admission_prelim'] = ['name' => _("zugelassenene Studiengänge"), 'group' => 'access']; + $attributes['sem']['admission_prelim_txt'] = ['name' => _("Vorläufigkeit der Anmeldungen"), 'group' => 'access']; + $attributes['sem']['admission_disable_waitlist'] = ['name' => _("Hinweistext bei Anmeldungen"), 'group' => 'access']; + $attributes['sem']['admission_starttime'] = ['name' => _("Startzeitpunkt der Anmeldemöglichkeit"), 'group' => 'access']; + $attributes['sem']['admission_endtime_sem'] = ['name' => _("Endzeitpunkt der Anmeldemöglichkeit"), 'group' => 'access']; + $attributes['sem']['lesezugriff'] = ['name' => _("Lesezugriff"), 'group' => 'access']; + $attributes['sem']['schreibzugriff'] = ['name' => _("Schreibzugriff"), 'group' => 'access']; + $attributes['sem']['passwort'] = ['name' => _("Passwort"), 'group' => 'access']; + $attributes['sem']['user_domain'] = ['name' => _("Nutzerdomänen zuordnen"), 'group' => 'access']; + $attributes['sem']['seminar_copy'] = ['name' => _("Veranstaltung kopieren"), 'group' => 'actions']; + $attributes['sem']['seminar_archive'] = ['name' => _("Veranstaltung archivieren"), 'group' => 'actions']; + $attributes['sem']['seminar_visibility'] = ['name' => _("Veranstaltung sichtbar/unsichtbar schalten"), 'group' => 'actions']; + + $attributes['inst']['name'] = ['name' => _("Name"), 'group' => 'basic']; + $attributes['inst']['fakultaets_id'] = ['name' => _("Fakultät"), 'group' => 'basic']; + $attributes['inst']['type'] = ['name' => _("Bezeichnung"), 'group' => 'basic']; + $attributes['inst']['strasse'] = ['name' => _("Straße"), 'group' => 'basic']; + $attributes['inst']['plz'] = ['name' => _("Ort"), 'group' => 'basic']; + $attributes['inst']['telefon'] = ['name' => _("Telefonnummer"), 'group' => 'basic']; + $attributes['inst']['fax'] = ['name' => _("Faxnummer"), 'group' => 'basic']; + $attributes['inst']['email'] = ['name' => _("E-Mail-Adresse"), 'group' => 'basic']; + $attributes['inst']['url'] = ['name' => _("Homepage"), 'group' => 'basic']; + $attributes['inst']['participants'] = ['name' => _("Mitarbeiter hinzufügen/löschen"), 'group' => 'personnel']; + $attributes['inst']['groups'] = ['name' => _("Gruppen hinzufügen/löschen"), 'group' => 'personnel']; + + $attributes['user']['name'] = ['name' => _("Vor- und Nachname"), 'group' => 'basic']; + $attributes['user']['username'] = ['name' => _("Nutzername"), 'group' => 'basic']; + $attributes['user']['password'] = ['name' => _("Passwort"), 'group' => 'basic']; + $attributes['user']['email'] = ['name' => _("E-Mail"), 'group' => 'basic']; + $attributes['user']['title'] = ['name' => _("Titel"), 'group' => 'basic']; + $attributes['user']['gender'] = ['name' => _("Geschlecht"), 'group' => 'basic']; + $attributes['user']['privatnr'] = ['name' => _("Telefon (privat)"), 'group' => 'basic']; + $attributes['user']['privatcell'] = ['name' => _("Mobiltelefon"), 'group' => 'basic']; + $attributes['user']['privadr'] = ['name' => _("Adresse (privat)"), 'group' => 'basic']; + $attributes['user']['hobby'] = ['name' => _("Hobbys"), 'group' => 'basic']; + $attributes['user']['lebenslauf'] = ['name' => _("Lebenslauf"), 'group' => 'basic']; + $attributes['user']['home'] = ['name' => _("Homepage"), 'group' => 'basic']; + $attributes['user']['publi'] = ['name' => _("Schwerpunkte"), 'group' => 'misc']; + $attributes['user']['schwerp'] = ['name' => _("Publikationen"), 'group' => 'misc']; + $attributes['user']['institute_data'] = ['name' => _("Einrichtungsdaten"), 'group' => 'misc']; + + foreach(DataField::getDataFields($type) as $df) { + $attributes[$type][$df->datafield_id] = ['name' => $df->name, 'group' => 'misc']; + } + + return ['groups' => $groups,'attributes' => $attributes[$type]]; + } + +} diff --git a/lib/classes/Loggable.class.php b/lib/classes/Loggable.class.php deleted file mode 100644 index ed0e162..0000000 --- a/lib/classes/Loggable.class.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @copyright 2013 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 3.0 - */ - -/** - * Loggable - * This interface provides necessary functions to use the Stud.IP internal - * logging. - * - * @see StudipLog - */ -interface Loggable -{ - - /** - * This function is used to format the info_template of the - * action used by the given event and its properties. It is the first step - * in the formatting process. It returns a string that will - * be formatted by the replacements for the Stud.IP standard objects - * (e.g. User, Seminar, Institute,...). - * See LogEvent::formatEvent(). - * - * @param LogEvent $event - */ - public static function logFormat(LogEvent $event); - - /** - * This function is used to search for objects related to log events. - * The search has to accept a string as part of the name or the id of the - * object. - * See search functions in StudipLog. - * - * @param string $needle The needle to search for (object id or part of the - * name) - * @param string $action_name The name of the action. - */ - public static function logSearch($needle, $action_name = null); - -} \ No newline at end of file diff --git a/lib/classes/Loggable.php b/lib/classes/Loggable.php new file mode 100644 index 0000000..ed0e162 --- /dev/null +++ b/lib/classes/Loggable.php @@ -0,0 +1,53 @@ + + * @copyright 2013 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 3.0 + */ + +/** + * Loggable + * This interface provides necessary functions to use the Stud.IP internal + * logging. + * + * @see StudipLog + */ +interface Loggable +{ + + /** + * This function is used to format the info_template of the + * action used by the given event and its properties. It is the first step + * in the formatting process. It returns a string that will + * be formatted by the replacements for the Stud.IP standard objects + * (e.g. User, Seminar, Institute,...). + * See LogEvent::formatEvent(). + * + * @param LogEvent $event + */ + public static function logFormat(LogEvent $event); + + /** + * This function is used to search for objects related to log events. + * The search has to accept a string as part of the name or the id of the + * object. + * See search functions in StudipLog. + * + * @param string $needle The needle to search for (object id or part of the + * name) + * @param string $action_name The name of the action. + */ + public static function logSearch($needle, $action_name = null); + +} \ No newline at end of file diff --git a/lib/classes/MVV.class.php b/lib/classes/MVV.class.php deleted file mode 100644 index 517514c..0000000 --- a/lib/classes/MVV.class.php +++ /dev/null @@ -1,849 +0,0 @@ - - * @author Timo Hartge - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.1 - */ - - -require_once 'config/mvv_config.php'; - -class MVV implements Loggable { - - /** - * The global key used by mvv classes to store values in cache. - */ - const CACHE_KEY = 'MVV'; - - /** - * Determines whether the mvv backend is visible. - * - * @return boolean True if backend is visible - */ - public static function isVisible() { - if (!$GLOBALS['perm']) { - return false; - } - if ($GLOBALS['perm']->have_perm('root') || ($GLOBALS['perm']->have_perm('admin') - && RolePersistence::isAssignedRole( - $GLOBALS['user']->id, 'MVVAdmin'))) { - return true; - } - if (RolePersistence::isAssignedRole( - $GLOBALS['user']->id, 'MVVEntwickler')) { - return true; - } - if (RolePersistence::isAssignedRole( - $GLOBALS['user']->id, 'MVVRedakteur')) { - return true; - } - if (RolePersistence::isAssignedRole( - $GLOBALS['user']->id, 'MVVTranslator')) { - return true; - } - if (RolePersistence::isAssignedRole( - $GLOBALS['user']->id, 'MVVFreigabe')) { - return true; - } - if (RolePersistence::isAssignedRole( - $GLOBALS['user']->id, 'MVVLvGruppenAdmin')) { - return true; - } - return false; - } - - /** - * Determines whether the search for modules is visible in the global search. - * - * @return boolean True if backend is visible in search - */ - public static function isVisibleSearch() - { - return isset($GLOBALS['perm']) - && ($GLOBALS['perm']->have_perm('autor') || Config::get()->COURSE_SEARCH_IS_VISIBLE_NOBODY) - && Modul::publicModulesAvailable(); - } - - /** - * This method enriches the logentry templates with data of the mvv classes - * - * @param LogEvent log event entry - * - * @return void - */ - public static function logFormat(LogEvent $event) - { - $templ = $event->action->info_template; - - $table = explode('.', $event->info); - switch ($table[0]) { - - case 'abschluss': - $abschluss = Abschluss::find($event->affected_range_id); - if ($abschluss) { - $url = URLHelper::getURL('dispatch.php/fachabschluss/abschluesse/details/' . $abschluss->getId(), [], true); - $templ = str_replace('%abschluss(%affected)', '' . htmlReady($abschluss->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_abschl_kategorie': - $abskategorie = AbschlussKategorie::find($event->affected_range_id); - if ($abskategorie) { - $url = URLHelper::getURL('dispatch.php/fachabschluss/kategorien/details/' . $abskategorie->getId(), [], true); - $templ = str_replace('%abskategorie(%affected)', '' . htmlReady($abskategorie->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_abschl_zuord': - $abschluss = Abschluss::find($event->affected_range_id); - if ($abschluss) { - $url = URLHelper::getURL('dispatch.php/fachabschluss/abschluesse/details/' . $abschluss->getId(), [], true); - $templ = str_replace('%abschluss(%affected)', '' . htmlReady($abschluss->getDisplayName()) . '', $templ); - } - $co_kategorie = AbschlussKategorie::find($event->coaffected_range_id); - if ($co_kategorie) { - $url = URLHelper::getURL('dispatch.php/fachabschluss/kategorien/details/' . $co_kategorie->getId(), [], true); - $templ = str_replace('%abskategorie(%coaffected)', '' . htmlReady($co_kategorie->getDisplayName()) . '', $templ); - } - break; - - case 'fach': - case 'mvv_fach_inst': - $fach = Fach::find($event->affected_range_id); - if ($fach) { - $url = URLHelper::getURL('dispatch.php/fachabschluss/faecher/details/' . $fach->getId(), [], true); - $templ = str_replace('%fach(%affected)', '' . htmlReady($fach->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_lvgruppe': - case 'mvv_lvgruppe_seminar': - $lvgruppe = Lvgruppe::find($event->affected_range_id); - if ($lvgruppe) { - $url = URLHelper::getURL('dispatch.php/lvgruppen/lvgruppen/details/' . $lvgruppe->getId(), [], true); - $templ = str_replace('%lvgruppe(%affected)', '' . htmlReady($lvgruppe->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_lvgruppe_modulteil': - $lvgruppe = Lvgruppe::find($event->affected_range_id); - if ($lvgruppe) { - $url = URLHelper::getURL('dispatch.php/lvgruppen/lvgruppen/details/' . $lvgruppe->getId(), [], true); - $templ = str_replace('%lv(%affected)', '' . htmlReady($lvgruppe->getDisplayName()) . '', $templ); - } - $co_modulteil = Modulteil::find($event->coaffected_range_id); - if ($co_modulteil) { - $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $co_modulteil->getId(), [], true); - $templ = str_replace('%modulteil(%coaffected)', '' . htmlReady($co_modulteil->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_modul': - case 'mvv_modul_user': - case 'mvv_modul_inst': - $modul = Modul::find($event->affected_range_id); - if ($modul) { - $url = URLHelper::getURL('dispatch.php/module/module/details/' . $modul->getId(), [], true); - $templ = str_replace('%modul(%affected)', '' . htmlReady($modul->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_modulteil': - $modulteil = Modulteil::find($event->affected_range_id); - if ($modulteil) { - $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modulteil->getId(), [], true); - $templ = str_replace('%modulteil(%affected)', '' . htmlReady($modulteil->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_modulteil_deskriptor': - $modulteil_desk = ModulteilDeskriptor::find($event->affected_range_id); - if ($modulteil_desk) { - $modteil = Modulteil::find($modulteil_desk->modulteil_id); - $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modteil->getId(), [], true); - $templ = str_replace('%modulteildesk(%affected)', 'in Modulteil ' . htmlReady($modteil->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_modulteil_language': - $modulteil = Modulteil::find($event->affected_range_id); - if ($modulteil) { - $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modulteil->getId(), [], true); - $templ = str_replace('%modulteil(%affected)', '' . htmlReady($modulteil->getDisplayName()) . '', $templ); - } - $co_mtlanguage = ModulteilLanguage::find([ - $event->affected_range_id, - $event->coaffected_range_id - ]); - if ($co_mtlanguage) { - $templ = str_replace('%language(%coaffected)', htmlReady($co_mtlanguage->getDisplayName()), $templ); - } - break; - - case 'mvv_modulteil_stgteilabschnitt': - $modulteil = Modulteil::find($event->affected_range_id); - if ($modulteil) { - $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modulteil->getId(), [], true); - $templ = str_replace('%modulteil(%affected)', '' . htmlReady($modulteil->getDisplayName()) . '', $templ); - } - $co_stgteilabs = StgteilAbschnitt::find($event->coaffected_range_id); - if ($co_stgteilabs) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/details_abschnitt/' . $co_stgteilabs->getId(), [], true); - $templ = str_replace('%stgteilabs(%coaffected)', '' . htmlReady($co_stgteilabs->getDisplayName()) . '', $templ); - $templ = str_replace('%fachsem', $event->dbg_info, $templ); - } - break; - - case 'mvv_modul_deskriptor': - $modul_desk = ModulDeskriptor::find($event->affected_range_id); - if ($modul_desk) { - $mod = Modul::find($modul_desk->modul_id); - $url = URLHelper::getURL('dispatch.php/module/module/details/' . $mod->getId(), [], true); - $templ = str_replace('%moduldesk(%affected)', 'in Modul ' . htmlReady($mod->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_modul_language': - $modul = Modul::find($event->affected_range_id); - if ($modul) { - $url = URLHelper::getURL('dispatch.php/module/module/details/' . $modul->getId(), [], true); - $templ = str_replace('%modul(%affected)', '' . htmlReady($modul->getDisplayName()) . '', $templ); - } - $co_mlanguage = ModulLanguage::find([ - $event->affected_range_id, - $event->coaffected_range_id - ]); - if ($co_mlanguage) { - $templ = str_replace('%language(%coaffected)', htmlReady($co_mlanguage->getDisplayName()), $templ); - } - break; - - case 'mvv_stgteil': - case 'mvv_fachberater': - $stgteil = StudiengangTeil::find($event->affected_range_id); - if ($stgteil) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengangteile/details_versionen/' . $stgteil->getId(), [], true); - $templ = str_replace('%stgteil(%affected)', '' . htmlReady($stgteil->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_stgteilabschnitt': - $stgteilabs = StgteilAbschnitt::find($event->affected_range_id); - if ($stgteilabs) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/details_abschnitt/' . $stgteilabs->getId(), [], true); - $templ = str_replace('%stgteilabs(%affected)', '' . htmlReady($stgteilabs->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_stgteilabschnitt_modul': - $stgteilabs = StgteilAbschnitt::find($event->affected_range_id); - if ($stgteilabs) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/details_abschnitt/' . $stgteilabs->getId(), [], true); - $templ = str_replace('%stgteilabs(%affected)', '' . htmlReady($stgteilabs->getDisplayName()) . '', $templ); - } - $co_modul = Modul::find($event->coaffected_range_id); - if ($co_modul) { - $url = URLHelper::getURL('dispatch.php/module/module/details/' . $co_modul->getId(), [], true); - $templ = str_replace('%modul(%coaffected)', '' . htmlReady($co_modul->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_stgteilversion': - $version = StgteilVersion::find($event->affected_range_id); - if ($version) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/abschnitte/' . $version->getId(), [], true); - $templ = str_replace('%version(%affected)', '' . htmlReady($version->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_stgteil_bez': - $stgteilbez = StgteilBezeichnung::find($event->affected_range_id); - if ($stgteilbez) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/stgteilbezeichnungen/details/' . $stgteilbez->getId(), [], true); - $templ = str_replace('%stgteilbez(%affected)', '' . htmlReady($stgteilbez->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_stg_stgteil': - $stg = Studiengang::find($event->affected_range_id); - if ($stg) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $stg->getId(), [], true); - $templ = str_replace('%stg(%affected)', '' . htmlReady($stg->getDisplayName()) . '', $templ); - } - $co_stgteil = StudiengangTeil::find($event->coaffected_range_id); - if ($co_stgteil) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengangteile/details_versionen/' . $co_stgteil->getId(), [], true); - $templ = str_replace('%stgteil(%coaffected)', '' . htmlReady($co_stgteil->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_studiengang': - $stg = Studiengang::find($event->affected_range_id); - if ($stg) { - $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $stg->getId(), [], true); - $templ = str_replace('%stg(%affected)', '' . htmlReady($stg->getDisplayName()) . '', $templ); - } - break; - - case 'mvv_contacts': - $contact = MvvContact::find($event->affected_range_id); - if ($contact) { - $url = URLHelper::getLink('dispatch.php/shared/contacts/details/index/' . $contact->id); - $templ = str_replace( - '%contact(%affected)', - '' . htmlReady($contact->getDisplayName()) . '', - $templ - ); - } - break; - - case 'mvv_contacts_ranges': - $contact_range = MvvContactRange::find($event->dbg_info); - if ($contact_range) { - if ($contact_range->contact) { - $url_affected = URLHelper::getLink( - 'dispatch.php/shared/contacts/details/index/' . $contact_range->contact->id); - $templ = str_replace( - '%contact(%affected)', - '' . htmlReady($contact_range->contact->getDisplayName()) . '', - $templ - ); - } - $range = null; - switch ($contact_range->range_type) { - case 'Modul': - $range = Modul::find($event->coaffected_range_id); - $url_coaffected = URLHelper::getLink( - 'dispatch.php/module/module/details/' . $range->id, - [], - true - ); - break; - case 'Studiengang': - $range = Studiengang::find($event->coaffected_range_id); - $url_coaffected = URLHelper::getLink( - 'dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $range->id, - [], - true - ); - break; - case 'StudiengangTeil': - $range = StudiengangTeil::find($event->coaffected_range_id); - $url_coaffected = URLHelper::getLink( - 'dispatch.php/studiengaenge/studiengangteile/details_versionen/' . $range->id, - [], - true - ); - - } - if ($range) { - $templ = str_replace( - '%range(%coaffected)', - '' . htmlReady($range->getDisplayName()) . '', - $templ - ); - } - } - break; - - case 'mvv_extern_contacts': - $extern_contact = MvvExternContact::find($event->affected_range_id); - if ($extern_contact) { - $url = URLHelper::getLink('dispatch.php/shared/contacts/details/index/' . $extern_contact->id); - $templ = str_replace( - '%contact(%affected)', - '' . htmlReady($extern_contact->getDisplayName()) . '', - $templ - ); - } - break; - - case 'mvv_files': - $file = MvvFile::find($event->affected_range_id); - if ($file) { - $url = URLHelper::getLink('dispatch.php/materialien/files/index/' . $file->id); - $templ = str_replace( - '%file(%affected)', - '' . htmlReady($file->getDisplayName()) . '', - $templ - ); - } - break; - - case 'mvv_files_filerefs': - $fileref = MvvFileFileref::find($event->affected_range_id); - if ($fileref) { - $url = URLHelper::getLink('dispatch.php/materialien/index/' . $fileref->mvvfile_id); - $templ = str_replace( - '%fileref(%affected)', - '' . htmlReady($fileref->getDisplayName()) . '', - $templ - ); - } - break; - - case 'mvv_files_ranges': - $file_range = MvvFileRange::find([ - $event->affected_range_id, - $event->coaffected_range_id - ]); - if ($file_range) { - if ($file_range->mvv_file) { - $url_affected = URLHelper::getLink( - 'dispatch.php/materialien/index/' . $file_range->mvvfile_id - ); - $templ = str_replace( - '%fileref(%affected)', - '' . htmlReady($file_range->mvv_file->getDisplayName()) . '', - $templ - ); - } - $range = null; - switch ($file_range->range_type) { - case 'Studiengang': - $range = Studiengang::find($event->coaffected_range_id); - $url_coaffected = URLHelper::getLink( - 'dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $range->id, - [], - true - ); - break; - case 'StgteilVersion': - $range = StgteilVersion::find($event->coaffected_range_id); - $url_coaffected = URLHelper::getLink( - 'dispatch.php/studiengaenge/studiengangteile/version/' . $range->id, - [], - true - ); - - } - if ($range) { - $templ = str_replace( - '%range(%coaffected)', - '' . htmlReady($range->getDisplayName()) . '', - $templ - ); - } - } - break; - - default: - break; - } - - // specials - // Benutzergruppen im Modul z.B. Modulverantwortlicher - $templ = str_replace('%gruppe', $event->dbg_info, $templ); - // Objekt konnte nicht eingesetzt werden da es vermutlich nicht mehr existiert, beim delete landet der alte Bezeichner im debug - $templ = str_replace('%modul(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%modulteil(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%modulteildesk(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%moduldesk(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%stg(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%stgteil(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%stgteilabs(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%version(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%stgteilbez(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%lvgruppe(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%fach(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%abschluss(%affected)', $event->dbg_info, $templ); - $templ = str_replace('%abskategorie(%affected)', $event->dbg_info, $templ); - - $templ = str_replace('%abskategorie(%coaffected)', $event->dbg_info, $templ); - $templ = str_replace('%modulteil(%coaffected)', $event->dbg_info, $templ); - $templ = str_replace('%modul(%coaffected)', $event->dbg_info, $templ); - $templ = str_replace('%language(%coaffected)', $event->dbg_info, $templ); - $templ = str_replace('%stgteilabs(%coaffected)', $event->dbg_info, $templ); - $templ = str_replace('%stgteil(%coaffected)', $event->dbg_info, $templ); - $templ = str_replace('%contact', $event->dbg_info, $templ); - - return $templ; - } - - /** - * This method searches the log-entries for log-actions of the mvv classes. - * Used by search function on log page. - * - * @param string $needle The search term. - * @param string $action_name The name of the log action. - * - * @return array Found log events. - */ - public static function logSearch($needle, $action_name = null) - { - $result = []; - - $modul_actions = [ - 'MVV_MODUL_NEW', - 'MVV_MODUL_UPDATE', - 'MVV_MODUL_DEL', - 'MVV_MODUL_DESK_NEW', - 'MVV_MODUL_DESK_UPDATE', - 'MVV_MODUL_DESK_DEL', - 'MVV_MODULINST_NEW', - 'MVV_MODULINST_DEL', - 'MVV_MODULINST_UPDATE', - 'MVV_MODUL_USER_NEW', - 'MVV_MODUL_USER_DEL', - 'MVV_MODUL_USER_UPDATE', - 'MVV_MODUL_LANG_NEW', - 'MVV_MODUL_LANG_DEL', - 'MVV_MODUL_LANG_UPDATE', - 'MVV_MODULTEIL_STGTEILABS_NEW', - 'MVV_MODULTEIL_STGTEILABS_DEL', - 'MVV_MODULTEIL_STGTEILABS_UPDATE', - 'MVV_STGTEILABS_MODUL_NEW', - 'MVV_STGTEILABS_MODUL_DEL', - 'MVV_STGTEILABS_MODUL_UPDATE' - ]; - - if (in_array($action_name, $modul_actions)) { - $module = Modul::findBySQL( - "code LIKE CONCAT('%', :needle, '%') OR modul_id = :needle" - ); - $deskriptoren = ModulDeskriptor::findBySql( - "bezeichnung LIKE CONCAT('%', :needle, '%') - OR deskriptor_id = :needle", - [':needle' => $needle] - ); - foreach ($module as $modul) { - $result[] = [ - $modul->getId(), - $modul->getDisplayName() - ]; - } - foreach ($deskriptoren as $desk) { - $modul = Modul::find($desk->modul_id); - $result[] = [ - $modul->getId(), - $modul->getDisplayName() - ]; - } - } - - $modulteile_actions = [ - 'MVV_MODULTEIL_NEW', - 'MVV_MODULTEIL_UPDATE', - 'MVV_MODULTEIL_DEL', - 'MVV_MODULTEIL_DESK_NEW', - 'MVV_MODULTEIL_DESK_UPDATE', - 'MVV_MODULTEIL_DESK_DEL', - 'MVV_MODULTEIL_LANG_NEW', - 'MVV_MODULTEIL_LANG_DEL', - 'MVV_MODULTEIL_LANG_UPDATE', - 'MVV_LVMODULTEIL_NEW', - 'MVV_LVMODULTEIL_DEL', - 'MVV_LVMODULTEIL_UPDATE' - ]; - - if (in_array($action_name, $modulteile_actions)) { - $deskriptoren = ModulDeskriptor::findBySql( - "bezeichnung LIKE CONCAT('%', :needle, '%') - OR deskriptor_id = :needle", - [':needle' => $needle] - ); - foreach ($deskriptoren as $desk) { - $modulteil = Modulteil::find($desk->modulteil_id); - $result[] = [ - $modulteil->getId(), - $modulteil->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_STUDIENGANG_NEW', - 'MVV_STUDIENGANG_UPDATE', - 'MVV_STUDIENGANG_DEL', - 'MVV_STG_STGTEIL_NEW', - 'MVV_STG_STGTEIL_DEL', - 'MVV_STG_STGTEIL_UPDATE' - ])) { - $stg = Studiengang::findBySQL( - "name LIKE CONCAT('%', :needle, '%') - OR studiengang_id = :needle", - [':needle' => $needle] - ); - foreach ($stg as $studiengang) { - $result[] = [ - $studiengang->getId(), - $studiengang->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_STGTEIL_NEW', - 'MVV_STGTEIL_UPDATE', - 'MVV_STGTEIL_DEL', - 'MVV_FACHBERATER_NEW', - 'MVV_FACHBERATER_UPDATE', - 'MVV_FACHBERATER_DEL' - ])) { - $stgteile = StudiengangTeil::findBySQL( - "zusatz LIKE CONCAT('%', :needle, '%') - OR zusatz_en LIKE CONCAT('%', :needle, '%') - OR stgteil_id = :needle", - [':needle' => $needle] - ); - foreach ($stgteile as $stgteil) { - $result[] = [ - $stgteil->getId(), - $stgteil->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_STGTEILVERSION_NEW', - 'MVV_STGTEILVERSION_UPDATE', - 'MVV_STGTEILVERSION_DEL' - ])) { - $versionen = StgteilVersion::findBySQL( - "code LIKE CONCAT('%', :needle, '%') - OR version_id = :needle - OR stgteil_id = :needle", - [':needle' => $needle] - ); - foreach ($versionen as $version) { - $result[] = [ - $version->getId(), - $version->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_STGTEILBEZ_NEW', - 'MVV_STGTEILBEZ_UPDATE', - 'MVV_STGTEILBEZ_DEL' - ])) { - $stgbez = StgteilBezeichnung::findBySQL( - "name LIKE CONCAT('%', :needle, '%') - OR name_en LIKE CONCAT('%', :needle, '%') - OR stgteil_bez_id = :needle", - [':needle' => $needle] - ); - foreach ($stgbez as $bez) { - $result[] = [ - $bez->getId(), - $bez->getDisplayName() - ]; - } - } - - $stgteil_actions = [ - 'MVV_STGTEILABS_NEW', - 'MVV_STGTEILABS_UPDATE', - 'MVV_STGTEILABS_DEL', - 'MVV_MODULTEIL_STGTEILABS_NEW', - 'MVV_MODULTEIL_STGTEILABS_DEL', - 'MVV_MODULTEIL_STGTEILABS_UPDATE', - 'MVV_STG_STGTEIL_NEW', - 'MVV_STG_STGTEIL_DEL', - 'MVV_STG_STGTEIL_UPDATE', - 'MVV_STGTEILABS_MODUL_NEW', - 'MVV_STGTEILABS_MODUL_DEL', - 'MVV_STGTEILABS_MODUL_UPDATE' - ]; - - if (in_array($action_name, $stgteil_actions)) { - $stgteilabs = Lvgruppe::findBySQL( - "name LIKE CONCAT('%', :needle, '%') - OR name_en LIKE CONCAT('%', :needle, '%') - OR abschnitt_id = :needle", - [':needle' => $needle] - ); - foreach ($stgteilabs as $abschnitt) { - $result[] = [ - $abschnitt->getId(), - $abschnitt->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_LVGRUPPE_NEW', - 'MVV_LVGRUPPE_DEL', - 'MVV_LVGRUPPE_UPDATE', - 'MVV_LVMODULTEIL_NEW', - 'MVV_LVMODULTEIL_DEL', - 'MVV_LVMODULTEIL_UPDATE', - 'MVV_LVSEMINAR_NEW', - 'MVV_LVSEMINAR_DEL', - 'MVV_LVSEMINAR_UPDATE' - ])) { - $lvgruppen = Lvgruppe::findBySQL( - "name LIKE CONCAT('%', :needle, '%') - OR name_en LIKE CONCAT('%', :needle, '%') - OR lvgruppe_id = :needle", - [':needle' => $needle] - ); - foreach ($lvgruppen as $lvgruppe) { - $result[] = [ - $lvgruppe->getId(), - $lvgruppe->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_FACH_NEW', - 'MVV_FACH_UPDATE', - 'MVV_FACH_DEL', - 'MVV_FACHINST_NEW', - 'MVV_FACHINST_DEL', - 'MVV_FACHINST_UPDATE' - ])) { - $faecher = Fach::findBySQL( - "name LIKE CONCAT('%', :needle, '%') - OR name_en LIKE CONCAT('%', :needle, '%') - OR fach_id = :needle", - [':needle' => $needle] - ); - foreach ($faecher as $fach) { - $result[] = [ - $fach->getId(), - $fach->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_ABSCHLUSS_NEW', - 'MVV_ABSCHLUSS_UPDATE', - 'MVV_ABSCHLUSS_DEL', - 'MVV_ABS_ZUORD_NEW', - 'MVV_ABS_ZUORD_DEL', - 'MVV_ABS_ZUORD_UPDATE' - ])) { - $abschluesse = Abschluss::findBySQL( - "name LIKE CONCAT('%', :needle, '%') - OR name_en LIKE CONCAT('%', :needle, '%') - OR abschluss_id = :needle", - [':needle' => $needle] - ); - foreach ($abschluesse as $abschluss) { - $result[] = [ - $abschluss->getId(), - $abschluss->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_KATEGORIE_NEW', - 'MVV_KATEGORIE_UPDATE', - 'MVV_KATEGORIE_DEL', - 'MVV_ABS_ZUORD_NEW', - 'MVV_ABS_ZUORD_DEL', - 'MVV_ABS_ZUORD_UPDATE' - ])) { - $abskategorien = AbschlussKategorie::findBySQL( - "name LIKE CONCAT('%', :needle, '%') - OR name_en LIKE CONCAT('%', :needle, '%') - OR kategorie_id = :needle", - [':needle' => $needle] - ); - foreach ($abskategorien as $abskategorie) { - $result[] = [ - $abskategorie->getId(), - $abskategorie->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_CONTACT_NEW', - 'MVV_CONTACT_UPDATE', - 'MVV_CONTACT_DELETE', - 'MVV_CONTACT_RANGE_NEW', - 'MVV_CONTACT_RANGE_UPDATE', - 'MVV_CONTACT_RANGE_DELETE', - 'MVV_CONTACT_EXTERN_NEW', - 'MVV_CONTACT_EXTERN_UPDATE', - 'MVV_CONTACT_EXTERN_DELETE' - ])) { - $contacts_intern = MvvContact::findBySQL( - "LEFT JOIN `auth_user_md5` - ON `mvv_contacts`.`contact_id` = `auth_user_md5`.`user_id` - WHERE `auth_user_md5`.`username` LIKE CONCAT('%', :needle, '%') - OR `auth_user_md5`.`nachname` LIKE CONCAT('%', :needle, '%') - OR `auth_user_md5`.`email` = :needle", - [':needle' => $needle] - ); - $contacts_extern = MvvContact::findBySQL( - "LEFT JOIN `mvv_extern_contacts` - ON `mvv_contacts`.`contact_id` = `mvv_extern_contacts`.`extern_contact_id` - WHERE `mvv_extern_contacts`.`name` = :needle - OR `mvv_extern_contacts`.`mail` = :needle", - [':needle' => $needle] - ); - $contacts = array_merge($contacts_intern, $contacts_extern); - foreach ($contacts as $contact) { - $result[] = [ - $contact->id, - $contact->getDisplayName() - ]; - } - } - - if (in_array($action_name, [ - 'MVV_FILE_NEW', - 'MVV_FILE_UPDATE', - 'MVV_FILE_DELETE', - 'MVV_FILE_RANGE_NEW', - 'MVV_FILE_RANGE_UPDATE', - 'MVV_FILE_RANGE_DELETE', - 'MVV_FILE_FILEREF_NEW', - 'MVV_FILE_FILEREF_UPDATE', - 'MVV_FILE_FILEREF_DEL' - ])) { - $files = MvvFile::findBySQL( - "LEFT JOIN `mvv_files_filerefs` USING (`mvvfile_id`) - LEFT JOIN `file_refs` - ON `mvv_files_filerefs`.`fileref_id` = `file_refs`.`id` - LEFT JOIN `files` - ON `file_refs`.`file_id` = `files`.`id` - WHERE `mvv_files`.`tags` LIKE CONCAT('%', :needle, '%') - OR `mvv_files_filerefs`.`name` LIKE CONCAT('%', :needle, '%') - OR `file_refs`.`name` LIKE CONCAT('%', :needle, '%') - OR `files`.`name` = :needle - OR `files`.`author_name` = :needle", - [':needle' => $needle] - ); - foreach ($files as $file) { - $result[$file->id] = [ - $file->id, - $file->getDisplayName() - ]; - } - } - - return $result; - } - - /** - * Returns imagepath for given language, used by MVV - * First tries $GLOBALS['CONTENT_LANGUAGES'], if not defined returns hardcoded path - * - * @param string $language e.g. 'DE' - * @return string path to language icon - */ - public static function getContentLanguageImagePath($language): string - { - $content_language = $GLOBALS['MVV_MODUL_DESKRIPTOR']['SPRACHE']['values'][$language]['content_language']; - return 'languages/' . ($GLOBALS['CONTENT_LANGUAGES'][$content_language]['picture'] ?? 'lang_' . mb_strtolower($language) . '.gif'); - } - -} diff --git a/lib/classes/MVV.php b/lib/classes/MVV.php new file mode 100644 index 0000000..517514c --- /dev/null +++ b/lib/classes/MVV.php @@ -0,0 +1,849 @@ + + * @author Timo Hartge + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.1 + */ + + +require_once 'config/mvv_config.php'; + +class MVV implements Loggable { + + /** + * The global key used by mvv classes to store values in cache. + */ + const CACHE_KEY = 'MVV'; + + /** + * Determines whether the mvv backend is visible. + * + * @return boolean True if backend is visible + */ + public static function isVisible() { + if (!$GLOBALS['perm']) { + return false; + } + if ($GLOBALS['perm']->have_perm('root') || ($GLOBALS['perm']->have_perm('admin') + && RolePersistence::isAssignedRole( + $GLOBALS['user']->id, 'MVVAdmin'))) { + return true; + } + if (RolePersistence::isAssignedRole( + $GLOBALS['user']->id, 'MVVEntwickler')) { + return true; + } + if (RolePersistence::isAssignedRole( + $GLOBALS['user']->id, 'MVVRedakteur')) { + return true; + } + if (RolePersistence::isAssignedRole( + $GLOBALS['user']->id, 'MVVTranslator')) { + return true; + } + if (RolePersistence::isAssignedRole( + $GLOBALS['user']->id, 'MVVFreigabe')) { + return true; + } + if (RolePersistence::isAssignedRole( + $GLOBALS['user']->id, 'MVVLvGruppenAdmin')) { + return true; + } + return false; + } + + /** + * Determines whether the search for modules is visible in the global search. + * + * @return boolean True if backend is visible in search + */ + public static function isVisibleSearch() + { + return isset($GLOBALS['perm']) + && ($GLOBALS['perm']->have_perm('autor') || Config::get()->COURSE_SEARCH_IS_VISIBLE_NOBODY) + && Modul::publicModulesAvailable(); + } + + /** + * This method enriches the logentry templates with data of the mvv classes + * + * @param LogEvent log event entry + * + * @return void + */ + public static function logFormat(LogEvent $event) + { + $templ = $event->action->info_template; + + $table = explode('.', $event->info); + switch ($table[0]) { + + case 'abschluss': + $abschluss = Abschluss::find($event->affected_range_id); + if ($abschluss) { + $url = URLHelper::getURL('dispatch.php/fachabschluss/abschluesse/details/' . $abschluss->getId(), [], true); + $templ = str_replace('%abschluss(%affected)', '' . htmlReady($abschluss->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_abschl_kategorie': + $abskategorie = AbschlussKategorie::find($event->affected_range_id); + if ($abskategorie) { + $url = URLHelper::getURL('dispatch.php/fachabschluss/kategorien/details/' . $abskategorie->getId(), [], true); + $templ = str_replace('%abskategorie(%affected)', '' . htmlReady($abskategorie->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_abschl_zuord': + $abschluss = Abschluss::find($event->affected_range_id); + if ($abschluss) { + $url = URLHelper::getURL('dispatch.php/fachabschluss/abschluesse/details/' . $abschluss->getId(), [], true); + $templ = str_replace('%abschluss(%affected)', '' . htmlReady($abschluss->getDisplayName()) . '', $templ); + } + $co_kategorie = AbschlussKategorie::find($event->coaffected_range_id); + if ($co_kategorie) { + $url = URLHelper::getURL('dispatch.php/fachabschluss/kategorien/details/' . $co_kategorie->getId(), [], true); + $templ = str_replace('%abskategorie(%coaffected)', '' . htmlReady($co_kategorie->getDisplayName()) . '', $templ); + } + break; + + case 'fach': + case 'mvv_fach_inst': + $fach = Fach::find($event->affected_range_id); + if ($fach) { + $url = URLHelper::getURL('dispatch.php/fachabschluss/faecher/details/' . $fach->getId(), [], true); + $templ = str_replace('%fach(%affected)', '' . htmlReady($fach->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_lvgruppe': + case 'mvv_lvgruppe_seminar': + $lvgruppe = Lvgruppe::find($event->affected_range_id); + if ($lvgruppe) { + $url = URLHelper::getURL('dispatch.php/lvgruppen/lvgruppen/details/' . $lvgruppe->getId(), [], true); + $templ = str_replace('%lvgruppe(%affected)', '' . htmlReady($lvgruppe->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_lvgruppe_modulteil': + $lvgruppe = Lvgruppe::find($event->affected_range_id); + if ($lvgruppe) { + $url = URLHelper::getURL('dispatch.php/lvgruppen/lvgruppen/details/' . $lvgruppe->getId(), [], true); + $templ = str_replace('%lv(%affected)', '' . htmlReady($lvgruppe->getDisplayName()) . '', $templ); + } + $co_modulteil = Modulteil::find($event->coaffected_range_id); + if ($co_modulteil) { + $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $co_modulteil->getId(), [], true); + $templ = str_replace('%modulteil(%coaffected)', '' . htmlReady($co_modulteil->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_modul': + case 'mvv_modul_user': + case 'mvv_modul_inst': + $modul = Modul::find($event->affected_range_id); + if ($modul) { + $url = URLHelper::getURL('dispatch.php/module/module/details/' . $modul->getId(), [], true); + $templ = str_replace('%modul(%affected)', '' . htmlReady($modul->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_modulteil': + $modulteil = Modulteil::find($event->affected_range_id); + if ($modulteil) { + $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modulteil->getId(), [], true); + $templ = str_replace('%modulteil(%affected)', '' . htmlReady($modulteil->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_modulteil_deskriptor': + $modulteil_desk = ModulteilDeskriptor::find($event->affected_range_id); + if ($modulteil_desk) { + $modteil = Modulteil::find($modulteil_desk->modulteil_id); + $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modteil->getId(), [], true); + $templ = str_replace('%modulteildesk(%affected)', 'in Modulteil ' . htmlReady($modteil->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_modulteil_language': + $modulteil = Modulteil::find($event->affected_range_id); + if ($modulteil) { + $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modulteil->getId(), [], true); + $templ = str_replace('%modulteil(%affected)', '' . htmlReady($modulteil->getDisplayName()) . '', $templ); + } + $co_mtlanguage = ModulteilLanguage::find([ + $event->affected_range_id, + $event->coaffected_range_id + ]); + if ($co_mtlanguage) { + $templ = str_replace('%language(%coaffected)', htmlReady($co_mtlanguage->getDisplayName()), $templ); + } + break; + + case 'mvv_modulteil_stgteilabschnitt': + $modulteil = Modulteil::find($event->affected_range_id); + if ($modulteil) { + $url = URLHelper::getURL('dispatch.php/module/module/modulteil_lvg/' . $modulteil->getId(), [], true); + $templ = str_replace('%modulteil(%affected)', '' . htmlReady($modulteil->getDisplayName()) . '', $templ); + } + $co_stgteilabs = StgteilAbschnitt::find($event->coaffected_range_id); + if ($co_stgteilabs) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/details_abschnitt/' . $co_stgteilabs->getId(), [], true); + $templ = str_replace('%stgteilabs(%coaffected)', '' . htmlReady($co_stgteilabs->getDisplayName()) . '', $templ); + $templ = str_replace('%fachsem', $event->dbg_info, $templ); + } + break; + + case 'mvv_modul_deskriptor': + $modul_desk = ModulDeskriptor::find($event->affected_range_id); + if ($modul_desk) { + $mod = Modul::find($modul_desk->modul_id); + $url = URLHelper::getURL('dispatch.php/module/module/details/' . $mod->getId(), [], true); + $templ = str_replace('%moduldesk(%affected)', 'in Modul ' . htmlReady($mod->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_modul_language': + $modul = Modul::find($event->affected_range_id); + if ($modul) { + $url = URLHelper::getURL('dispatch.php/module/module/details/' . $modul->getId(), [], true); + $templ = str_replace('%modul(%affected)', '' . htmlReady($modul->getDisplayName()) . '', $templ); + } + $co_mlanguage = ModulLanguage::find([ + $event->affected_range_id, + $event->coaffected_range_id + ]); + if ($co_mlanguage) { + $templ = str_replace('%language(%coaffected)', htmlReady($co_mlanguage->getDisplayName()), $templ); + } + break; + + case 'mvv_stgteil': + case 'mvv_fachberater': + $stgteil = StudiengangTeil::find($event->affected_range_id); + if ($stgteil) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengangteile/details_versionen/' . $stgteil->getId(), [], true); + $templ = str_replace('%stgteil(%affected)', '' . htmlReady($stgteil->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_stgteilabschnitt': + $stgteilabs = StgteilAbschnitt::find($event->affected_range_id); + if ($stgteilabs) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/details_abschnitt/' . $stgteilabs->getId(), [], true); + $templ = str_replace('%stgteilabs(%affected)', '' . htmlReady($stgteilabs->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_stgteilabschnitt_modul': + $stgteilabs = StgteilAbschnitt::find($event->affected_range_id); + if ($stgteilabs) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/details_abschnitt/' . $stgteilabs->getId(), [], true); + $templ = str_replace('%stgteilabs(%affected)', '' . htmlReady($stgteilabs->getDisplayName()) . '', $templ); + } + $co_modul = Modul::find($event->coaffected_range_id); + if ($co_modul) { + $url = URLHelper::getURL('dispatch.php/module/module/details/' . $co_modul->getId(), [], true); + $templ = str_replace('%modul(%coaffected)', '' . htmlReady($co_modul->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_stgteilversion': + $version = StgteilVersion::find($event->affected_range_id); + if ($version) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/versionen/abschnitte/' . $version->getId(), [], true); + $templ = str_replace('%version(%affected)', '' . htmlReady($version->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_stgteil_bez': + $stgteilbez = StgteilBezeichnung::find($event->affected_range_id); + if ($stgteilbez) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/stgteilbezeichnungen/details/' . $stgteilbez->getId(), [], true); + $templ = str_replace('%stgteilbez(%affected)', '' . htmlReady($stgteilbez->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_stg_stgteil': + $stg = Studiengang::find($event->affected_range_id); + if ($stg) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $stg->getId(), [], true); + $templ = str_replace('%stg(%affected)', '' . htmlReady($stg->getDisplayName()) . '', $templ); + } + $co_stgteil = StudiengangTeil::find($event->coaffected_range_id); + if ($co_stgteil) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengangteile/details_versionen/' . $co_stgteil->getId(), [], true); + $templ = str_replace('%stgteil(%coaffected)', '' . htmlReady($co_stgteil->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_studiengang': + $stg = Studiengang::find($event->affected_range_id); + if ($stg) { + $url = URLHelper::getURL('dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $stg->getId(), [], true); + $templ = str_replace('%stg(%affected)', '' . htmlReady($stg->getDisplayName()) . '', $templ); + } + break; + + case 'mvv_contacts': + $contact = MvvContact::find($event->affected_range_id); + if ($contact) { + $url = URLHelper::getLink('dispatch.php/shared/contacts/details/index/' . $contact->id); + $templ = str_replace( + '%contact(%affected)', + '' . htmlReady($contact->getDisplayName()) . '', + $templ + ); + } + break; + + case 'mvv_contacts_ranges': + $contact_range = MvvContactRange::find($event->dbg_info); + if ($contact_range) { + if ($contact_range->contact) { + $url_affected = URLHelper::getLink( + 'dispatch.php/shared/contacts/details/index/' . $contact_range->contact->id); + $templ = str_replace( + '%contact(%affected)', + '' . htmlReady($contact_range->contact->getDisplayName()) . '', + $templ + ); + } + $range = null; + switch ($contact_range->range_type) { + case 'Modul': + $range = Modul::find($event->coaffected_range_id); + $url_coaffected = URLHelper::getLink( + 'dispatch.php/module/module/details/' . $range->id, + [], + true + ); + break; + case 'Studiengang': + $range = Studiengang::find($event->coaffected_range_id); + $url_coaffected = URLHelper::getLink( + 'dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $range->id, + [], + true + ); + break; + case 'StudiengangTeil': + $range = StudiengangTeil::find($event->coaffected_range_id); + $url_coaffected = URLHelper::getLink( + 'dispatch.php/studiengaenge/studiengangteile/details_versionen/' . $range->id, + [], + true + ); + + } + if ($range) { + $templ = str_replace( + '%range(%coaffected)', + '' . htmlReady($range->getDisplayName()) . '', + $templ + ); + } + } + break; + + case 'mvv_extern_contacts': + $extern_contact = MvvExternContact::find($event->affected_range_id); + if ($extern_contact) { + $url = URLHelper::getLink('dispatch.php/shared/contacts/details/index/' . $extern_contact->id); + $templ = str_replace( + '%contact(%affected)', + '' . htmlReady($extern_contact->getDisplayName()) . '', + $templ + ); + } + break; + + case 'mvv_files': + $file = MvvFile::find($event->affected_range_id); + if ($file) { + $url = URLHelper::getLink('dispatch.php/materialien/files/index/' . $file->id); + $templ = str_replace( + '%file(%affected)', + '' . htmlReady($file->getDisplayName()) . '', + $templ + ); + } + break; + + case 'mvv_files_filerefs': + $fileref = MvvFileFileref::find($event->affected_range_id); + if ($fileref) { + $url = URLHelper::getLink('dispatch.php/materialien/index/' . $fileref->mvvfile_id); + $templ = str_replace( + '%fileref(%affected)', + '' . htmlReady($fileref->getDisplayName()) . '', + $templ + ); + } + break; + + case 'mvv_files_ranges': + $file_range = MvvFileRange::find([ + $event->affected_range_id, + $event->coaffected_range_id + ]); + if ($file_range) { + if ($file_range->mvv_file) { + $url_affected = URLHelper::getLink( + 'dispatch.php/materialien/index/' . $file_range->mvvfile_id + ); + $templ = str_replace( + '%fileref(%affected)', + '' . htmlReady($file_range->mvv_file->getDisplayName()) . '', + $templ + ); + } + $range = null; + switch ($file_range->range_type) { + case 'Studiengang': + $range = Studiengang::find($event->coaffected_range_id); + $url_coaffected = URLHelper::getLink( + 'dispatch.php/studiengaenge/studiengaenge/details_studiengang/' . $range->id, + [], + true + ); + break; + case 'StgteilVersion': + $range = StgteilVersion::find($event->coaffected_range_id); + $url_coaffected = URLHelper::getLink( + 'dispatch.php/studiengaenge/studiengangteile/version/' . $range->id, + [], + true + ); + + } + if ($range) { + $templ = str_replace( + '%range(%coaffected)', + '' . htmlReady($range->getDisplayName()) . '', + $templ + ); + } + } + break; + + default: + break; + } + + // specials + // Benutzergruppen im Modul z.B. Modulverantwortlicher + $templ = str_replace('%gruppe', $event->dbg_info, $templ); + // Objekt konnte nicht eingesetzt werden da es vermutlich nicht mehr existiert, beim delete landet der alte Bezeichner im debug + $templ = str_replace('%modul(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%modulteil(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%modulteildesk(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%moduldesk(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%stg(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%stgteil(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%stgteilabs(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%version(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%stgteilbez(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%lvgruppe(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%fach(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%abschluss(%affected)', $event->dbg_info, $templ); + $templ = str_replace('%abskategorie(%affected)', $event->dbg_info, $templ); + + $templ = str_replace('%abskategorie(%coaffected)', $event->dbg_info, $templ); + $templ = str_replace('%modulteil(%coaffected)', $event->dbg_info, $templ); + $templ = str_replace('%modul(%coaffected)', $event->dbg_info, $templ); + $templ = str_replace('%language(%coaffected)', $event->dbg_info, $templ); + $templ = str_replace('%stgteilabs(%coaffected)', $event->dbg_info, $templ); + $templ = str_replace('%stgteil(%coaffected)', $event->dbg_info, $templ); + $templ = str_replace('%contact', $event->dbg_info, $templ); + + return $templ; + } + + /** + * This method searches the log-entries for log-actions of the mvv classes. + * Used by search function on log page. + * + * @param string $needle The search term. + * @param string $action_name The name of the log action. + * + * @return array Found log events. + */ + public static function logSearch($needle, $action_name = null) + { + $result = []; + + $modul_actions = [ + 'MVV_MODUL_NEW', + 'MVV_MODUL_UPDATE', + 'MVV_MODUL_DEL', + 'MVV_MODUL_DESK_NEW', + 'MVV_MODUL_DESK_UPDATE', + 'MVV_MODUL_DESK_DEL', + 'MVV_MODULINST_NEW', + 'MVV_MODULINST_DEL', + 'MVV_MODULINST_UPDATE', + 'MVV_MODUL_USER_NEW', + 'MVV_MODUL_USER_DEL', + 'MVV_MODUL_USER_UPDATE', + 'MVV_MODUL_LANG_NEW', + 'MVV_MODUL_LANG_DEL', + 'MVV_MODUL_LANG_UPDATE', + 'MVV_MODULTEIL_STGTEILABS_NEW', + 'MVV_MODULTEIL_STGTEILABS_DEL', + 'MVV_MODULTEIL_STGTEILABS_UPDATE', + 'MVV_STGTEILABS_MODUL_NEW', + 'MVV_STGTEILABS_MODUL_DEL', + 'MVV_STGTEILABS_MODUL_UPDATE' + ]; + + if (in_array($action_name, $modul_actions)) { + $module = Modul::findBySQL( + "code LIKE CONCAT('%', :needle, '%') OR modul_id = :needle" + ); + $deskriptoren = ModulDeskriptor::findBySql( + "bezeichnung LIKE CONCAT('%', :needle, '%') + OR deskriptor_id = :needle", + [':needle' => $needle] + ); + foreach ($module as $modul) { + $result[] = [ + $modul->getId(), + $modul->getDisplayName() + ]; + } + foreach ($deskriptoren as $desk) { + $modul = Modul::find($desk->modul_id); + $result[] = [ + $modul->getId(), + $modul->getDisplayName() + ]; + } + } + + $modulteile_actions = [ + 'MVV_MODULTEIL_NEW', + 'MVV_MODULTEIL_UPDATE', + 'MVV_MODULTEIL_DEL', + 'MVV_MODULTEIL_DESK_NEW', + 'MVV_MODULTEIL_DESK_UPDATE', + 'MVV_MODULTEIL_DESK_DEL', + 'MVV_MODULTEIL_LANG_NEW', + 'MVV_MODULTEIL_LANG_DEL', + 'MVV_MODULTEIL_LANG_UPDATE', + 'MVV_LVMODULTEIL_NEW', + 'MVV_LVMODULTEIL_DEL', + 'MVV_LVMODULTEIL_UPDATE' + ]; + + if (in_array($action_name, $modulteile_actions)) { + $deskriptoren = ModulDeskriptor::findBySql( + "bezeichnung LIKE CONCAT('%', :needle, '%') + OR deskriptor_id = :needle", + [':needle' => $needle] + ); + foreach ($deskriptoren as $desk) { + $modulteil = Modulteil::find($desk->modulteil_id); + $result[] = [ + $modulteil->getId(), + $modulteil->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_STUDIENGANG_NEW', + 'MVV_STUDIENGANG_UPDATE', + 'MVV_STUDIENGANG_DEL', + 'MVV_STG_STGTEIL_NEW', + 'MVV_STG_STGTEIL_DEL', + 'MVV_STG_STGTEIL_UPDATE' + ])) { + $stg = Studiengang::findBySQL( + "name LIKE CONCAT('%', :needle, '%') + OR studiengang_id = :needle", + [':needle' => $needle] + ); + foreach ($stg as $studiengang) { + $result[] = [ + $studiengang->getId(), + $studiengang->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_STGTEIL_NEW', + 'MVV_STGTEIL_UPDATE', + 'MVV_STGTEIL_DEL', + 'MVV_FACHBERATER_NEW', + 'MVV_FACHBERATER_UPDATE', + 'MVV_FACHBERATER_DEL' + ])) { + $stgteile = StudiengangTeil::findBySQL( + "zusatz LIKE CONCAT('%', :needle, '%') + OR zusatz_en LIKE CONCAT('%', :needle, '%') + OR stgteil_id = :needle", + [':needle' => $needle] + ); + foreach ($stgteile as $stgteil) { + $result[] = [ + $stgteil->getId(), + $stgteil->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_STGTEILVERSION_NEW', + 'MVV_STGTEILVERSION_UPDATE', + 'MVV_STGTEILVERSION_DEL' + ])) { + $versionen = StgteilVersion::findBySQL( + "code LIKE CONCAT('%', :needle, '%') + OR version_id = :needle + OR stgteil_id = :needle", + [':needle' => $needle] + ); + foreach ($versionen as $version) { + $result[] = [ + $version->getId(), + $version->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_STGTEILBEZ_NEW', + 'MVV_STGTEILBEZ_UPDATE', + 'MVV_STGTEILBEZ_DEL' + ])) { + $stgbez = StgteilBezeichnung::findBySQL( + "name LIKE CONCAT('%', :needle, '%') + OR name_en LIKE CONCAT('%', :needle, '%') + OR stgteil_bez_id = :needle", + [':needle' => $needle] + ); + foreach ($stgbez as $bez) { + $result[] = [ + $bez->getId(), + $bez->getDisplayName() + ]; + } + } + + $stgteil_actions = [ + 'MVV_STGTEILABS_NEW', + 'MVV_STGTEILABS_UPDATE', + 'MVV_STGTEILABS_DEL', + 'MVV_MODULTEIL_STGTEILABS_NEW', + 'MVV_MODULTEIL_STGTEILABS_DEL', + 'MVV_MODULTEIL_STGTEILABS_UPDATE', + 'MVV_STG_STGTEIL_NEW', + 'MVV_STG_STGTEIL_DEL', + 'MVV_STG_STGTEIL_UPDATE', + 'MVV_STGTEILABS_MODUL_NEW', + 'MVV_STGTEILABS_MODUL_DEL', + 'MVV_STGTEILABS_MODUL_UPDATE' + ]; + + if (in_array($action_name, $stgteil_actions)) { + $stgteilabs = Lvgruppe::findBySQL( + "name LIKE CONCAT('%', :needle, '%') + OR name_en LIKE CONCAT('%', :needle, '%') + OR abschnitt_id = :needle", + [':needle' => $needle] + ); + foreach ($stgteilabs as $abschnitt) { + $result[] = [ + $abschnitt->getId(), + $abschnitt->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_LVGRUPPE_NEW', + 'MVV_LVGRUPPE_DEL', + 'MVV_LVGRUPPE_UPDATE', + 'MVV_LVMODULTEIL_NEW', + 'MVV_LVMODULTEIL_DEL', + 'MVV_LVMODULTEIL_UPDATE', + 'MVV_LVSEMINAR_NEW', + 'MVV_LVSEMINAR_DEL', + 'MVV_LVSEMINAR_UPDATE' + ])) { + $lvgruppen = Lvgruppe::findBySQL( + "name LIKE CONCAT('%', :needle, '%') + OR name_en LIKE CONCAT('%', :needle, '%') + OR lvgruppe_id = :needle", + [':needle' => $needle] + ); + foreach ($lvgruppen as $lvgruppe) { + $result[] = [ + $lvgruppe->getId(), + $lvgruppe->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_FACH_NEW', + 'MVV_FACH_UPDATE', + 'MVV_FACH_DEL', + 'MVV_FACHINST_NEW', + 'MVV_FACHINST_DEL', + 'MVV_FACHINST_UPDATE' + ])) { + $faecher = Fach::findBySQL( + "name LIKE CONCAT('%', :needle, '%') + OR name_en LIKE CONCAT('%', :needle, '%') + OR fach_id = :needle", + [':needle' => $needle] + ); + foreach ($faecher as $fach) { + $result[] = [ + $fach->getId(), + $fach->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_ABSCHLUSS_NEW', + 'MVV_ABSCHLUSS_UPDATE', + 'MVV_ABSCHLUSS_DEL', + 'MVV_ABS_ZUORD_NEW', + 'MVV_ABS_ZUORD_DEL', + 'MVV_ABS_ZUORD_UPDATE' + ])) { + $abschluesse = Abschluss::findBySQL( + "name LIKE CONCAT('%', :needle, '%') + OR name_en LIKE CONCAT('%', :needle, '%') + OR abschluss_id = :needle", + [':needle' => $needle] + ); + foreach ($abschluesse as $abschluss) { + $result[] = [ + $abschluss->getId(), + $abschluss->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_KATEGORIE_NEW', + 'MVV_KATEGORIE_UPDATE', + 'MVV_KATEGORIE_DEL', + 'MVV_ABS_ZUORD_NEW', + 'MVV_ABS_ZUORD_DEL', + 'MVV_ABS_ZUORD_UPDATE' + ])) { + $abskategorien = AbschlussKategorie::findBySQL( + "name LIKE CONCAT('%', :needle, '%') + OR name_en LIKE CONCAT('%', :needle, '%') + OR kategorie_id = :needle", + [':needle' => $needle] + ); + foreach ($abskategorien as $abskategorie) { + $result[] = [ + $abskategorie->getId(), + $abskategorie->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_CONTACT_NEW', + 'MVV_CONTACT_UPDATE', + 'MVV_CONTACT_DELETE', + 'MVV_CONTACT_RANGE_NEW', + 'MVV_CONTACT_RANGE_UPDATE', + 'MVV_CONTACT_RANGE_DELETE', + 'MVV_CONTACT_EXTERN_NEW', + 'MVV_CONTACT_EXTERN_UPDATE', + 'MVV_CONTACT_EXTERN_DELETE' + ])) { + $contacts_intern = MvvContact::findBySQL( + "LEFT JOIN `auth_user_md5` + ON `mvv_contacts`.`contact_id` = `auth_user_md5`.`user_id` + WHERE `auth_user_md5`.`username` LIKE CONCAT('%', :needle, '%') + OR `auth_user_md5`.`nachname` LIKE CONCAT('%', :needle, '%') + OR `auth_user_md5`.`email` = :needle", + [':needle' => $needle] + ); + $contacts_extern = MvvContact::findBySQL( + "LEFT JOIN `mvv_extern_contacts` + ON `mvv_contacts`.`contact_id` = `mvv_extern_contacts`.`extern_contact_id` + WHERE `mvv_extern_contacts`.`name` = :needle + OR `mvv_extern_contacts`.`mail` = :needle", + [':needle' => $needle] + ); + $contacts = array_merge($contacts_intern, $contacts_extern); + foreach ($contacts as $contact) { + $result[] = [ + $contact->id, + $contact->getDisplayName() + ]; + } + } + + if (in_array($action_name, [ + 'MVV_FILE_NEW', + 'MVV_FILE_UPDATE', + 'MVV_FILE_DELETE', + 'MVV_FILE_RANGE_NEW', + 'MVV_FILE_RANGE_UPDATE', + 'MVV_FILE_RANGE_DELETE', + 'MVV_FILE_FILEREF_NEW', + 'MVV_FILE_FILEREF_UPDATE', + 'MVV_FILE_FILEREF_DEL' + ])) { + $files = MvvFile::findBySQL( + "LEFT JOIN `mvv_files_filerefs` USING (`mvvfile_id`) + LEFT JOIN `file_refs` + ON `mvv_files_filerefs`.`fileref_id` = `file_refs`.`id` + LEFT JOIN `files` + ON `file_refs`.`file_id` = `files`.`id` + WHERE `mvv_files`.`tags` LIKE CONCAT('%', :needle, '%') + OR `mvv_files_filerefs`.`name` LIKE CONCAT('%', :needle, '%') + OR `file_refs`.`name` LIKE CONCAT('%', :needle, '%') + OR `files`.`name` = :needle + OR `files`.`author_name` = :needle", + [':needle' => $needle] + ); + foreach ($files as $file) { + $result[$file->id] = [ + $file->id, + $file->getDisplayName() + ]; + } + } + + return $result; + } + + /** + * Returns imagepath for given language, used by MVV + * First tries $GLOBALS['CONTENT_LANGUAGES'], if not defined returns hardcoded path + * + * @param string $language e.g. 'DE' + * @return string path to language icon + */ + public static function getContentLanguageImagePath($language): string + { + $content_language = $GLOBALS['MVV_MODUL_DESKRIPTOR']['SPRACHE']['values'][$language]['content_language']; + return 'languages/' . ($GLOBALS['CONTENT_LANGUAGES'][$content_language]['picture'] ?? 'lang_' . mb_strtolower($language) . '.gif'); + } + +} diff --git a/lib/classes/Markup.class.php b/lib/classes/Markup.class.php deleted file mode 100644 index fbde67b..0000000 --- a/lib/classes/Markup.class.php +++ /dev/null @@ -1,788 +0,0 @@ - - */ -namespace Studip; - -require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_ClassifyLinks.php'; -require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_ClassifyTables.php'; -require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_LinkifyEmail.php'; -require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_TransformLinks.php'; -require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_Unlinkify.php'; - -class Markup -{ - /** - * Apply markup rules and clean the text up. - * - * @param TextFormat $markup Markup rules applied on marked-up text. - * @param string $text Marked-up text on which rules are applied. - * @param boolean $trim Trim text before applying markup rules, if TRUE. - * - * @return string HTML code computed from marked-up text. - */ - public static function apply($markup, $text, $trim) - { - return $markup->format(self::markupToHtml($text, $trim, false)); - } - - // signature for HTML entries - const HTML_MARKER = ''; - - // signature for HTML fallback entries - const HTML_MARKER_FALLBACK = ''; - - // regular expression for detecting HTML signature - const HTML_MARKER_REGEXP = '/^\s*/i'; - - /** - * Return `true` if the WYSIWYG editor is enabled for this user. - * @deprecated since Stud.IP 5.5 - * - * @return boolean always returns `true`. - */ - public static function editorEnabled() - { - return true; - } - - /** - * Return `true` for HTML code and `false` for plain text. - * - * HTML code must either match `HTML_MARKER_REGEXP` or begin - * with '<' and end with '>' (leading and trailing whitespace - * is ignored). Everything else is considered to be plain - * text. - * - * @param string $text HTML code or plain text. - * - * @return boolean `true` for HTML code, `false` for plain text. - */ - public static function isHtml($text) - { - return self::hasHtmlMarker($text); - } - - /** - * Return `true` for Stud.IP-HTML and `false` otherwise. - * - * Stud.IP-HTML is HTML that can contain Stud.IP Markup. - * - * Stud.IP-HTML must match Stud.IP 3.2's HTML marker. - * Leading and trailing whitespace is ignored. - * - * Everything else is considered not Stud.IP-HTML. In other - * words, if it's not Stud.IP-HTML it might be everything - * from plain text to binary code. But usually it's either - * Stud.IP markup or plain HTML code, then. - * - * @param string $text Text that is or isn't Stud.IP-HTML. - * - * @return boolean `true` for Stud.IP-HTML - */ - public static function isHtmlFallback($text) - { - $text = trim($text); - - // it's not fallback if the new HTML marker is detected - if (MarkupPrivate\Text\startsWith($text, self::HTML_MARKER)) { - return false; - } - - // it's Stud.IP-HTML if Stud.IP 3.2's HTML marker is detected - if (MarkupPrivate\Text\startsWith($text, self::HTML_MARKER_FALLBACK)) { - return true; - } - - return false; - } - - /** - * Return `true` for HTML code and `false` for plain text. - * - * HTML code must start with a match for `HTML_MARKER_REGEXP`. - * - * @param string $text HTML code or plain text. - * - * @return boolean `true` for HTML code, `false` for plain text. - */ - public static function hasHtmlMarker($text) - { - return preg_match(self::HTML_MARKER_REGEXP, $text); - } - - /** - * Mark a given text as HTML code. - * - * No sanity-checking is done on the given text. It is simply - * marked up so to be identified by Markup::isHtml as HTML - * code. - * - * @param string $text The text to be marked up as HTML code. - * - * @return string The text marked up as HTML code. - */ - public static function markAsHtml($text) - { - // NOTE keep this function in sync with the JavaScript - // function markAsHtml in WyswygHtmlHead.php - if (self::hasHtmlMarker($text) || trim($text) === '') { - return $text; // marker already set, don't set twice - } - return self::HTML_MARKER . $text; - } - - /** - * Apply markup rules after running text through HTML ready. - * - * @param TextFormat $markup Markup rules applied on marked-up text. - * @param string $text Marked-up text on which rules are applied. - * @param boolean $trim Trim text before applying markup rules, if TRUE. - * - * @return string HTML code computed from marked-up text. - */ - private static function markupHtmlReady($markup, $text, $trim) - { - return str_replace("\n", '
', self::markupText( - $markup, self::htmlReady(self::unixEOL($text), $trim))); - } - - /** - * Convert line break to Unix format. - * - * @param string $text Text with possibly mixed line breaks (Win, Mac, Unix). - * - * @return string Text with Unix line breaks only. - */ - private static function unixEOL($text) - { - return preg_replace("/\r\n?/", "\n", $text); - } - - /** - * Apply markup rules on plain text. - * - * @param TextFormat $markup Markup rules applied on marked-up text. - * @param string $text Marked-up text on which rules are applied. - * - * @return string HTML code computed from marked-up text. - */ - private static function markupText($markup, $text) - { - return symbol($markup->format($text)); - } - - /** - * Call HTMLPurifier to create safe HTML. - * - * @param string $dirty_html Unsafe or 'uncleaned' HTML code. - * @param boolean $autoformat Apply the AutoFormat rules - * @return string Clean and safe HTML code. - */ - private static function purify($dirty_html, $autoformat = true) - { - $purifier = self::createPurifier($autoformat); - - return $purifier->purify($dirty_html); - } - - /** - * Call HTMLPurifier to filter the HTML code (if the source is detected - * to contain HTML, returns the argument unchanged otherwise). The HTML - * marker is restored afterwards, if it was present. - * - * @param string $dirty_html Unsafe or 'uncleaned' HTML code. - * @return string Clean and safe HTML code. - */ - public static function purifyHtml($html) - { - if ($html instanceof \I18NString) { - $base = self::purifyHtml($html->original()); - $lang = $html->toArray(); - - foreach ($lang as &$value) { - $value = self::purifyHtml($value); - } - - return new \I18NString($base, $lang); - } - - if (self::isHtml($html)) { - $html = self::markAsHtml(self::purify($html)); - } - - return $html; - } - - /** - * Create HTML purifier instance with Stud.IP-specific configuration. - * - * @param boolean $autoformat Apply the AutoFormat rules - * @return \HTMLPurifier A new instance of the HTML purifier. - */ - private static function createPurifier($autoformat) - { - $config = \HTMLPurifier_Config::createDefault(); - $config->set('Cache.SerializerPath', $GLOBALS['TMP_PATH']); - $config->set('Core.RemoveInvalidImg', true); - - // restrict allowed HTML tags and attributes - // - // note that changes here should also be reflected in CKEditor's - // settings!! - // - // NOTE The list could be restricted even further by allowing only - // specific values for some attributes and CSS styles, but that is not - // directly supported by HTMLPurifier and would need to be implemented - // with a filter similar to ClassifyLinks. - // - // This is a list of further restrictions that can/should be introduced - // at a later time point maybe, if possible: - // - // - always open external links in a new tab or window - // a[class="link-extern" href="..." target="_blank"] - // - only allow left margin and horizontal text alignment to be set in - // divs (NOTE maybe remove these two features completely?): - // div[style="margin-left:(40|80|...)px; text-align:(center|right|justify)"] - // - img[style] should only allow float:left or float:right - // - only allow text color and background color to be set in a span's - // style attribute (NOTE 'wiki-links' are currently set here due to - // implementation difficulties, but probably this should be - // changed...): - // span[style="color:(#000000|#800000|...); - // background-color:(#000000|#800000|...)" - // class="wiki-link"] - // - tables should always have the class "content" (it should not be - // optional and no other class should be set): - // table[class="content"] - // - table headings should have a column and/or a row scope or no scope - // at all, but nothing else: - // th[scope="(col | row)"] - // - fonts: only Stud.IP-specific fonts should be allowed - // - $config->set('HTML.Allowed', ' - a[class|href|target|rel|name|id] - audio[controls|src|height|width|style] - big - blockquote - br - caption - code[class] - div[class|style] - em - figure[class|style] - figcaption - h1 - h2 - h3 - h4 - h5 - h6 - hr - i - img[alt|src|height|width|class|style] - li - ol[reversed|start|style] - p[style] - pre[class] - span[style|class] - strong - u - ul[style] - s - small - sub - sup - table[class|style] - tbody - td[colspan|rowspan|style] - thead - th[colspan|rowspan|style|scope] - tr - tt - video[controls|src|height|width|style] - '); - - $config->set('Attr.AllowedFrameTargets', ['_blank']); - $config->set('Attr.AllowedRel', ['nofollow']); - $config->set('Attr.EnableID', true); - $config->set('Attr.AllowedClasses', [ - 'author', - 'content', - 'image', - 'image-style-side', - 'image_resized', - 'language-cpp', - 'language-css', - 'language-diff', - 'language-java', - 'language-javascript', - 'language-json', - 'language-php', - 'language-python', - 'language-ruby', - 'language-scss', - 'language-sql', - 'language-xml', - 'link-extern', - 'link-intern', - 'math-tex', - 'table', - 'usercode', - 'wiki-link' - ]); - $config->set('CSS.AllowedFonts', [ - 'serif', - 'sans-serif', - 'monospace', - 'cursive' - ]); - $config->set('CSS.AllowedProperties', [ - 'margin-left', - 'text-align', - 'width', - 'height', - 'color', - 'background-color', // needed by span, td - 'border-color', - 'border-style', - 'float', - 'border', - 'vertical-align' - ]); - $config->set('CSS.MaxImgLength', null); - - if ($autoformat) { - $config->set('AutoFormat.Linkify', true); - $config->set('AutoFormat.Custom', [ - 'ClassifyLinks', - 'ClassifyTables', - 'LinkifyEmail' - ]); - $config->set('AutoFormat.RemoveSpansWithoutAttributes', true); - } else { - $config->set('AutoFormat.Custom', ['TransformLinks']); - } - - // avoid - $def = $config->getHTMLDefinition(true); - $img = $def->addBlankElement('img'); - $img->attr_transform_post[] - = new MarkupPrivate\Purifier\AttrTransform_Image_Source(); - - $def->addElement('audio', 'Inline', 'Flow', 'Common', [ - 'src*' => 'URI', - 'width' => 'Length', - 'height' => 'Length', - 'controls' => 'Text', // Bool triggers bug in HTMLPurifier - ]); - - $def->addElement('video', 'Inline', 'Flow', 'Common', [ - 'src*' => 'URI', - 'width' => 'Length', - 'height' => 'Length', - 'controls' => 'Text', // Bool triggers bug in HTMLPurifier - ]); - - $def->addElement('figcaption', 'Inline', 'Flow', 'Common'); - $def->addElement('figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'); - - $def->addAttribute('ol', 'reversed', 'Bool'); - $def->addAttribute('ol', 'style', 'Text'); - $def->addAttribute('ul', 'style', 'Text'); - - return new \HTMLPurifier($config); - } - - /** - * Convert special characters to HTML entities, and clean up. - * - * @param string $text This text's special chars will be converted. - * @param boolean $trim Trim text before applying markup rules, if TRUE. - * @param boolean $br Replace newlines by
, if TRUE. - * @param boolean $double_encode Encode existing HTML entities, if TRUE. - * @return string The converted string. - */ - public static function htmlReady( - $text, $trim = true, $br = false, $double_encode = true - ) { - $text = htmlspecialchars($text, ENT_QUOTES, 'utf-8', $double_encode); - - if ($trim) { - $text = trim($text); - } - if ($br) { // fix newlines - $text = nl2br($text, false); - } - return $text; - } - - /** - * Prepare text for wysiwyg (if enabled), otherwise convert special - * characters using htmlReady. - * - * @param string $text The text. - * @param boolean $trim Trim text before applying markup rules, if TRUE. - * @param boolean $br Replace newlines by
, if TRUE and wysiwyg editor disabled. - * @param boolean $double_encode Encode existing HTML entities, if TRUE and wysiwyg editor disabled. - * @return string The converted string. - */ - public static function wysiwygReady( - $text, $trim = true, $br = false, $double_encode = true - ) { - if (self::editorEnabled()) { - $text = self::markupToHtml($text, $trim); - } - return self::htmlReady($text, $trim, $br, $double_encode); - } - - /** - * Convert Stud.IP markup (possibly mixed with HTML if fallback mode is - * enabled) to editable HTML. Pure HTML will only run through the purifier. - * - * @param string $text The text. - * @param boolean $trim Trim text before applying markup rules, if TRUE. - * @param boolean $mark Mark result text as HTML, if TRUE. - * @return string The converted string. - */ - public static function markupToHtml($text, $trim = true, $mark = true) - { - if (!trim($text)) { - return $text; - } - if (self::isHtml($text)) { - $is_fallback = self::isHtmlFallback($text); - $text = self::purify($text, false); - - if ($is_fallback) { - $text = self::markupText(new \StudipCoreFormat(), $text); - } - } else { - $text = self::markupHtmlReady(new \StudipCoreFormat(), $text, $trim); - } - - return $mark ? self::markAsHtml($text) : $text; - } - - /** - * Call HTMLPurifier to remove all HTML tags from the string (if the source - * is detected to contain HTML, returns the argument unchanged otherwise). - * - * @param string $html HTML code to filter - * @return string The converted string. - */ - public static function removeHtml($html) - { - if (self::isHtml($html)) { - $config = \HTMLPurifier_Config::createDefault(); - $config->set('Cache.SerializerPath', $GLOBALS['TMP_PATH']); - $config->set('HTML.Allowed', 'a[href],img[alt|src],br'); - $config->set('AutoFormat.Custom', ['Unlinkify']); - - $html = str_replace('', '
', $html); - $html = str_replace('', '
', $html); - $html = str_replace('', '
', $html); - $html = str_replace('', '
', $html); - $html = str_replace('

', '



', $html); - $html = str_replace('', '

', $html); - - $purifier = new \HTMLPurifier($config); - $html = $purifier->purify($html); - - // Replace new lines with simple line break; twice because we don't - // want to create unneccessary white space if a
is followed - // by a new line - $html = str_replace('
' . PHP_EOL, PHP_EOL, $html); - $html = str_replace('
', PHP_EOL, $html); - - $html = \decodeHTML(trim($html)); - } - - return $html; - } -} - -/** - * Members of Studip\MarkupPrivate must not be used outside of this file!! - */ - -namespace Studip\MarkupPrivate\Purifier; - -use Studip\MarkupPrivate\MediaProxy; - -/** - * Remove invalid attributes. - */ -class AttrTransform_Image_Source extends \HTMLPurifier_AttrTransform -{ - /** - * Implements abstract method of base class. - */ - function transform($attr, $config, $context) - { - try { - $attr['src'] = MediaProxy\getMediaUrl($attr['src']); - } catch (MediaProxy\InvalidInternalLinkException $e) { - // invalid internal link ==> remove attribute - $GLOBALS['msg'][] = _('Ungültige interne Medienverknüpfung entfernt: ') - . \htmlentities($e->getUrl()); - $attr['src'] = NULL; // remove attribute - } catch (MediaProxy\ExternalMediaDeniedException $e) { - $GLOBALS['msg'][] = _('Verbotene externe Medienverknüpfung entfernt: ') - . \htmlentities($e->getUrl()); - $attr['src'] = NULL; // remove attribute - } - return $attr; - } -} - -//// media proxy ////////////////////////////////////////////////////////////// - -namespace Studip\MarkupPrivate\MediaProxy; - -use Studip\MarkupPrivate\Text; - -/** - * Check if media proxy should be used and if so return the respective URL. - * - * @param string $url URL to media file. - * @return mixed URL string to media file (possibly 'proxied') - * or NULL if URL is invalid. - */ -function getMediaUrl($url) { - // even though proxied URLs shouldn't be stored in the database, the - // next line will handle those cases where they're accidentally there - $url = decodeMediaProxyUrl($url); - - // handle internal media links - if (isStudipMediaUrl($url)) { - return transformInternalIdnaLink($url); - } - if (isInternalLink($url)) { - // link is studip-internal, but not to a valid media location - throw new InvalidInternalLinkException($url); - } - - // handle external media links - $external_media = \Config::get()->LOAD_EXTERNAL_MEDIA; - if ($external_media === 'proxy' && - \Seminar_Session::is_current_session_authenticated() - ) { - // media proxy must be accessed by an internal link - return encodeMediaProxyUrl($url); - } - if ($external_media === 'allow') { - return $url; - } - throw new ExternalMediaDeniedException($url); -} - -/** - * Return media proxy URL for an unproxied URL. - * - * @params string $url Unproxied media URL. - * @return string Media proxy URL for accessing the same resource. - */ -function encodeMediaProxyUrl($url) { - return transformInternalIdnaLink( - getMediaProxyUrl() .'?url=' . \urlencode(\idna_link($url))); -} - -/** - * Extract the original URL from a media proxy URL. - * - * @param string $url The media proxy URL. - * return string The original URL. If $url does not point to the media - * proxy then this is the exact same value given by $url. - */ -function decodeMediaProxyUrl($url) { - # TODO make it work for 'url=' at any position in query - $urlpath = removeStudipDomain($url); - $proxypath = removeStudipDomain(getMediaProxyUrl()) . '?url='; - if (Text\startsWith($urlpath, $proxypath)) { - return \urldecode(Text\removePrefix($urlpath, $proxypath)); - } - return $url; -} - -/** - * Return Stud.IP's absolute media proxy URL. - */ -function getMediaProxyUrl() { - return $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/media_proxy'; -} - -/** - * Test if an URL points to a valid internal Stud.IP media path. - * - * @param string $url Internal Stud.IP URL. - * @returns boolean TRUE for internal media link URLs, FALSE otherwise. - */ -function isStudipMediaUrl($url) { - return isInternalLink($url) && - isStudipMediaUrlPath(getStudipRelativePath($url)); -} - -function isInternalLink($url) { - return is_internal_url(transformInternalIdnaLink($url)); -} - -//// url utilities //////////////////////////////////////////////////////////// - -/** - * Remove domain name from internal URLs. - * - * Remove scheme, domain and authentication information from internal - * Stud.IP URLs. Leave external URLs untouched. - * - * @param string $url URL from which to remove internal domain. - * @returns string URL without internal domain or the exact same - * value as $url for external URLs. - */ -function removeStudipDomain($url) { - if (!isInternalLink($url)) { - return $url; - } - $parsed_url = \parse_url(transformInternalIdnaLink($url)); - $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; - $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; - $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; - return $path . $query . $fragment; -} - -/** - * Return a URL's path component with the absolute Stud.IP path removed. - * - * NOTE: If the URL is not an internal Stud.IP URL, the path component will - * nevertheless be returned without issuing an error message. - * - * Example: - * >>> getStudipRelativePath('http://localhost:8080' - * . '/studip/sendfile.php?type=0&file_id=ABC123&file_name=nice.jpg') - * 'sendfile.php' - * - * @param string $url The URL from which to return the Stud.IP-relative - * path component. - * returns string Stud.IP-relative path component of $url. - */ -function getStudipRelativePath($url) { - $parsed_url = \parse_url(transformInternalIdnaLink($url)); - $parsed_studip_url = getParsedStudipUrl(); - return Text\removePrefix($parsed_url['path'], $parsed_studip_url['path']); -} - -/** - * Return an associative array containing the Stud.IP URL elements. - * - * see also: http://php.net/manual/en/function.parse-url.php - * - * @returns mixed Same values that PHP's parse_url() returns. - */ -function getParsedStudipUrl() { - return \parse_url($GLOBALS['ABSOLUTE_URI_STUDIP']); -} - -/** - * Test if path is valid for internal Stud.IP media URLs. - * - * @params string $path The path component of an URL. - * return boolean TRUE for valid media paths, FALSE otherwise. - */ -function isStudipMediaUrlPath($path) { - list($path_head) = \explode('/', $path); - $valid_paths = ['sendfile.php', 'download', 'assets', 'pictures']; - return \mb_strpos(\urldecode($path), '../') === false && \in_array($path_head, $valid_paths); -} - -/** - * Return a normalized, internal URL. - * - * @params string $url An internal URL. - * @returns string Normalized internal URL. - */ -function transformInternalIdnaLink($url) { - return \idna_link(\TransformInternalLinks($url)); -} - -//// url exceptions /////////////////////////////////////////////////////////// - -class UrlException extends \Exception -{ - private $url; - - public function __construct($url) { - parent::__construct(); - $this->url = $url; - } - - public function getUrl() - { - return $this->url; - } -} - -class InvalidInternalLinkException extends UrlException -{ -} - -class ExternalMediaDeniedException extends UrlException -{ -} - -//// string utilities ///////////////////////////////////////////////////////// - -namespace Studip\MarkupPrivate\Text; - -/** - * Test if string starts with prefix. - * - * @param string $string Tested string. - * @param string $prefix Prefix of tested string. - * - * @return boolean TRUE if string starts with prefix. - */ -function startsWith($string, $prefix) { - return \mb_substr($string, 0, \mb_strlen($prefix)) === $prefix; -} - -/** - * Test if string ends with suffix. - * - * @param string $string Tested string. - * @param string $suffix Suffix of tested string. - * - * @return boolean TRUE if string ends with suffix. - */ -function endsWith($string, $suffix) { - return \mb_substr($string, - \mb_strlen($suffix)) === $suffix; -} - -/** - * Remove prefix from string. - * - * Does not change the string if it has a different prefix. - * - * @param string $string The string that must start with the prefix. - * @param string $prefix The prefix of the string. - * - * @return string String without prefix. - */ -function removePrefix($string, $prefix) { - return startsWith($string, $prefix) ? \mb_substr($string, \mb_strlen($prefix)) : $string; -} diff --git a/lib/classes/Markup.php b/lib/classes/Markup.php new file mode 100644 index 0000000..fbde67b --- /dev/null +++ b/lib/classes/Markup.php @@ -0,0 +1,788 @@ + + */ +namespace Studip; + +require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_ClassifyLinks.php'; +require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_ClassifyTables.php'; +require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_LinkifyEmail.php'; +require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_TransformLinks.php'; +require_once __DIR__ . '/htmlpurifier/HTMLPurifier_Injector_Unlinkify.php'; + +class Markup +{ + /** + * Apply markup rules and clean the text up. + * + * @param TextFormat $markup Markup rules applied on marked-up text. + * @param string $text Marked-up text on which rules are applied. + * @param boolean $trim Trim text before applying markup rules, if TRUE. + * + * @return string HTML code computed from marked-up text. + */ + public static function apply($markup, $text, $trim) + { + return $markup->format(self::markupToHtml($text, $trim, false)); + } + + // signature for HTML entries + const HTML_MARKER = ''; + + // signature for HTML fallback entries + const HTML_MARKER_FALLBACK = ''; + + // regular expression for detecting HTML signature + const HTML_MARKER_REGEXP = '/^\s*/i'; + + /** + * Return `true` if the WYSIWYG editor is enabled for this user. + * @deprecated since Stud.IP 5.5 + * + * @return boolean always returns `true`. + */ + public static function editorEnabled() + { + return true; + } + + /** + * Return `true` for HTML code and `false` for plain text. + * + * HTML code must either match `HTML_MARKER_REGEXP` or begin + * with '<' and end with '>' (leading and trailing whitespace + * is ignored). Everything else is considered to be plain + * text. + * + * @param string $text HTML code or plain text. + * + * @return boolean `true` for HTML code, `false` for plain text. + */ + public static function isHtml($text) + { + return self::hasHtmlMarker($text); + } + + /** + * Return `true` for Stud.IP-HTML and `false` otherwise. + * + * Stud.IP-HTML is HTML that can contain Stud.IP Markup. + * + * Stud.IP-HTML must match Stud.IP 3.2's HTML marker. + * Leading and trailing whitespace is ignored. + * + * Everything else is considered not Stud.IP-HTML. In other + * words, if it's not Stud.IP-HTML it might be everything + * from plain text to binary code. But usually it's either + * Stud.IP markup or plain HTML code, then. + * + * @param string $text Text that is or isn't Stud.IP-HTML. + * + * @return boolean `true` for Stud.IP-HTML + */ + public static function isHtmlFallback($text) + { + $text = trim($text); + + // it's not fallback if the new HTML marker is detected + if (MarkupPrivate\Text\startsWith($text, self::HTML_MARKER)) { + return false; + } + + // it's Stud.IP-HTML if Stud.IP 3.2's HTML marker is detected + if (MarkupPrivate\Text\startsWith($text, self::HTML_MARKER_FALLBACK)) { + return true; + } + + return false; + } + + /** + * Return `true` for HTML code and `false` for plain text. + * + * HTML code must start with a match for `HTML_MARKER_REGEXP`. + * + * @param string $text HTML code or plain text. + * + * @return boolean `true` for HTML code, `false` for plain text. + */ + public static function hasHtmlMarker($text) + { + return preg_match(self::HTML_MARKER_REGEXP, $text); + } + + /** + * Mark a given text as HTML code. + * + * No sanity-checking is done on the given text. It is simply + * marked up so to be identified by Markup::isHtml as HTML + * code. + * + * @param string $text The text to be marked up as HTML code. + * + * @return string The text marked up as HTML code. + */ + public static function markAsHtml($text) + { + // NOTE keep this function in sync with the JavaScript + // function markAsHtml in WyswygHtmlHead.php + if (self::hasHtmlMarker($text) || trim($text) === '') { + return $text; // marker already set, don't set twice + } + return self::HTML_MARKER . $text; + } + + /** + * Apply markup rules after running text through HTML ready. + * + * @param TextFormat $markup Markup rules applied on marked-up text. + * @param string $text Marked-up text on which rules are applied. + * @param boolean $trim Trim text before applying markup rules, if TRUE. + * + * @return string HTML code computed from marked-up text. + */ + private static function markupHtmlReady($markup, $text, $trim) + { + return str_replace("\n", '
', self::markupText( + $markup, self::htmlReady(self::unixEOL($text), $trim))); + } + + /** + * Convert line break to Unix format. + * + * @param string $text Text with possibly mixed line breaks (Win, Mac, Unix). + * + * @return string Text with Unix line breaks only. + */ + private static function unixEOL($text) + { + return preg_replace("/\r\n?/", "\n", $text); + } + + /** + * Apply markup rules on plain text. + * + * @param TextFormat $markup Markup rules applied on marked-up text. + * @param string $text Marked-up text on which rules are applied. + * + * @return string HTML code computed from marked-up text. + */ + private static function markupText($markup, $text) + { + return symbol($markup->format($text)); + } + + /** + * Call HTMLPurifier to create safe HTML. + * + * @param string $dirty_html Unsafe or 'uncleaned' HTML code. + * @param boolean $autoformat Apply the AutoFormat rules + * @return string Clean and safe HTML code. + */ + private static function purify($dirty_html, $autoformat = true) + { + $purifier = self::createPurifier($autoformat); + + return $purifier->purify($dirty_html); + } + + /** + * Call HTMLPurifier to filter the HTML code (if the source is detected + * to contain HTML, returns the argument unchanged otherwise). The HTML + * marker is restored afterwards, if it was present. + * + * @param string $dirty_html Unsafe or 'uncleaned' HTML code. + * @return string Clean and safe HTML code. + */ + public static function purifyHtml($html) + { + if ($html instanceof \I18NString) { + $base = self::purifyHtml($html->original()); + $lang = $html->toArray(); + + foreach ($lang as &$value) { + $value = self::purifyHtml($value); + } + + return new \I18NString($base, $lang); + } + + if (self::isHtml($html)) { + $html = self::markAsHtml(self::purify($html)); + } + + return $html; + } + + /** + * Create HTML purifier instance with Stud.IP-specific configuration. + * + * @param boolean $autoformat Apply the AutoFormat rules + * @return \HTMLPurifier A new instance of the HTML purifier. + */ + private static function createPurifier($autoformat) + { + $config = \HTMLPurifier_Config::createDefault(); + $config->set('Cache.SerializerPath', $GLOBALS['TMP_PATH']); + $config->set('Core.RemoveInvalidImg', true); + + // restrict allowed HTML tags and attributes + // + // note that changes here should also be reflected in CKEditor's + // settings!! + // + // NOTE The list could be restricted even further by allowing only + // specific values for some attributes and CSS styles, but that is not + // directly supported by HTMLPurifier and would need to be implemented + // with a filter similar to ClassifyLinks. + // + // This is a list of further restrictions that can/should be introduced + // at a later time point maybe, if possible: + // + // - always open external links in a new tab or window + // a[class="link-extern" href="..." target="_blank"] + // - only allow left margin and horizontal text alignment to be set in + // divs (NOTE maybe remove these two features completely?): + // div[style="margin-left:(40|80|...)px; text-align:(center|right|justify)"] + // - img[style] should only allow float:left or float:right + // - only allow text color and background color to be set in a span's + // style attribute (NOTE 'wiki-links' are currently set here due to + // implementation difficulties, but probably this should be + // changed...): + // span[style="color:(#000000|#800000|...); + // background-color:(#000000|#800000|...)" + // class="wiki-link"] + // - tables should always have the class "content" (it should not be + // optional and no other class should be set): + // table[class="content"] + // - table headings should have a column and/or a row scope or no scope + // at all, but nothing else: + // th[scope="(col | row)"] + // - fonts: only Stud.IP-specific fonts should be allowed + // + $config->set('HTML.Allowed', ' + a[class|href|target|rel|name|id] + audio[controls|src|height|width|style] + big + blockquote + br + caption + code[class] + div[class|style] + em + figure[class|style] + figcaption + h1 + h2 + h3 + h4 + h5 + h6 + hr + i + img[alt|src|height|width|class|style] + li + ol[reversed|start|style] + p[style] + pre[class] + span[style|class] + strong + u + ul[style] + s + small + sub + sup + table[class|style] + tbody + td[colspan|rowspan|style] + thead + th[colspan|rowspan|style|scope] + tr + tt + video[controls|src|height|width|style] + '); + + $config->set('Attr.AllowedFrameTargets', ['_blank']); + $config->set('Attr.AllowedRel', ['nofollow']); + $config->set('Attr.EnableID', true); + $config->set('Attr.AllowedClasses', [ + 'author', + 'content', + 'image', + 'image-style-side', + 'image_resized', + 'language-cpp', + 'language-css', + 'language-diff', + 'language-java', + 'language-javascript', + 'language-json', + 'language-php', + 'language-python', + 'language-ruby', + 'language-scss', + 'language-sql', + 'language-xml', + 'link-extern', + 'link-intern', + 'math-tex', + 'table', + 'usercode', + 'wiki-link' + ]); + $config->set('CSS.AllowedFonts', [ + 'serif', + 'sans-serif', + 'monospace', + 'cursive' + ]); + $config->set('CSS.AllowedProperties', [ + 'margin-left', + 'text-align', + 'width', + 'height', + 'color', + 'background-color', // needed by span, td + 'border-color', + 'border-style', + 'float', + 'border', + 'vertical-align' + ]); + $config->set('CSS.MaxImgLength', null); + + if ($autoformat) { + $config->set('AutoFormat.Linkify', true); + $config->set('AutoFormat.Custom', [ + 'ClassifyLinks', + 'ClassifyTables', + 'LinkifyEmail' + ]); + $config->set('AutoFormat.RemoveSpansWithoutAttributes', true); + } else { + $config->set('AutoFormat.Custom', ['TransformLinks']); + } + + // avoid + $def = $config->getHTMLDefinition(true); + $img = $def->addBlankElement('img'); + $img->attr_transform_post[] + = new MarkupPrivate\Purifier\AttrTransform_Image_Source(); + + $def->addElement('audio', 'Inline', 'Flow', 'Common', [ + 'src*' => 'URI', + 'width' => 'Length', + 'height' => 'Length', + 'controls' => 'Text', // Bool triggers bug in HTMLPurifier + ]); + + $def->addElement('video', 'Inline', 'Flow', 'Common', [ + 'src*' => 'URI', + 'width' => 'Length', + 'height' => 'Length', + 'controls' => 'Text', // Bool triggers bug in HTMLPurifier + ]); + + $def->addElement('figcaption', 'Inline', 'Flow', 'Common'); + $def->addElement('figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'); + + $def->addAttribute('ol', 'reversed', 'Bool'); + $def->addAttribute('ol', 'style', 'Text'); + $def->addAttribute('ul', 'style', 'Text'); + + return new \HTMLPurifier($config); + } + + /** + * Convert special characters to HTML entities, and clean up. + * + * @param string $text This text's special chars will be converted. + * @param boolean $trim Trim text before applying markup rules, if TRUE. + * @param boolean $br Replace newlines by
, if TRUE. + * @param boolean $double_encode Encode existing HTML entities, if TRUE. + * @return string The converted string. + */ + public static function htmlReady( + $text, $trim = true, $br = false, $double_encode = true + ) { + $text = htmlspecialchars($text, ENT_QUOTES, 'utf-8', $double_encode); + + if ($trim) { + $text = trim($text); + } + if ($br) { // fix newlines + $text = nl2br($text, false); + } + return $text; + } + + /** + * Prepare text for wysiwyg (if enabled), otherwise convert special + * characters using htmlReady. + * + * @param string $text The text. + * @param boolean $trim Trim text before applying markup rules, if TRUE. + * @param boolean $br Replace newlines by
, if TRUE and wysiwyg editor disabled. + * @param boolean $double_encode Encode existing HTML entities, if TRUE and wysiwyg editor disabled. + * @return string The converted string. + */ + public static function wysiwygReady( + $text, $trim = true, $br = false, $double_encode = true + ) { + if (self::editorEnabled()) { + $text = self::markupToHtml($text, $trim); + } + return self::htmlReady($text, $trim, $br, $double_encode); + } + + /** + * Convert Stud.IP markup (possibly mixed with HTML if fallback mode is + * enabled) to editable HTML. Pure HTML will only run through the purifier. + * + * @param string $text The text. + * @param boolean $trim Trim text before applying markup rules, if TRUE. + * @param boolean $mark Mark result text as HTML, if TRUE. + * @return string The converted string. + */ + public static function markupToHtml($text, $trim = true, $mark = true) + { + if (!trim($text)) { + return $text; + } + if (self::isHtml($text)) { + $is_fallback = self::isHtmlFallback($text); + $text = self::purify($text, false); + + if ($is_fallback) { + $text = self::markupText(new \StudipCoreFormat(), $text); + } + } else { + $text = self::markupHtmlReady(new \StudipCoreFormat(), $text, $trim); + } + + return $mark ? self::markAsHtml($text) : $text; + } + + /** + * Call HTMLPurifier to remove all HTML tags from the string (if the source + * is detected to contain HTML, returns the argument unchanged otherwise). + * + * @param string $html HTML code to filter + * @return string The converted string. + */ + public static function removeHtml($html) + { + if (self::isHtml($html)) { + $config = \HTMLPurifier_Config::createDefault(); + $config->set('Cache.SerializerPath', $GLOBALS['TMP_PATH']); + $config->set('HTML.Allowed', 'a[href],img[alt|src],br'); + $config->set('AutoFormat.Custom', ['Unlinkify']); + + $html = str_replace('', '
', $html); + $html = str_replace('', '
', $html); + $html = str_replace('', '
', $html); + $html = str_replace('', '
', $html); + $html = str_replace('

', '



', $html); + $html = str_replace('', '

', $html); + + $purifier = new \HTMLPurifier($config); + $html = $purifier->purify($html); + + // Replace new lines with simple line break; twice because we don't + // want to create unneccessary white space if a
is followed + // by a new line + $html = str_replace('
' . PHP_EOL, PHP_EOL, $html); + $html = str_replace('
', PHP_EOL, $html); + + $html = \decodeHTML(trim($html)); + } + + return $html; + } +} + +/** + * Members of Studip\MarkupPrivate must not be used outside of this file!! + */ + +namespace Studip\MarkupPrivate\Purifier; + +use Studip\MarkupPrivate\MediaProxy; + +/** + * Remove invalid attributes. + */ +class AttrTransform_Image_Source extends \HTMLPurifier_AttrTransform +{ + /** + * Implements abstract method of base class. + */ + function transform($attr, $config, $context) + { + try { + $attr['src'] = MediaProxy\getMediaUrl($attr['src']); + } catch (MediaProxy\InvalidInternalLinkException $e) { + // invalid internal link ==> remove attribute + $GLOBALS['msg'][] = _('Ungültige interne Medienverknüpfung entfernt: ') + . \htmlentities($e->getUrl()); + $attr['src'] = NULL; // remove attribute + } catch (MediaProxy\ExternalMediaDeniedException $e) { + $GLOBALS['msg'][] = _('Verbotene externe Medienverknüpfung entfernt: ') + . \htmlentities($e->getUrl()); + $attr['src'] = NULL; // remove attribute + } + return $attr; + } +} + +//// media proxy ////////////////////////////////////////////////////////////// + +namespace Studip\MarkupPrivate\MediaProxy; + +use Studip\MarkupPrivate\Text; + +/** + * Check if media proxy should be used and if so return the respective URL. + * + * @param string $url URL to media file. + * @return mixed URL string to media file (possibly 'proxied') + * or NULL if URL is invalid. + */ +function getMediaUrl($url) { + // even though proxied URLs shouldn't be stored in the database, the + // next line will handle those cases where they're accidentally there + $url = decodeMediaProxyUrl($url); + + // handle internal media links + if (isStudipMediaUrl($url)) { + return transformInternalIdnaLink($url); + } + if (isInternalLink($url)) { + // link is studip-internal, but not to a valid media location + throw new InvalidInternalLinkException($url); + } + + // handle external media links + $external_media = \Config::get()->LOAD_EXTERNAL_MEDIA; + if ($external_media === 'proxy' && + \Seminar_Session::is_current_session_authenticated() + ) { + // media proxy must be accessed by an internal link + return encodeMediaProxyUrl($url); + } + if ($external_media === 'allow') { + return $url; + } + throw new ExternalMediaDeniedException($url); +} + +/** + * Return media proxy URL for an unproxied URL. + * + * @params string $url Unproxied media URL. + * @return string Media proxy URL for accessing the same resource. + */ +function encodeMediaProxyUrl($url) { + return transformInternalIdnaLink( + getMediaProxyUrl() .'?url=' . \urlencode(\idna_link($url))); +} + +/** + * Extract the original URL from a media proxy URL. + * + * @param string $url The media proxy URL. + * return string The original URL. If $url does not point to the media + * proxy then this is the exact same value given by $url. + */ +function decodeMediaProxyUrl($url) { + # TODO make it work for 'url=' at any position in query + $urlpath = removeStudipDomain($url); + $proxypath = removeStudipDomain(getMediaProxyUrl()) . '?url='; + if (Text\startsWith($urlpath, $proxypath)) { + return \urldecode(Text\removePrefix($urlpath, $proxypath)); + } + return $url; +} + +/** + * Return Stud.IP's absolute media proxy URL. + */ +function getMediaProxyUrl() { + return $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/media_proxy'; +} + +/** + * Test if an URL points to a valid internal Stud.IP media path. + * + * @param string $url Internal Stud.IP URL. + * @returns boolean TRUE for internal media link URLs, FALSE otherwise. + */ +function isStudipMediaUrl($url) { + return isInternalLink($url) && + isStudipMediaUrlPath(getStudipRelativePath($url)); +} + +function isInternalLink($url) { + return is_internal_url(transformInternalIdnaLink($url)); +} + +//// url utilities //////////////////////////////////////////////////////////// + +/** + * Remove domain name from internal URLs. + * + * Remove scheme, domain and authentication information from internal + * Stud.IP URLs. Leave external URLs untouched. + * + * @param string $url URL from which to remove internal domain. + * @returns string URL without internal domain or the exact same + * value as $url for external URLs. + */ +function removeStudipDomain($url) { + if (!isInternalLink($url)) { + return $url; + } + $parsed_url = \parse_url(transformInternalIdnaLink($url)); + $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + return $path . $query . $fragment; +} + +/** + * Return a URL's path component with the absolute Stud.IP path removed. + * + * NOTE: If the URL is not an internal Stud.IP URL, the path component will + * nevertheless be returned without issuing an error message. + * + * Example: + * >>> getStudipRelativePath('http://localhost:8080' + * . '/studip/sendfile.php?type=0&file_id=ABC123&file_name=nice.jpg') + * 'sendfile.php' + * + * @param string $url The URL from which to return the Stud.IP-relative + * path component. + * returns string Stud.IP-relative path component of $url. + */ +function getStudipRelativePath($url) { + $parsed_url = \parse_url(transformInternalIdnaLink($url)); + $parsed_studip_url = getParsedStudipUrl(); + return Text\removePrefix($parsed_url['path'], $parsed_studip_url['path']); +} + +/** + * Return an associative array containing the Stud.IP URL elements. + * + * see also: http://php.net/manual/en/function.parse-url.php + * + * @returns mixed Same values that PHP's parse_url() returns. + */ +function getParsedStudipUrl() { + return \parse_url($GLOBALS['ABSOLUTE_URI_STUDIP']); +} + +/** + * Test if path is valid for internal Stud.IP media URLs. + * + * @params string $path The path component of an URL. + * return boolean TRUE for valid media paths, FALSE otherwise. + */ +function isStudipMediaUrlPath($path) { + list($path_head) = \explode('/', $path); + $valid_paths = ['sendfile.php', 'download', 'assets', 'pictures']; + return \mb_strpos(\urldecode($path), '../') === false && \in_array($path_head, $valid_paths); +} + +/** + * Return a normalized, internal URL. + * + * @params string $url An internal URL. + * @returns string Normalized internal URL. + */ +function transformInternalIdnaLink($url) { + return \idna_link(\TransformInternalLinks($url)); +} + +//// url exceptions /////////////////////////////////////////////////////////// + +class UrlException extends \Exception +{ + private $url; + + public function __construct($url) { + parent::__construct(); + $this->url = $url; + } + + public function getUrl() + { + return $this->url; + } +} + +class InvalidInternalLinkException extends UrlException +{ +} + +class ExternalMediaDeniedException extends UrlException +{ +} + +//// string utilities ///////////////////////////////////////////////////////// + +namespace Studip\MarkupPrivate\Text; + +/** + * Test if string starts with prefix. + * + * @param string $string Tested string. + * @param string $prefix Prefix of tested string. + * + * @return boolean TRUE if string starts with prefix. + */ +function startsWith($string, $prefix) { + return \mb_substr($string, 0, \mb_strlen($prefix)) === $prefix; +} + +/** + * Test if string ends with suffix. + * + * @param string $string Tested string. + * @param string $suffix Suffix of tested string. + * + * @return boolean TRUE if string ends with suffix. + */ +function endsWith($string, $suffix) { + return \mb_substr($string, - \mb_strlen($suffix)) === $suffix; +} + +/** + * Remove prefix from string. + * + * Does not change the string if it has a different prefix. + * + * @param string $string The string that must start with the prefix. + * @param string $prefix The prefix of the string. + * + * @return string String without prefix. + */ +function removePrefix($string, $prefix) { + return startsWith($string, $prefix) ? \mb_substr($string, \mb_strlen($prefix)) : $string; +} diff --git a/lib/classes/MessageBox.class.php b/lib/classes/MessageBox.class.php deleted file mode 100644 index 8936ebe..0000000 --- a/lib/classes/MessageBox.class.php +++ /dev/null @@ -1,177 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL Licence 2 - * @category Stud.IP - * @package layout - * @since 1.10 - * - */ - -/** - * class MessageBox - * - * usage: - * - * echo MessageBox::error('Nachricht', ['optional details']); - * - * use the optional parameter $close_details for displaying the message box with - * closed details - * - * echo MessageBox::success('Nachricht', ['optional details'], true); - * - */ -class MessageBox implements LayoutMessage -{ - /** - * type and contents of the message box - */ - public $class; - public $message; - public $details; - public $close_details; - protected $hide_close = false; - public static $counter = 0; - - /** - * This function returns an exception message box. Use it only for system errors - * or security related problems. - * - * @param string $message - * @param array $details - * @param boolean $close_details - * @return object MessageBox object - */ - public static function exception($message, $details = [], $close_details = false) - { - return new MessageBox('exception', $message, $details, $close_details); - } - - /** - * This function returns an error message box. Use it for validation errors, - * problems and other wrong user input. - * - * @param string $message - * @param array $details (optional) - * @param boolean $close_details (optional) - * @return object MessageBox object - */ - public static function error($message, $details = [], $close_details = false) - { - return new MessageBox('error', $message, $details, $close_details); - } - - /** - * This function returns a success message box. Use it for confirmation of user - * interaction. - * - * @param string $message - * @param array $details (optional) - * @param boolean $close_details (optional) - * @return object MessageBox object - */ - public static function success($message, $details = [], $close_details = false) - { - return new MessageBox('success', $message, $details, $close_details); - } - - /** - * This function returns an info message box. Use it to report neutral - * informations. - * - * @param string $message - * @param array $details (optional) - * @param boolean $close_details (optional) - * @return object MessageBox object - */ - public static function info($message, $details = [], $close_details = false) - { - return new MessageBox('info', $message, $details, $close_details); - } - - /** - * This function returns a warning message box. Use it to report potentially - * wrong behaviour. - * - * @param string $message - * @param array $details (optional) - * @param boolean $close_details (optional) - * @return object MessageBox object - */ - public static function warning($message, $details = [], $close_details = false) - { - return new MessageBox('warning', $message, $details, $close_details); - } - - /** - * Initializes a new MessageBox object of the given class. - * - * @param string $class the type of this message - * @param string $message - * @param array $details (optional) - * @param boolean $close_details (optional) - */ - protected function __construct($class, $message, $details = [], $close_details = false) - { - $this->class = $class; - $this->message = $message; - $this->details = $details; - $this->close_details = $close_details; - } - - /** - * Sets the state whether the close button should be hidden or not. - * - * @param boolean $state Whether the close button should be hidden or not - * @return MessageBox instance to allow chaining - */ - public function hideClose($state = true) - { - $this->hide_close = (bool) $state; - return $this; - } - - /** - * Return whether this messagebox can be closed or not. - * @return bool - */ - public function isCloseable(): bool - { - return $this->hide_close; - } - - /** - * This method renders a MessageBox object to a string. - * - * @return string html output of the message box - */ - public function __toString() - { - $label = [ - 'exception' => _('Systemfehler'), - 'error' => _('Fehler'), - 'warning' => _('Warnung'), - 'info' => _('Hinweis'), - 'success' => _('Erfolg'), - ]; - return $GLOBALS['template_factory']->render('shared/message_box', [ - 'class' => $this->class, - 'message' => $this->message, - 'details' => is_array($this->details) ? $this->details : [], - 'close_details' => $this->close_details, - 'hide_close' => $this->hide_close, - 'label' => $label[$this->class], - 'counter' => self::$counter++, - ]); - } -} diff --git a/lib/classes/MessageBox.php b/lib/classes/MessageBox.php new file mode 100644 index 0000000..8936ebe --- /dev/null +++ b/lib/classes/MessageBox.php @@ -0,0 +1,177 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL Licence 2 + * @category Stud.IP + * @package layout + * @since 1.10 + * + */ + +/** + * class MessageBox + * + * usage: + * + * echo MessageBox::error('Nachricht', ['optional details']); + * + * use the optional parameter $close_details for displaying the message box with + * closed details + * + * echo MessageBox::success('Nachricht', ['optional details'], true); + * + */ +class MessageBox implements LayoutMessage +{ + /** + * type and contents of the message box + */ + public $class; + public $message; + public $details; + public $close_details; + protected $hide_close = false; + public static $counter = 0; + + /** + * This function returns an exception message box. Use it only for system errors + * or security related problems. + * + * @param string $message + * @param array $details + * @param boolean $close_details + * @return object MessageBox object + */ + public static function exception($message, $details = [], $close_details = false) + { + return new MessageBox('exception', $message, $details, $close_details); + } + + /** + * This function returns an error message box. Use it for validation errors, + * problems and other wrong user input. + * + * @param string $message + * @param array $details (optional) + * @param boolean $close_details (optional) + * @return object MessageBox object + */ + public static function error($message, $details = [], $close_details = false) + { + return new MessageBox('error', $message, $details, $close_details); + } + + /** + * This function returns a success message box. Use it for confirmation of user + * interaction. + * + * @param string $message + * @param array $details (optional) + * @param boolean $close_details (optional) + * @return object MessageBox object + */ + public static function success($message, $details = [], $close_details = false) + { + return new MessageBox('success', $message, $details, $close_details); + } + + /** + * This function returns an info message box. Use it to report neutral + * informations. + * + * @param string $message + * @param array $details (optional) + * @param boolean $close_details (optional) + * @return object MessageBox object + */ + public static function info($message, $details = [], $close_details = false) + { + return new MessageBox('info', $message, $details, $close_details); + } + + /** + * This function returns a warning message box. Use it to report potentially + * wrong behaviour. + * + * @param string $message + * @param array $details (optional) + * @param boolean $close_details (optional) + * @return object MessageBox object + */ + public static function warning($message, $details = [], $close_details = false) + { + return new MessageBox('warning', $message, $details, $close_details); + } + + /** + * Initializes a new MessageBox object of the given class. + * + * @param string $class the type of this message + * @param string $message + * @param array $details (optional) + * @param boolean $close_details (optional) + */ + protected function __construct($class, $message, $details = [], $close_details = false) + { + $this->class = $class; + $this->message = $message; + $this->details = $details; + $this->close_details = $close_details; + } + + /** + * Sets the state whether the close button should be hidden or not. + * + * @param boolean $state Whether the close button should be hidden or not + * @return MessageBox instance to allow chaining + */ + public function hideClose($state = true) + { + $this->hide_close = (bool) $state; + return $this; + } + + /** + * Return whether this messagebox can be closed or not. + * @return bool + */ + public function isCloseable(): bool + { + return $this->hide_close; + } + + /** + * This method renders a MessageBox object to a string. + * + * @return string html output of the message box + */ + public function __toString() + { + $label = [ + 'exception' => _('Systemfehler'), + 'error' => _('Fehler'), + 'warning' => _('Warnung'), + 'info' => _('Hinweis'), + 'success' => _('Erfolg'), + ]; + return $GLOBALS['template_factory']->render('shared/message_box', [ + 'class' => $this->class, + 'message' => $this->message, + 'details' => is_array($this->details) ? $this->details : [], + 'close_details' => $this->close_details, + 'hide_close' => $this->hide_close, + 'label' => $label[$this->class], + 'counter' => self::$counter++, + ]); + } +} diff --git a/lib/classes/ModulesNotification.class.php b/lib/classes/ModulesNotification.class.php deleted file mode 100644 index f414071..0000000 --- a/lib/classes/ModulesNotification.class.php +++ /dev/null @@ -1,182 +0,0 @@ -, Suchi & Berg GmbH -* @access public -* @modulegroup core -* @package studip_core -*/ - -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// Modules.class.php -// Checks fuer Module (global und lokal fuer Veranstaltungen und Einrichtungen), Schreib-/Lesezugriff -// Copyright (C) 2003 Cornelis Kater , Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -class ModulesNotification -{ - - public $registered_notification_modules = []; - public $subject; - - public function __construct () - { - foreach (MyRealmModel::getDefaultModules() as $id => $module) { - if (!is_object($module)) { - continue; - } - - $metadata = $module->getMetadata(); - - $this->registered_notification_modules[$id] = [ - 'icon' => !empty($metadata['icon']) ? $metadata['icon'] : null, - 'name' => !empty($metadata['displayname']) ? $metadata['displayname'] : $module->getPluginName(), - ]; - if ($module instanceof CoreOverview) { - $this->registered_notification_modules[$id]['name'] = _("Ankündigungen"); - $this->registered_notification_modules[$id]['icon'] = Icon::create('news'); - } - if (!is_object($this->registered_notification_modules[$id]['icon'])) { - $icon = $module->getPluginURL() . '/' . $this->registered_notification_modules[$id]['icon']; - $this->registered_notification_modules[$id]['icon'] = Icon::create($icon); - } - } - $this->registered_notification_modules[-1] = - [ - 'name' => _("Umfragen und Tests"), - 'icon' => Icon::create('vote') - ]; - $this->registered_notification_modules[0] = - [ - 'name' => _("Grunddaten der Veranstaltung"), - 'icon' => Icon::create('seminar') - ]; - - $this->subject = _("Stud.IP Benachrichtigung"); - } - - - - public function getAllNotifications ($user_id = null) - { - if ($user_id === null) { - $user_id = $GLOBALS['user']->id; - } - - $my_sem = []; - $query = "SELECT s.Seminar_id, s.Name, s.chdate, s.start_time, IFNULL(visitdate, :threshold) AS visitdate - FROM seminar_user_notifications su - JOIN seminar_user USING (user_id, seminar_id) - JOIN seminare s USING (Seminar_id) - LEFT JOIN object_user_visits ouv - ON ( - ouv.object_id = su.Seminar_id - AND ouv.user_id = :user_id - AND ouv.plugin_id = 0 - ) - WHERE su.user_id = :user_id"; - - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':user_id', $user_id); - $statement->bindValue(':threshold', object_get_visit_threshold()); - $statement->execute(); - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $seminar_id = $row['Seminar_id']; - $tools = ToolActivation::findbyRange_id($seminar_id); - $notification = CourseMemberNotification::find([$user_id, $seminar_id]); - - if (!$notification || count($notification->notification_data) === 0) { - continue; - } - - $my_sem[$seminar_id] = [ - 'name' => $row['Name'], - 'chdate' => $row['chdate'], - 'start_time' => $row['start_time'], - 'tools' => new SimpleCollection($tools), - 'visitdate' => $row['visitdate'], - 'notification' => $notification->notification_data->getArrayCopy(), - ]; - } - $visit_data = get_objects_visits(array_keys($my_sem), 'sem', null, $user_id, array_keys($this->registered_notification_modules)); - $news = []; - foreach ($my_sem as $seminar_id => $s_data) { - $navigation = MyRealmModel::getAdditionalNavigations($seminar_id, $s_data, null, $user_id, $visit_data[$seminar_id]); - $n_data = []; - foreach ($this->registered_notification_modules as $id => $m_data) { - if ( - in_array($id, $s_data['notification']) - && isset($navigation[$id]) - && $navigation[$id]->getImage() - && $navigation[$id]->getImage()->getRole() === Icon::ROLE_ATTENTION - ) { - $data = $this->getPluginText($navigation[$id], $seminar_id, $id); - if ($data) { - $n_data[] = $data; - } - } - } - if (count($n_data)) { - $news[$s_data['name']] = $n_data; - } - } - if (count($news)) { - $user = User::find($user_id); - $auth_plugin = $user->auth_plugin; - if (!is_a('StudipAuth' . ucfirst($auth_plugin), 'StudipAuthSSO', true)) { - $auth_plugin = null; - } - $template = $GLOBALS['template_factory']->open('mail/notification_html'); - $template->set_attribute('lang', getUserLanguagePath($user_id)); - $template->set_attribute('rec_fullname', $user->getFullname('full')); - $template->set_attribute('rec_username', $user->username); - $template->set_attribute('news', $news); - $template->set_attribute('sso', $auth_plugin); - - $template_text = $GLOBALS['template_factory']->open('mail/notification_text'); - $template_text->set_attribute('news', $news); - $template_text->set_attribute('sso', $auth_plugin); - return ['text' => $template_text->render(), 'html' => $template->render()]; - } - - return null; - } - - function getPluginText($nav, $seminar_id, $id) - { - $base_url = URLHelper::setBaseURL(''); - URLHelper::setBaseURl($base_url); - if ($nav instanceof Navigation && $nav->isVisible(true)) { - $url = 'seminar_main.php?again=yes&auswahl=' . $seminar_id . '&redirect_to=' . strtr($nav->getURL(), '?', '&'); - $icon = $nav->getImage(); - $text = $nav->getTitle(); - if (!$text) { - $text = $this->registered_notification_modules[$id]['name']; - } - $text .= ' - ' . $nav->getLinkAttributes()['title']; - return compact('text', 'url', 'icon', 'seminar_id'); - } - } -} diff --git a/lib/classes/ModulesNotification.php b/lib/classes/ModulesNotification.php new file mode 100644 index 0000000..f414071 --- /dev/null +++ b/lib/classes/ModulesNotification.php @@ -0,0 +1,182 @@ +, Suchi & Berg GmbH +* @access public +* @modulegroup core +* @package studip_core +*/ + +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// Modules.class.php +// Checks fuer Module (global und lokal fuer Veranstaltungen und Einrichtungen), Schreib-/Lesezugriff +// Copyright (C) 2003 Cornelis Kater , Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +class ModulesNotification +{ + + public $registered_notification_modules = []; + public $subject; + + public function __construct () + { + foreach (MyRealmModel::getDefaultModules() as $id => $module) { + if (!is_object($module)) { + continue; + } + + $metadata = $module->getMetadata(); + + $this->registered_notification_modules[$id] = [ + 'icon' => !empty($metadata['icon']) ? $metadata['icon'] : null, + 'name' => !empty($metadata['displayname']) ? $metadata['displayname'] : $module->getPluginName(), + ]; + if ($module instanceof CoreOverview) { + $this->registered_notification_modules[$id]['name'] = _("Ankündigungen"); + $this->registered_notification_modules[$id]['icon'] = Icon::create('news'); + } + if (!is_object($this->registered_notification_modules[$id]['icon'])) { + $icon = $module->getPluginURL() . '/' . $this->registered_notification_modules[$id]['icon']; + $this->registered_notification_modules[$id]['icon'] = Icon::create($icon); + } + } + $this->registered_notification_modules[-1] = + [ + 'name' => _("Umfragen und Tests"), + 'icon' => Icon::create('vote') + ]; + $this->registered_notification_modules[0] = + [ + 'name' => _("Grunddaten der Veranstaltung"), + 'icon' => Icon::create('seminar') + ]; + + $this->subject = _("Stud.IP Benachrichtigung"); + } + + + + public function getAllNotifications ($user_id = null) + { + if ($user_id === null) { + $user_id = $GLOBALS['user']->id; + } + + $my_sem = []; + $query = "SELECT s.Seminar_id, s.Name, s.chdate, s.start_time, IFNULL(visitdate, :threshold) AS visitdate + FROM seminar_user_notifications su + JOIN seminar_user USING (user_id, seminar_id) + JOIN seminare s USING (Seminar_id) + LEFT JOIN object_user_visits ouv + ON ( + ouv.object_id = su.Seminar_id + AND ouv.user_id = :user_id + AND ouv.plugin_id = 0 + ) + WHERE su.user_id = :user_id"; + + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':user_id', $user_id); + $statement->bindValue(':threshold', object_get_visit_threshold()); + $statement->execute(); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $seminar_id = $row['Seminar_id']; + $tools = ToolActivation::findbyRange_id($seminar_id); + $notification = CourseMemberNotification::find([$user_id, $seminar_id]); + + if (!$notification || count($notification->notification_data) === 0) { + continue; + } + + $my_sem[$seminar_id] = [ + 'name' => $row['Name'], + 'chdate' => $row['chdate'], + 'start_time' => $row['start_time'], + 'tools' => new SimpleCollection($tools), + 'visitdate' => $row['visitdate'], + 'notification' => $notification->notification_data->getArrayCopy(), + ]; + } + $visit_data = get_objects_visits(array_keys($my_sem), 'sem', null, $user_id, array_keys($this->registered_notification_modules)); + $news = []; + foreach ($my_sem as $seminar_id => $s_data) { + $navigation = MyRealmModel::getAdditionalNavigations($seminar_id, $s_data, null, $user_id, $visit_data[$seminar_id]); + $n_data = []; + foreach ($this->registered_notification_modules as $id => $m_data) { + if ( + in_array($id, $s_data['notification']) + && isset($navigation[$id]) + && $navigation[$id]->getImage() + && $navigation[$id]->getImage()->getRole() === Icon::ROLE_ATTENTION + ) { + $data = $this->getPluginText($navigation[$id], $seminar_id, $id); + if ($data) { + $n_data[] = $data; + } + } + } + if (count($n_data)) { + $news[$s_data['name']] = $n_data; + } + } + if (count($news)) { + $user = User::find($user_id); + $auth_plugin = $user->auth_plugin; + if (!is_a('StudipAuth' . ucfirst($auth_plugin), 'StudipAuthSSO', true)) { + $auth_plugin = null; + } + $template = $GLOBALS['template_factory']->open('mail/notification_html'); + $template->set_attribute('lang', getUserLanguagePath($user_id)); + $template->set_attribute('rec_fullname', $user->getFullname('full')); + $template->set_attribute('rec_username', $user->username); + $template->set_attribute('news', $news); + $template->set_attribute('sso', $auth_plugin); + + $template_text = $GLOBALS['template_factory']->open('mail/notification_text'); + $template_text->set_attribute('news', $news); + $template_text->set_attribute('sso', $auth_plugin); + return ['text' => $template_text->render(), 'html' => $template->render()]; + } + + return null; + } + + function getPluginText($nav, $seminar_id, $id) + { + $base_url = URLHelper::setBaseURL(''); + URLHelper::setBaseURl($base_url); + if ($nav instanceof Navigation && $nav->isVisible(true)) { + $url = 'seminar_main.php?again=yes&auswahl=' . $seminar_id . '&redirect_to=' . strtr($nav->getURL(), '?', '&'); + $icon = $nav->getImage(); + $text = $nav->getTitle(); + if (!$text) { + $text = $this->registered_notification_modules[$id]['name']; + } + $text .= ' - ' . $nav->getLinkAttributes()['title']; + return compact('text', 'url', 'icon', 'seminar_id'); + } + } +} diff --git a/lib/classes/MultiDimArrayObject.class.php b/lib/classes/MultiDimArrayObject.class.php deleted file mode 100644 index b578018..0000000 --- a/lib/classes/MultiDimArrayObject.class.php +++ /dev/null @@ -1,129 +0,0 @@ - - * - */ -class MultiDimArrayObject extends StudipArrayObject -{ - /** - * Constructor - * - * @param array $input - * @param int $flags - * @param string $iteratorClass - */ - public function __construct($input = [], $flags = self::STD_PROP_LIST, $iteratorClass = 'ArrayIterator') - { - parent::__construct([], $flags, $iteratorClass); - $this->exchangeArray($input); - } - - /** - * Appends the value - * - * @param mixed $value - * @return void - */ - public function append($value) - { - $this->offsetSet(null, $value); - } - - /** - * Exchange the array for another one. - * - * @param array|ArrayObject $data - * @return array - */ - public function exchangeArray($data) - { - if (!is_array($data) && !is_object($data)) { - throw new InvalidArgumentException('Passed variable is not an array or object'); - } - - if ($data instanceof \StudipArrayObject) { - $data = $data->getArrayCopy(); - } - if (!is_array($data)) { - $data = (array) $data; - } - - $storage = $this->storage; - - $this->storage = $this->recursiveArrayToArrayObjects($data); - - return $storage; - } - - /** - * Creates a copy of the ArrayObject. - * - * @return array - */ - public function getArrayCopy() - { - $ret = []; - foreach($this->storage as $key => $value) { - if ($value instanceOf StudipArrayObject) { - $ret[$key] = $value->getArrayCopy(); - } else { - $ret[$key] = $value; - } - } - return $ret; - } - - /** - * Create a new iterator from an ArrayObject instance - */ - public function getIterator(): Traversable - { - $class = $this->iteratorClass; - - return new $class($this->getArrayCopy()); - } - - /** - * Sets the value at the specified key to value - * - * @param mixed $key - * @param mixed $value - */ - public function offsetSet($key, $value): void - { - $new_value = $this->recursiveArrayToArrayObjects($value); - if (is_array($new_value)) { - $class = get_called_class(); - $new_value = new $class($new_value, $this->getFlags(), $this->getIteratorClass()); - } - if (is_null($key)) { - $this->storage[] = $new_value; - } else { - $this->storage[$key] = $new_value; - } - } - - protected function recursiveArrayToArrayObjects($data) - { - if ($data instanceOf StudipArrayObject) { - $data = $data->getArrayCopy(); - } - if (is_array($data)) { - $new_data = []; - foreach ($data as $key => $value) { - $new_value = $this->recursiveArrayToArrayObjects($value); - if (is_array($new_value)) { - $class = get_called_class(); - $new_data[$key] = new $class($new_value, $this->getFlags(), $this->getIteratorClass()); - } else { - $new_data[$key] = $value; - } - } - return $new_data; - } - return $data; - } -} diff --git a/lib/classes/MultiDimArrayObject.php b/lib/classes/MultiDimArrayObject.php new file mode 100644 index 0000000..b578018 --- /dev/null +++ b/lib/classes/MultiDimArrayObject.php @@ -0,0 +1,129 @@ + + * + */ +class MultiDimArrayObject extends StudipArrayObject +{ + /** + * Constructor + * + * @param array $input + * @param int $flags + * @param string $iteratorClass + */ + public function __construct($input = [], $flags = self::STD_PROP_LIST, $iteratorClass = 'ArrayIterator') + { + parent::__construct([], $flags, $iteratorClass); + $this->exchangeArray($input); + } + + /** + * Appends the value + * + * @param mixed $value + * @return void + */ + public function append($value) + { + $this->offsetSet(null, $value); + } + + /** + * Exchange the array for another one. + * + * @param array|ArrayObject $data + * @return array + */ + public function exchangeArray($data) + { + if (!is_array($data) && !is_object($data)) { + throw new InvalidArgumentException('Passed variable is not an array or object'); + } + + if ($data instanceof \StudipArrayObject) { + $data = $data->getArrayCopy(); + } + if (!is_array($data)) { + $data = (array) $data; + } + + $storage = $this->storage; + + $this->storage = $this->recursiveArrayToArrayObjects($data); + + return $storage; + } + + /** + * Creates a copy of the ArrayObject. + * + * @return array + */ + public function getArrayCopy() + { + $ret = []; + foreach($this->storage as $key => $value) { + if ($value instanceOf StudipArrayObject) { + $ret[$key] = $value->getArrayCopy(); + } else { + $ret[$key] = $value; + } + } + return $ret; + } + + /** + * Create a new iterator from an ArrayObject instance + */ + public function getIterator(): Traversable + { + $class = $this->iteratorClass; + + return new $class($this->getArrayCopy()); + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + */ + public function offsetSet($key, $value): void + { + $new_value = $this->recursiveArrayToArrayObjects($value); + if (is_array($new_value)) { + $class = get_called_class(); + $new_value = new $class($new_value, $this->getFlags(), $this->getIteratorClass()); + } + if (is_null($key)) { + $this->storage[] = $new_value; + } else { + $this->storage[$key] = $new_value; + } + } + + protected function recursiveArrayToArrayObjects($data) + { + if ($data instanceOf StudipArrayObject) { + $data = $data->getArrayCopy(); + } + if (is_array($data)) { + $new_data = []; + foreach ($data as $key => $value) { + $new_value = $this->recursiveArrayToArrayObjects($value); + if (is_array($new_value)) { + $class = get_called_class(); + $new_data[$key] = new $class($new_value, $this->getFlags(), $this->getIteratorClass()); + } else { + $new_data[$key] = $value; + } + } + return $new_data; + } + return $data; + } +} diff --git a/lib/classes/MultiPersonSearch.class.php b/lib/classes/MultiPersonSearch.class.php deleted file mode 100644 index af10e6b..0000000 --- a/lib/classes/MultiPersonSearch.class.php +++ /dev/null @@ -1,548 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @link http://docs.studip.de/develop/Entwickler/MultiPersonSearch - */ -class MultiPersonSearch { - - private $name; - private $linkIconPath = ""; - private $linkText = ""; - private $title = ""; - private $description = ""; - private $executeURL; - private $jsFunction = null; - private $pageURL = null; - private $quickfilterIds = []; - private $defaultSelectableUsersIDs = []; - private $defaultSelectedUsersIDs = []; - private $searchObject = null; - private $additionalHMTL = ""; - private $navigationItem = ""; - private $dataDialogStatus = false; - - /** - * restores a MultiPersonSearch object. - * - * @param string name of the object - * - * @return MultiPersonSearch - */ - public static function load($name) - { - $mp = new MultiPersonSearch($name); - $mp->restoreFromSession(); - return $mp; - } - - /** - * returns a MultiPersonSearch object. - * - * @param string name of the object - * - * @return MultiPersonSearch - */ - public static function get($name) - { - $mp = new MultiPersonSearch($name); - return $mp; - } - - /** - * contsructs a new MultiPersonSearch object. - * - * @param string name of the object and html ids - */ - public function __construct($name) - { - $this->name = $name; - $_SESSION['multipersonsearch'][$this->name]['lastUse'] = time(); - $this->collectGarbage(); - $this->setDefaultValues(); - - } - - /** - * returns the newly added persons. The array will contain all - * persons which are selected (on the right side of the dialog) but - * without the defaultSelectedUsers. - * - * @return array containing all new persons - */ - public function getAddedUsers() { - return $_SESSION['multipersonsearch'][$this->name]['added'] ?? []; - } - - /** - * saves the added persons to $_SESSION. - */ - public function saveAddedUsersToSession() { - $addedUsers = []; - foreach (Request::optionArray($this->name . '_selectbox') as $selected) { - if (!in_array($selected, $_SESSION['multipersonsearch'][$this->name]['defaultSelectedUsersIDs'])) { - $addedUsers[] = $selected; - } - } - $_SESSION['multipersonsearch'][$this->name]['added'] = $addedUsers; - $_SESSION['multipersonsearch'][$this->name]['additional'] = Request::optionArray('additional'); - } - - /** - * returns the removed persons. The array will contain all - * persons which were selected by default (on the right side of the - * dialog) and then removed by the user. - * - * @return array containing all removed persons - */ - public function getRemovedUsers() { - return $_SESSION['multipersonsearch'][$this->name]['removed']; - } - - /** - * saves the removed persons to $_SESSION. - */ - public function saveRemovedUsersToSession() { - $removedUsers = []; - foreach ($this->defaultSelectedUsersIDs as $default) { - if (!in_array($default, Request::optionArray($this->name . '_selectbox'))) { - $removedUsers[] = $default; - } - } - $_SESSION['multipersonsearch'][$this->name]['removed'] = $removedUsers; - } - - /** - * renders a link to open the multipersonsearch dialog. - * - * @param string $with_link_text include link text in output - */ - public function render($with_link_text = true) { - $template = $GLOBALS['template_factory']->open('multipersonsearch/link.php'); - - $template->set_attribute('linkIconPath', $this->linkIconPath); - $template->set_attribute('linkText', $with_link_text ? $this->linkText : ''); - $template->set_attribute('title', $this->title); - $template->set_attribute('name', $this->name); - $template->set_attribute('description', $this->description); - $template->set_attribute('executeURL', $this->executeURL); - $template->set_attribute('jsFunction', $this->jsFunction); - $this->storeToSession(); - return $template->render(); - } - - /** - * sets the icon of the link to open the dialog. To hide the icon an - * empty string can be set. - * - * @param string path ot the icon - * - * @return MultiPersonSearch - */ - public function setLinkIconPath($path) { - $this->linkIconPath = $path; - - return $this; - } - - /** - * returns the icon of the link to open the dialog. - * - * @return string path ot the icon. - */ - public function getLinkIconPath() { - return $this->linkIconPath; - } - - /** - * sets the link text of the link to open the dialog. To hide the - * text an empty string can be set. - * - * @param string text of the link - * - * @return MultiPersonSearch - * - */ - public function setLinkText($text = "") { - $this->linkText = $text; - - return $this; - } - - /** - * returns the link text of the link. - * - * @return string text of the link. - */ - public function getLinkText() { - return $this->linkText; - } - - /** - * sets the action which will handle the added and removed persons after saving the dialog. - * - * @param string action - * - * @return MultiPersonSearch - */ - public function setExecuteURL($action) { - $this->executeURL = $action; - - return $this; - } - - /** - * returns the action which will handle the added and removed persons after saving the dialog. - * - * @return string action which will handle the form data. - */ - public function getExecuteURL() { - return $this->executeURL; - } - - /** - * sets a JavaScript-function to be fired when the user has pressed the submit-button. - * Arguments are: - * function fireme(id_of_item, text_of_item) - * example setting: MPS->setJSFunctionOnSubmit('fireme'); - * - * @param string $function_name the name of the javascript function - * - * @return MultiPersonSearch - */ - public function setJSFunctionOnSubmit($function_name) - { - $this->jsFunction = $function_name; - return $this; - } - - /** - * returns a JavaScript-function which should be fired when the user has pressed the submit button. - * - * @return string function name - */ - public function getJSFunctionOnSubmit() - { - return $this->jsFunction; - } - - /** - * sets the search object. - * - * @param SearchType object of type SearchType (e.g. SQLSearch.class.php) - * - * @return MultiPersonSearch - */ - public function setSearchObject($searchType) { - $this->searchObject = $searchType; - - return $this; - } - - /** - * returns the search object. - * - * @return SearchType - */ - public function getSearchObject() { - return $this->searchObject; - } - - /** - * sets html code which will be shown inside the form element. - * - * @param string html code - * - * @return MultiPersonSearch - */ - public function setAdditionalHTML($html) { - $this->additionalHMTL = $html; - - return $this; - } - - - /** - * enables or disabled data-dialog - * @param boolean $status - * @return $this - */ - public function setDataDialogStatus($status) { - $this->dataDialogStatus = $status; - - return $this; - } - - /** - * returns if data-dialog is enabled or disabled - * @return bool - */ - public function getDataDialogStatus() { - return $this->dataDialogStatus; - } - /** - * returns html code which will be shown inside the form element. - * - * @return string html code - */ - public function getAdditionHTML() { - return $this->additionalHMTL; - } - - /** - * returns an additional option array. - * - * @return string html code - */ - public function getAdditionalOptionArray() { - return $_SESSION['multipersonsearch'][$this->name]['additional']; - } - - /** - * sets the persons which will be shown as selectable by default on - * the left side of the dialoag. - * - * @param array array containing user-ids - */ - public function setDefaultSelectableUser($userArray) { - $userArray = array_unique($userArray); - $this->defaultSelectableUsersIDs = []; - if (is_array($userArray)) { - foreach ($userArray as $userId) { - $this->defaultSelectableUsersIDs[] = $userId; - } - } - return $this; - } - /** - * returns the ids of defaultselectable users. - * - * @return array - */ - public function getDefaultSelectableUsersIDs() { - return $this->defaultSelectableUsersIDs; - } - - /** - * sets the persons which will be shown as selected by default on - * the right side of the dialoag. - * - * @param array array containing user-ids - */ - public function setDefaultSelectedUser($userArray) { - $userArray = array_unique($userArray); - $this->defaultSelectedUsersIDs = []; - if (is_array($userArray)) { - foreach ($userArray as $userId) { - $this->defaultSelectedUsersIDs[] = $userId; - } - } - return $this; - } - - /** - * returns the ids of defaultselected users. - * - * @return array - */ - public function getDefaultSelectedUsersIDs() { - return $this->defaultSelectedUsersIDs; - } - - - /** - * sets the title of the dialog. - * - * @param string $title title of the dialog - * - * @return MultiPersonSearch - */ - public function setTitle($title) { - $this->title = $title; - return $this; - } - - /** - * returns the title. - * - * @return string - */ - public function getTitle() { - return $this->title; - } - - - /** - * sets the description of the dialog. - * - * @param string $desc description of the dialog - * - * @return MultiPersonSearch - */ - public function setDescription($desc) { - $this->description = $desc; - return $this; - } - - /** - * returns the description. - * - * @return string - */ - public function getDescription() { - return $this->description; - } - - /** - * returns the url of the page where the GUI element is added. - * - * @return string - */ - public function getPageUrl() { - return $this->pageURL; - } - - /** - * adds a new quickfilter. - * - * @param string $title title of the new quickfilter - * @param array $userArray containing all user-ids belonging to the quickfilter - * - * @return MultiPersonSearch - */ - public function addQuickfilter($title, $userArray) { - $users = []; - $usersIds = []; - if (is_array($userArray)) { - foreach ($userArray as $userId) { - $usersIds[] = $userId; - } - } - $this->quickfilterIds[(string) $title] = $usersIds; - - return $this; - } - - /** - * returns the ids of quickfilters. - * - * @return array - */ - public function getQuickfilterIds() - { - return $this->quickfilterIds ?: []; - } - - /** - * clears all quickfilters. - * - * @return MultiPersonSearch - */ - public function clearQuickfilters() { - $this->quickfilterIds = []; - - return $this; - } - - /** - * sets the navigation item. - * - * @param string $navigationItem navigation item - * - * @return MultiPersonSearch - */ - public function setNavigationItem($navigationItem) { - $this->navigationItem = $navigationItem; - - return $this; - } - - /** - * returns the navigation item. - * - * @return string - */ - public function getNavigationItem() { - return $this->navigationItem; - } - - /** - * stores the internal data to a session. - */ - public function storeToSession() { - $_SESSION['multipersonsearch'][$this->name]['title'] = $this->title; - $_SESSION['multipersonsearch'][$this->name]['description'] = $this->description; - $_SESSION['multipersonsearch'][$this->name]['additionalHMTL'] = $this->additionalHMTL; - $_SESSION['multipersonsearch'][$this->name]['executeURL'] = $this->executeURL; - $_SESSION['multipersonsearch'][$this->name]['jsFunction'] = $this->jsFunction; - $_SESSION['multipersonsearch'][$this->name]['pageURL'] = Request::url(); - $_SESSION['multipersonsearch'][$this->name]['defaultSelectableUsersIDs'] = $this->defaultSelectableUsersIDs; - $_SESSION['multipersonsearch'][$this->name]['defaultSelectedUsersIDs'] = $this->defaultSelectedUsersIDs; - $_SESSION['multipersonsearch'][$this->name]['quickfilterIds'] = $this->quickfilterIds; - $_SESSION['multipersonsearch'][$this->name]['searchObject'] = serialize($this->searchObject); - $_SESSION['multipersonsearch'][$this->name]['navigationItem'] = $this->navigationItem; - $_SESSION['multipersonsearch'][$this->name]['dataDialogStatus'] = $this->dataDialogStatus; - } - - /** - * restores the internal data from a session. - */ - public function restoreFromSession() { - if (isset($_SESSION['multipersonsearch'][$this->name])) { - $this->title = $_SESSION['multipersonsearch'][$this->name]['title'] ?? ''; - $this->description = $_SESSION['multipersonsearch'][$this->name]['description'] ?? ''; - $this->quickfilterIds = $_SESSION['multipersonsearch'][$this->name]['quickfilterIds'] ?? []; - $this->additionalHMTL = $_SESSION['multipersonsearch'][$this->name]['additionalHMTL'] ?? ''; - $this->executeURL = html_entity_decode($_SESSION['multipersonsearch'][$this->name]['executeURL'] ?? ''); - $this->jsFunction = $_SESSION['multipersonsearch'][$this->name]['jsFunction'] ?? ''; - $this->pageURL = $_SESSION['multipersonsearch'][$this->name]['pageURL'] ?? ''; - $this->defaultSelectableUsersIDs = $_SESSION['multipersonsearch'][$this->name]['defaultSelectableUsersIDs'] ?? []; - $this->defaultSelectedUsersIDs = $_SESSION['multipersonsearch'][$this->name]['defaultSelectedUsersIDs'] ?? []; - $this->searchObject = unserialize($_SESSION['multipersonsearch'][$this->name]['searchObject'] ?? null); - $this->navigationItem = $_SESSION['multipersonsearch'][$this->name]['navigationItem'] ?? null; - $this->dataDialogStatus = $_SESSION['multipersonsearch'][$this->name]['dataDialogStatus'] ?? ''; - } - } - - /** - * clears the session data. - */ - public function clearSession() { - unset($_SESSION['multipersonsearch'][$this->name]); - } - - /** - * sets default values of the internal variables. - */ - private function setDefaultValues() { - $this->title = ''; - $this->description = _('Bitte wählen Sie aus, wen Sie hinzufügen möchten.'); - $this->linkIconPath = Icon::create('add'); - } - - /** - * clear unused sessions. - */ - private function collectGarbage() { - $maxLifeTime = 30; // minutes - foreach ($_SESSION['multipersonsearch'] as $key=>$value) { - if (time() - $value['lastUse'] > $maxLifeTime * 60) { - unset($_SESSION['multipersonsearch'][$key]); - } - } - } - -} diff --git a/lib/classes/MultiPersonSearch.php b/lib/classes/MultiPersonSearch.php new file mode 100644 index 0000000..af10e6b --- /dev/null +++ b/lib/classes/MultiPersonSearch.php @@ -0,0 +1,548 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @link http://docs.studip.de/develop/Entwickler/MultiPersonSearch + */ +class MultiPersonSearch { + + private $name; + private $linkIconPath = ""; + private $linkText = ""; + private $title = ""; + private $description = ""; + private $executeURL; + private $jsFunction = null; + private $pageURL = null; + private $quickfilterIds = []; + private $defaultSelectableUsersIDs = []; + private $defaultSelectedUsersIDs = []; + private $searchObject = null; + private $additionalHMTL = ""; + private $navigationItem = ""; + private $dataDialogStatus = false; + + /** + * restores a MultiPersonSearch object. + * + * @param string name of the object + * + * @return MultiPersonSearch + */ + public static function load($name) + { + $mp = new MultiPersonSearch($name); + $mp->restoreFromSession(); + return $mp; + } + + /** + * returns a MultiPersonSearch object. + * + * @param string name of the object + * + * @return MultiPersonSearch + */ + public static function get($name) + { + $mp = new MultiPersonSearch($name); + return $mp; + } + + /** + * contsructs a new MultiPersonSearch object. + * + * @param string name of the object and html ids + */ + public function __construct($name) + { + $this->name = $name; + $_SESSION['multipersonsearch'][$this->name]['lastUse'] = time(); + $this->collectGarbage(); + $this->setDefaultValues(); + + } + + /** + * returns the newly added persons. The array will contain all + * persons which are selected (on the right side of the dialog) but + * without the defaultSelectedUsers. + * + * @return array containing all new persons + */ + public function getAddedUsers() { + return $_SESSION['multipersonsearch'][$this->name]['added'] ?? []; + } + + /** + * saves the added persons to $_SESSION. + */ + public function saveAddedUsersToSession() { + $addedUsers = []; + foreach (Request::optionArray($this->name . '_selectbox') as $selected) { + if (!in_array($selected, $_SESSION['multipersonsearch'][$this->name]['defaultSelectedUsersIDs'])) { + $addedUsers[] = $selected; + } + } + $_SESSION['multipersonsearch'][$this->name]['added'] = $addedUsers; + $_SESSION['multipersonsearch'][$this->name]['additional'] = Request::optionArray('additional'); + } + + /** + * returns the removed persons. The array will contain all + * persons which were selected by default (on the right side of the + * dialog) and then removed by the user. + * + * @return array containing all removed persons + */ + public function getRemovedUsers() { + return $_SESSION['multipersonsearch'][$this->name]['removed']; + } + + /** + * saves the removed persons to $_SESSION. + */ + public function saveRemovedUsersToSession() { + $removedUsers = []; + foreach ($this->defaultSelectedUsersIDs as $default) { + if (!in_array($default, Request::optionArray($this->name . '_selectbox'))) { + $removedUsers[] = $default; + } + } + $_SESSION['multipersonsearch'][$this->name]['removed'] = $removedUsers; + } + + /** + * renders a link to open the multipersonsearch dialog. + * + * @param string $with_link_text include link text in output + */ + public function render($with_link_text = true) { + $template = $GLOBALS['template_factory']->open('multipersonsearch/link.php'); + + $template->set_attribute('linkIconPath', $this->linkIconPath); + $template->set_attribute('linkText', $with_link_text ? $this->linkText : ''); + $template->set_attribute('title', $this->title); + $template->set_attribute('name', $this->name); + $template->set_attribute('description', $this->description); + $template->set_attribute('executeURL', $this->executeURL); + $template->set_attribute('jsFunction', $this->jsFunction); + $this->storeToSession(); + return $template->render(); + } + + /** + * sets the icon of the link to open the dialog. To hide the icon an + * empty string can be set. + * + * @param string path ot the icon + * + * @return MultiPersonSearch + */ + public function setLinkIconPath($path) { + $this->linkIconPath = $path; + + return $this; + } + + /** + * returns the icon of the link to open the dialog. + * + * @return string path ot the icon. + */ + public function getLinkIconPath() { + return $this->linkIconPath; + } + + /** + * sets the link text of the link to open the dialog. To hide the + * text an empty string can be set. + * + * @param string text of the link + * + * @return MultiPersonSearch + * + */ + public function setLinkText($text = "") { + $this->linkText = $text; + + return $this; + } + + /** + * returns the link text of the link. + * + * @return string text of the link. + */ + public function getLinkText() { + return $this->linkText; + } + + /** + * sets the action which will handle the added and removed persons after saving the dialog. + * + * @param string action + * + * @return MultiPersonSearch + */ + public function setExecuteURL($action) { + $this->executeURL = $action; + + return $this; + } + + /** + * returns the action which will handle the added and removed persons after saving the dialog. + * + * @return string action which will handle the form data. + */ + public function getExecuteURL() { + return $this->executeURL; + } + + /** + * sets a JavaScript-function to be fired when the user has pressed the submit-button. + * Arguments are: + * function fireme(id_of_item, text_of_item) + * example setting: MPS->setJSFunctionOnSubmit('fireme'); + * + * @param string $function_name the name of the javascript function + * + * @return MultiPersonSearch + */ + public function setJSFunctionOnSubmit($function_name) + { + $this->jsFunction = $function_name; + return $this; + } + + /** + * returns a JavaScript-function which should be fired when the user has pressed the submit button. + * + * @return string function name + */ + public function getJSFunctionOnSubmit() + { + return $this->jsFunction; + } + + /** + * sets the search object. + * + * @param SearchType object of type SearchType (e.g. SQLSearch.class.php) + * + * @return MultiPersonSearch + */ + public function setSearchObject($searchType) { + $this->searchObject = $searchType; + + return $this; + } + + /** + * returns the search object. + * + * @return SearchType + */ + public function getSearchObject() { + return $this->searchObject; + } + + /** + * sets html code which will be shown inside the form element. + * + * @param string html code + * + * @return MultiPersonSearch + */ + public function setAdditionalHTML($html) { + $this->additionalHMTL = $html; + + return $this; + } + + + /** + * enables or disabled data-dialog + * @param boolean $status + * @return $this + */ + public function setDataDialogStatus($status) { + $this->dataDialogStatus = $status; + + return $this; + } + + /** + * returns if data-dialog is enabled or disabled + * @return bool + */ + public function getDataDialogStatus() { + return $this->dataDialogStatus; + } + /** + * returns html code which will be shown inside the form element. + * + * @return string html code + */ + public function getAdditionHTML() { + return $this->additionalHMTL; + } + + /** + * returns an additional option array. + * + * @return string html code + */ + public function getAdditionalOptionArray() { + return $_SESSION['multipersonsearch'][$this->name]['additional']; + } + + /** + * sets the persons which will be shown as selectable by default on + * the left side of the dialoag. + * + * @param array array containing user-ids + */ + public function setDefaultSelectableUser($userArray) { + $userArray = array_unique($userArray); + $this->defaultSelectableUsersIDs = []; + if (is_array($userArray)) { + foreach ($userArray as $userId) { + $this->defaultSelectableUsersIDs[] = $userId; + } + } + return $this; + } + /** + * returns the ids of defaultselectable users. + * + * @return array + */ + public function getDefaultSelectableUsersIDs() { + return $this->defaultSelectableUsersIDs; + } + + /** + * sets the persons which will be shown as selected by default on + * the right side of the dialoag. + * + * @param array array containing user-ids + */ + public function setDefaultSelectedUser($userArray) { + $userArray = array_unique($userArray); + $this->defaultSelectedUsersIDs = []; + if (is_array($userArray)) { + foreach ($userArray as $userId) { + $this->defaultSelectedUsersIDs[] = $userId; + } + } + return $this; + } + + /** + * returns the ids of defaultselected users. + * + * @return array + */ + public function getDefaultSelectedUsersIDs() { + return $this->defaultSelectedUsersIDs; + } + + + /** + * sets the title of the dialog. + * + * @param string $title title of the dialog + * + * @return MultiPersonSearch + */ + public function setTitle($title) { + $this->title = $title; + return $this; + } + + /** + * returns the title. + * + * @return string + */ + public function getTitle() { + return $this->title; + } + + + /** + * sets the description of the dialog. + * + * @param string $desc description of the dialog + * + * @return MultiPersonSearch + */ + public function setDescription($desc) { + $this->description = $desc; + return $this; + } + + /** + * returns the description. + * + * @return string + */ + public function getDescription() { + return $this->description; + } + + /** + * returns the url of the page where the GUI element is added. + * + * @return string + */ + public function getPageUrl() { + return $this->pageURL; + } + + /** + * adds a new quickfilter. + * + * @param string $title title of the new quickfilter + * @param array $userArray containing all user-ids belonging to the quickfilter + * + * @return MultiPersonSearch + */ + public function addQuickfilter($title, $userArray) { + $users = []; + $usersIds = []; + if (is_array($userArray)) { + foreach ($userArray as $userId) { + $usersIds[] = $userId; + } + } + $this->quickfilterIds[(string) $title] = $usersIds; + + return $this; + } + + /** + * returns the ids of quickfilters. + * + * @return array + */ + public function getQuickfilterIds() + { + return $this->quickfilterIds ?: []; + } + + /** + * clears all quickfilters. + * + * @return MultiPersonSearch + */ + public function clearQuickfilters() { + $this->quickfilterIds = []; + + return $this; + } + + /** + * sets the navigation item. + * + * @param string $navigationItem navigation item + * + * @return MultiPersonSearch + */ + public function setNavigationItem($navigationItem) { + $this->navigationItem = $navigationItem; + + return $this; + } + + /** + * returns the navigation item. + * + * @return string + */ + public function getNavigationItem() { + return $this->navigationItem; + } + + /** + * stores the internal data to a session. + */ + public function storeToSession() { + $_SESSION['multipersonsearch'][$this->name]['title'] = $this->title; + $_SESSION['multipersonsearch'][$this->name]['description'] = $this->description; + $_SESSION['multipersonsearch'][$this->name]['additionalHMTL'] = $this->additionalHMTL; + $_SESSION['multipersonsearch'][$this->name]['executeURL'] = $this->executeURL; + $_SESSION['multipersonsearch'][$this->name]['jsFunction'] = $this->jsFunction; + $_SESSION['multipersonsearch'][$this->name]['pageURL'] = Request::url(); + $_SESSION['multipersonsearch'][$this->name]['defaultSelectableUsersIDs'] = $this->defaultSelectableUsersIDs; + $_SESSION['multipersonsearch'][$this->name]['defaultSelectedUsersIDs'] = $this->defaultSelectedUsersIDs; + $_SESSION['multipersonsearch'][$this->name]['quickfilterIds'] = $this->quickfilterIds; + $_SESSION['multipersonsearch'][$this->name]['searchObject'] = serialize($this->searchObject); + $_SESSION['multipersonsearch'][$this->name]['navigationItem'] = $this->navigationItem; + $_SESSION['multipersonsearch'][$this->name]['dataDialogStatus'] = $this->dataDialogStatus; + } + + /** + * restores the internal data from a session. + */ + public function restoreFromSession() { + if (isset($_SESSION['multipersonsearch'][$this->name])) { + $this->title = $_SESSION['multipersonsearch'][$this->name]['title'] ?? ''; + $this->description = $_SESSION['multipersonsearch'][$this->name]['description'] ?? ''; + $this->quickfilterIds = $_SESSION['multipersonsearch'][$this->name]['quickfilterIds'] ?? []; + $this->additionalHMTL = $_SESSION['multipersonsearch'][$this->name]['additionalHMTL'] ?? ''; + $this->executeURL = html_entity_decode($_SESSION['multipersonsearch'][$this->name]['executeURL'] ?? ''); + $this->jsFunction = $_SESSION['multipersonsearch'][$this->name]['jsFunction'] ?? ''; + $this->pageURL = $_SESSION['multipersonsearch'][$this->name]['pageURL'] ?? ''; + $this->defaultSelectableUsersIDs = $_SESSION['multipersonsearch'][$this->name]['defaultSelectableUsersIDs'] ?? []; + $this->defaultSelectedUsersIDs = $_SESSION['multipersonsearch'][$this->name]['defaultSelectedUsersIDs'] ?? []; + $this->searchObject = unserialize($_SESSION['multipersonsearch'][$this->name]['searchObject'] ?? null); + $this->navigationItem = $_SESSION['multipersonsearch'][$this->name]['navigationItem'] ?? null; + $this->dataDialogStatus = $_SESSION['multipersonsearch'][$this->name]['dataDialogStatus'] ?? ''; + } + } + + /** + * clears the session data. + */ + public function clearSession() { + unset($_SESSION['multipersonsearch'][$this->name]); + } + + /** + * sets default values of the internal variables. + */ + private function setDefaultValues() { + $this->title = ''; + $this->description = _('Bitte wählen Sie aus, wen Sie hinzufügen möchten.'); + $this->linkIconPath = Icon::create('add'); + } + + /** + * clear unused sessions. + */ + private function collectGarbage() { + $maxLifeTime = 30; // minutes + foreach ($_SESSION['multipersonsearch'] as $key=>$value) { + if (time() - $value['lastUse'] > $maxLifeTime * 60) { + unset($_SESSION['multipersonsearch'][$key]); + } + } + } + +} diff --git a/lib/classes/NotificationCenter.class.php b/lib/classes/NotificationCenter.class.php deleted file mode 100644 index aaaa9e8..0000000 --- a/lib/classes/NotificationCenter.class.php +++ /dev/null @@ -1,174 +0,0 @@ - $predicate, - 'observer' => [$observer, $method] - ]; - } - - /** - * Remove an object registered with the NotificationCenter. - * Trying to remove an observer that was not registered is - * allowed and has no effect. - * - * @param object $observer object to be removed - * @param string $event name of event (may be NULL) - * @param mixed $object subject to observe (may be NULL) - */ - public static function removeObserver($observer, $event = NULL, $object = NULL) - { - if ($event === NULL) { - $events = array_keys(self::$observers); - } else if (isset(self::$observers[$event])) { - $events = [$event]; - } else { - return; - } - - foreach ($events as $event) { - foreach (self::$observers[$event] as $index => $list) { - if ($object === NULL - || $list['predicate'] && $list['predicate']($object)) { - - if ($list['observer'][0] === $observer) { - unset(self::$observers[$event][$index]); - } - } - } - } - } - - /** - * Post an event notification to all registered observers. - * Only observers registered for this event type and subject - * are notified. - * - * @param string $event name of this notification - * @param mixed $object subject of this notification - * @param mixed $user_data additional information (optional) - * - * @throws NotificationVetoException on observer veto - */ - public static function postNotification($event, $object, $user_data = null) - { - $current_observers = []; - foreach (self::$observers as $e => $l) { - if ($e === '' || fnmatch($e, $event, FNM_NOESCAPE)) { - $current_observers = array_merge($current_observers, $l); - } - } - - foreach ($current_observers as $list) { - if (!$list['predicate'] || $list['predicate']($object)) { - call_user_func($list['observer'], $event, $object, $user_data); - } - } - } - - /** - * Convenience method that uses a jQuery like structure for event - * registration by closures. - * - * @param string $event - * @param Callable $callback - * @param mixed $object - * @since Stud.IP 4.2 - */ - public static function on($event, Callable $callback, $object = null) - { - if ($callback instanceof Closure || is_object($callback)) { - static::addObserver($callback, '__invoke', $event, $object); - } elseif (is_array($callback)) { - static::addObserver($callback[0], $callback[1], $event, $object); - } elseif (is_string($callback)) { - throw new Exception('Strings as callable may not be passed to ' . __METHOD__); - } - } - - /** - * Convenience method that uses a jQuery like structure for event - * unregistration by closures. - * - * @param string $event - * @param Callable $callback - * @param mixed $object - * @since Stud.IP 4.2 - */ - public static function off($event, Callable $callback, $object = null) - { - if ($callback instanceof Closure || is_object($callback)) { - static::removeObserver($callback, $event, $object); - } elseif (is_array($callback)) { - static::removeObserver($callback[0], $event, $object); - } elseif (is_string($callback)) { - throw new Exception('Strings as callable may not be passed to ' . __METHOD__); - } - } -} diff --git a/lib/classes/NotificationCenter.php b/lib/classes/NotificationCenter.php new file mode 100644 index 0000000..aaaa9e8 --- /dev/null +++ b/lib/classes/NotificationCenter.php @@ -0,0 +1,174 @@ + $predicate, + 'observer' => [$observer, $method] + ]; + } + + /** + * Remove an object registered with the NotificationCenter. + * Trying to remove an observer that was not registered is + * allowed and has no effect. + * + * @param object $observer object to be removed + * @param string $event name of event (may be NULL) + * @param mixed $object subject to observe (may be NULL) + */ + public static function removeObserver($observer, $event = NULL, $object = NULL) + { + if ($event === NULL) { + $events = array_keys(self::$observers); + } else if (isset(self::$observers[$event])) { + $events = [$event]; + } else { + return; + } + + foreach ($events as $event) { + foreach (self::$observers[$event] as $index => $list) { + if ($object === NULL + || $list['predicate'] && $list['predicate']($object)) { + + if ($list['observer'][0] === $observer) { + unset(self::$observers[$event][$index]); + } + } + } + } + } + + /** + * Post an event notification to all registered observers. + * Only observers registered for this event type and subject + * are notified. + * + * @param string $event name of this notification + * @param mixed $object subject of this notification + * @param mixed $user_data additional information (optional) + * + * @throws NotificationVetoException on observer veto + */ + public static function postNotification($event, $object, $user_data = null) + { + $current_observers = []; + foreach (self::$observers as $e => $l) { + if ($e === '' || fnmatch($e, $event, FNM_NOESCAPE)) { + $current_observers = array_merge($current_observers, $l); + } + } + + foreach ($current_observers as $list) { + if (!$list['predicate'] || $list['predicate']($object)) { + call_user_func($list['observer'], $event, $object, $user_data); + } + } + } + + /** + * Convenience method that uses a jQuery like structure for event + * registration by closures. + * + * @param string $event + * @param Callable $callback + * @param mixed $object + * @since Stud.IP 4.2 + */ + public static function on($event, Callable $callback, $object = null) + { + if ($callback instanceof Closure || is_object($callback)) { + static::addObserver($callback, '__invoke', $event, $object); + } elseif (is_array($callback)) { + static::addObserver($callback[0], $callback[1], $event, $object); + } elseif (is_string($callback)) { + throw new Exception('Strings as callable may not be passed to ' . __METHOD__); + } + } + + /** + * Convenience method that uses a jQuery like structure for event + * unregistration by closures. + * + * @param string $event + * @param Callable $callback + * @param mixed $object + * @since Stud.IP 4.2 + */ + public static function off($event, Callable $callback, $object = null) + { + if ($callback instanceof Closure || is_object($callback)) { + static::removeObserver($callback, $event, $object); + } elseif (is_array($callback)) { + static::removeObserver($callback[0], $event, $object); + } elseif (is_string($callback)) { + throw new Exception('Strings as callable may not be passed to ' . __METHOD__); + } + } +} diff --git a/lib/classes/PrivacyObject.interface.php b/lib/classes/PrivacyObject.interface.php deleted file mode 100644 index 8129634..0000000 --- a/lib/classes/PrivacyObject.interface.php +++ /dev/null @@ -1,11 +0,0 @@ -questionnaire. - */ -interface QuestionType { - - /** - * Returns a specific icon for this type of question. Note this is not bound to the - * object but called staticly. - * @return Icon the specific icon for this type of question - */ - static public function getIcon(bool $active = false) : Icon; - - - /** - * Returns the shape of the icon that is used in vue. - * @return string - */ - static public function getIconShape(); - - /** - * Returns the name of the type of question like "Frage" or "Test" or "Dateiablage" - * This name is not showed to the participant of the questionnaire, but to the editor. - * It might get displayed like "add another Frage to questionnaire", where 'Frage' - * is the name of the type. - * @return string : the name of this type of question. - */ - static public function getName(); - - /** - * Returns an array with two parts: First one is the name of the component for editing the question. Second - * one is the import path of the component. Plugins can use this to get their component imported. - * @return Array - */ - static public function getEditingComponent(); - - /** - * Usually the $questiondata is already in the correct format. But for some question types - * some data have to be manipulated by for example the HTML-purifier. So this takes - * the questiondata and changed them before they get stored. - * @param $questiondata - * @return mixed - */ - public function beforeStoringQuestiondata($questiondata); - - /** - * Display the question to the user. This template will be embedded into a - * html
-tag. Maybe more questions will appear in that form and maybe - * more questions of the same type. This is important to know, so that you - * prefix all the input-fields. - * - * Wrong: "> - * - * Right: getId()]['a'] ? " checked" : "" ?>"> - * - * Try to prefix all your input variables at least with the id of the question, - * so that they will never conflict with other variables. - * - * @return Flexi\Template - */ - public function getDisplayTemplate(); - - /** - * Uses current user and Request-variables to create an answer for the question. - * Return this answer. It does not necessarily be stored to the database! - * @return QuestionnaireAnswer or derived - */ - public function createAnswer(); - - /** - * In the evaluation of the questionnaire you can click on a certain answer and get the evaluation filtered - * by the the people that have given that answer. This method asks from the question, what user_ids have - * given the answer_option. Answer option could be anything that this question understands as an answer. - * @param $answer_option - * @return mixed - */ - public function getUserIdsOfFilteredAnswer($answer_option); - - /** - * Returns a template with the results of this question. - * - * @param $only_user_ids : array\null array of user_ids that the results should be restricted to. - * this is used to show only a subset of results to the user for - * visible evaluation of the results. If the questionnaire is anonymous - * just do nothing. - * - * @return Flexi\Template - */ - public function getResultTemplate($only_user_ids = null); - - /** - * This method is called to generate a csv-export from a whole questionnaire. - * The returned array looks like this: - * array( - * 'Answer 1' => array('e7a0a84b161f3e8c09b4a0a2e8a58147' => "1", '7e81ec247c151c02ffd479511e24cc03' => "0"), - * 'Answer 2' => array('e7a0a84b161f3e8c09b4a0a2e8a58147' => "1", '7e81ec247c151c02ffd479511e24cc03' => "1") - * ) - * This is a two-dimensional array. The first array provides a set of answers. The values of - * this array are themselves arrays with the user_ids as indexes and the user's - * answers as the values. With this construction you can evaluate single-choice tests - * as well as multiple-choice tests. - * @return array : indexed with user_id and valued with the value of the answer. - * If your QuestionType allows more than one value (i.e. multiple-choice) - * you might need to serialize it. - */ - public function getResultArray(); - - /** - * A method to be called after the questionnaire has ended. - * @return void - */ - public function onEnding(); -} diff --git a/lib/classes/QuestionType.php b/lib/classes/QuestionType.php new file mode 100644 index 0000000..03019fe --- /dev/null +++ b/lib/classes/QuestionType.php @@ -0,0 +1,118 @@ +questionnaire. + */ +interface QuestionType { + + /** + * Returns a specific icon for this type of question. Note this is not bound to the + * object but called staticly. + * @return Icon the specific icon for this type of question + */ + static public function getIcon(bool $active = false) : Icon; + + + /** + * Returns the shape of the icon that is used in vue. + * @return string + */ + static public function getIconShape(); + + /** + * Returns the name of the type of question like "Frage" or "Test" or "Dateiablage" + * This name is not showed to the participant of the questionnaire, but to the editor. + * It might get displayed like "add another Frage to questionnaire", where 'Frage' + * is the name of the type. + * @return string : the name of this type of question. + */ + static public function getName(); + + /** + * Returns an array with two parts: First one is the name of the component for editing the question. Second + * one is the import path of the component. Plugins can use this to get their component imported. + * @return Array + */ + static public function getEditingComponent(); + + /** + * Usually the $questiondata is already in the correct format. But for some question types + * some data have to be manipulated by for example the HTML-purifier. So this takes + * the questiondata and changed them before they get stored. + * @param $questiondata + * @return mixed + */ + public function beforeStoringQuestiondata($questiondata); + + /** + * Display the question to the user. This template will be embedded into a + * html -tag. Maybe more questions will appear in that form and maybe + * more questions of the same type. This is important to know, so that you + * prefix all the input-fields. + * + * Wrong: "> + * + * Right: getId()]['a'] ? " checked" : "" ?>"> + * + * Try to prefix all your input variables at least with the id of the question, + * so that they will never conflict with other variables. + * + * @return Flexi\Template + */ + public function getDisplayTemplate(); + + /** + * Uses current user and Request-variables to create an answer for the question. + * Return this answer. It does not necessarily be stored to the database! + * @return QuestionnaireAnswer or derived + */ + public function createAnswer(); + + /** + * In the evaluation of the questionnaire you can click on a certain answer and get the evaluation filtered + * by the the people that have given that answer. This method asks from the question, what user_ids have + * given the answer_option. Answer option could be anything that this question understands as an answer. + * @param $answer_option + * @return mixed + */ + public function getUserIdsOfFilteredAnswer($answer_option); + + /** + * Returns a template with the results of this question. + * + * @param $only_user_ids : array\null array of user_ids that the results should be restricted to. + * this is used to show only a subset of results to the user for + * visible evaluation of the results. If the questionnaire is anonymous + * just do nothing. + * + * @return Flexi\Template + */ + public function getResultTemplate($only_user_ids = null); + + /** + * This method is called to generate a csv-export from a whole questionnaire. + * The returned array looks like this: + * array( + * 'Answer 1' => array('e7a0a84b161f3e8c09b4a0a2e8a58147' => "1", '7e81ec247c151c02ffd479511e24cc03' => "0"), + * 'Answer 2' => array('e7a0a84b161f3e8c09b4a0a2e8a58147' => "1", '7e81ec247c151c02ffd479511e24cc03' => "1") + * ) + * This is a two-dimensional array. The first array provides a set of answers. The values of + * this array are themselves arrays with the user_ids as indexes and the user's + * answers as the values. With this construction you can evaluate single-choice tests + * as well as multiple-choice tests. + * @return array : indexed with user_id and valued with the value of the answer. + * If your QuestionType allows more than one value (i.e. multiple-choice) + * you might need to serialize it. + */ + public function getResultArray(); + + /** + * A method to be called after the questionnaire has ended. + * @return void + */ + public function onEnding(); +} diff --git a/lib/classes/QuickSearch.class.php b/lib/classes/QuickSearch.class.php deleted file mode 100644 index d3983a3..0000000 --- a/lib/classes/QuickSearch.class.php +++ /dev/null @@ -1,503 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This class provides a small and intuitive GUI-element for an instant search of - * courses, persons, institutes or other items. Mainly the structure to include - * a QuickSearch-field is the following: - * //code-begin - * $sf = new QuickSearch("username"); - * $sf->withButton(); - * $sf->specialSQL("SELECT username, CONCAT(Vorname, \" \", Nachname) " . - * "FROM auth_user_md5 " . - * "WHERE CONCAT(Vorname, \" \", Nachname) LIKE :input " . - * "AND perms = 'dozent'", _("Dozenten suchen")); - * print $sf->render(); - * //code-end - * This code should be included into an html -tag. It will provide a nice looking - * input-field with a button (this is the $sf->withButton() command for), a javascript - * instant-search for your items (in this case 'Dozenten') and also a non-javascript - * oldschool version of a searchfield with a select-box in step 2 of the search (first - * write what you search, click on the button and then select in the select-box what - * you wanted to have). - * You can handle the searchfield in your form-tag as if you have written an - * ''. - * - * For most cases you may only want to search for persons, courses or institutes. - * Thus a shortcut is implemented in this class. You may write - * //code-begin - * $sf = new QuickSearch("username", "username"); - * $sf->withButton(); - * print $sf->render(); - * //code-end - * to receive a searchfield that is automatically searching for users and inserts - * the selected users's username in the searchfield. The first parameter of the - * constructor 'new Quicksearch' is the name of the variable in your form and is - * completely free to name. The (optional) second parameter describes what you are - * searching for: username, user_id, Seminar_id or Institut_id. - * - * Also you can do method-chaining with this class, so you can press everything - * you need infront of your semicolon. Watch this example: - * //code-begin - * print QuickSearch::get("username", "username")->withButton->render(); - * //code-end - * - * Lastly you can replace the second argument of the constructor (or get-method) - * by an object whose class extends the SearchType-class. This might be - * useful to create your own searches and handle them with oop-style or even - * use a totally different search-engine like lucene-index! All you need to - * do so is implement your searchclass and follow this example: - * //code-begin - * class TeacherSearch extends SearchType { - * ... - * } - * $searcher = new TeacherSearch(); - * print QuickSearch::get("username", $searcher)->withButton->render(); - * //code-end - * Watch the SearchType class in lib/classes/searchtypes/SearchType.class.php - * for details. - * Enjoy! - */ -class QuickSearch -{ - - const GC_LIFETIME = 10800; // = 3 * 60 * 60 = 3 hours - - static $count_QS = 0; //static counter of all instances of this class - - private $name; //name of the input/select field - private $search; //may be an object or a string - private $avatarLike; //like "user_id", "username", "Seminar_id" or stuff - private $withButton; //if true, the field will be displayed with a looking-glass-button to click on - private $selectBox = true; - private $withAttributes = []; - private $box_width = "233"; //width of the box withButton - private $box_align = "right";//align of the lookingglass in the withButton-box - private $autocomplete_disabled = false; - private $search_button_name; - private $reset_button_name; - private $defaultID = null; - private $defaultName = null; - private $jsfunction = null; - private $inputClass = null; - private $inputStyle = null; - private $specialQuery = null; - private $minLength = 3; - - - /** - * Deletes all older requests that have not been used for three hours - * from the session - * - * @return int Number of removed searches - */ - public static function garbageCollect() - { - if (empty($_SESSION['QuickSearches'])) { - return 0; - } - $count = count($_SESSION['QuickSearches']); - - $_SESSION['QuickSearches'] = array_filter($_SESSION['QuickSearches'], function ($query) { - return $query['time'] + QuickSearch::GC_LIFETIME > time(); - }); - - return $count - count($_SESSION['QuickSearches']); - } - - /** - * Retrieves the search object for the given id previously stored in - * the session. - * - * @param String $query_id Id of the quicksearch object - * @return SearchType Quicksearch object - * @throws RuntimeException when the given query does not exist in session - */ - public static function getFromSession($query_id) - { - self::garbageCollect(); - - if (!isset($_SESSION['QuickSearches'][$query_id])) { - throw new RuntimeException('Quicksearch id not in session'); - } - - // Store last access to search - $_SESSION['QuickSearches'][$query_id]['time'] = time(); - - $query = $_SESSION['QuickSearches'][$query_id]; - - if ($query['includePath']) { - include_once $query['includePath']; - } - - return unserialize($query['object']); - - } - - /** - * returns an instance of QuickSearch so you can use it as singleton - * - * @param string $name the name of the destinated variable in your html-form. Handle it - * as if it was an '' input. - * @param string $search if set to user_id, username, Seminar_id, Arbeitsgruppe_id or Institute_id - * the searchfield will automatically search for persons, courses, workgroups, institutes and - * you don't need to call the specialSearch-method. - * - * @return object of type QuickSearch - */ - public static function get($name, $search = NULL) - { - return new static($name, $search); - } - - - /** - * constructor which prepares a searchfield for persons, courses, institutes or - * special items you may want to search for. This is a GUI-class, see - * QuickSearch.class.php for further documentation. - * - * @param string $name the name of the destinated variable in your html-form. Handle it - * as if it was an '' input. - * @param string $search if set to user_id, username, Seminar_id, Arbeitsgruppe_id or Institute_id - * the searchfield will automatically search for persons, courses, workgroups, institutes and - * you don't need to call the specialSearch-method. - * - * @return void - */ - final public function __construct($name, $search = NULL) - { - self::$count_QS++; - $this->name = $name; - $this->withButton = false; - $this->avatarLike = ""; - if ($search instanceof SearchType) { - $this->search = $search; - } else { - $this->search = NULL; - } - $this->setAttributes([]); - } - - /** - * if set to true, the searchfield will be a nice-looking grey searchfield with - * a magnifier-symbol as a submit-button. Set this to false to create your own - * submit-button and style of the form. - * @param mixed $design associative array of params. - * - * @return QuickSearch - */ - public function withButton($design = []) - { - $this->withButton = true; - if (isset($design['width'])) { - $this->box_width = $design['width']; - } - $this->box_align = $design['align'] ?? "right"; - $this->search_button_name = $design['search_button_name'] ?? ''; - $this->reset_button_name = $design['reset_button_name'] ?? ''; - return $this; - } - - /** - * this will disable a submit button for the searchfield - * - * @return QuickSearch - */ - public function withoutButton() - { - $this->withButton = false; - return $this; - } - - /** - * Here you can set a default-value for the searchfield - * - * @param string $valueID the default-ID that should be stored - * @param string $valueName the default value, that should be displayed - * - remember that these may not be the same, they may be - * something like "ae2b1fca515949e5d54fb22b8ed95575", "test_dozent" - * - * @return QuickSearch - */ - public function defaultValue($valueID, $valueName) - { - $this->defaultID = $valueID; - $this->defaultName = $valueName; - return $this; - } - - /** - * defines a css class for the searchfield - * - * @param string $class any css class name for the "input type=text" tag - * - * @return QuickSearch - */ - public function setInputClass($class) - { - $this->withAttributes['class'] = $class; - return $this; - } - - /** - * defines css-proporties for searchfield that will be included as 'style="$style"' - * - * @param string $style one or more css-proporties separated with ";" - * - * @return QuickSearch - */ - public function setInputStyle($style) - { - $this->withAttributes['style'] = $style; - return $this; - } - - /** - * Set the minimum length to start searching - * - * @param int $minLength - * - * @return QuickSearch - */ - public function setMinLength(int $minLength) - { - $this->minLength = $minLength; - - return $this; - } - - /** - * disables the select-box, which is displayed for non-JS users who will - * choose with this box, which item they want to have. - * - * @param bool $set false if we DO want a select-box, false otherwise - * - * @return QuickSearch - */ - public function noSelectbox($set = true) - { - $this->selectBox = !$set; - return $this; - } - - /** - * disables the ajax autocomplete for this searchfield - * If you want to disable all QuickSearches, you better use the - * config variable global -> AJAX_AUTOCOMPLETE_DISABLED - * @param disable boolean: true (default) to disable, false to enable - * autocomplete via ajax. - * @return QuickSearch - */ - public function disableAutocomplete($disable = true) { - $this->autocomplete_disabled = $disable; - return $this; - } - - /** - * set a JavaScript-function to be fired after the user has selected a - * value in the QuickSearch field. Arguments are: - * function fireme(id_of_item, text_of_item) - * example setting: QS->fireJSFunctionOnSelect('fireme'); - * - * @param string $function_name the name of the javascript function - * - * @return QuickSearch - */ - public function fireJSFunctionOnSelect($function_name) - { - $this->jsfunction = $function_name; - return $this; - } - - /** - * assigns special attributes to the html-element of the searchfield - * - * @param array $ttr_array like array("title" => "hello world") - * - * @return QuickSearch - */ - public function setAttributes($attr_array) - { - if (is_array($attr_array)) { - $this->withAttributes = $attr_array; - } - if (!isset($this->withAttributes['aria-label']) - && !isset($this->withAttributes['aria-labelledby']) - && $this->search) { - $this->withAttributes['aria-label'] = $this->search->getTitle(); - } - return $this; - } - - /** - * Returns whether the underlying search type requires an extended - * layout. - * - * @return bool indicating whether an extended layout is required - */ - public function hasExtendedLayout() - { - return !empty($this->search->extendedLayout); - } - - /** - * last step: display everything and be happy! - * comment: the Ajax-Result (for the javascript-instant-search) will be also displayed here, - * but that does not need to concern you. - * - * @return string - */ - public function render() - { - if (trim(Request::get($this->name.'_parameter')) - && (Request::get($this->name.'_parameter') != $this->beschriftung()) - && !Request::get($this->name) - && $this->selectBox) { - //No Javascript activated and having searched: - $searchresults = $this->searchresults(Request::get($this->name.'_parameter')); - - $template = $GLOBALS['template_factory']->open('quicksearch/selectbox.php'); - $template->set_attribute('withButton', $this->withButton); - $template->set_attribute('box_align', $this->box_align); - $template->set_attribute('box_width', $this->box_width); - $template->set_attribute('withAttributes', $this->withAttributes); - $template->set_attribute('searchresults', $searchresults); - $template->set_attribute('name', $this->name); - $template->set_attribute('search_button_name', $this->search_button_name); - $template->set_attribute('reset_button_name', $this->reset_button_name); - $template->set_attribute('extendedLayout', $this->hasExtendedLayout()); - return $template->render(); - - } else { - $query_id = $this->storeSearchInSession(); - - //Ausgabe: - $template = $GLOBALS['template_factory']->open('quicksearch/inputfield.php'); - $template->set_attribute('withButton', $this->withButton); - $template->set_attribute('box_align', $this->box_align); - $template->set_attribute('box_width', $this->box_width); - $template->set_attribute('inputStyle', $this->inputStyle ?? ''); - $template->set_attribute('beschriftung', $this->beschriftung()); - $template->set_attribute('name', $this->name); - $template->set_attribute('defaultID', $this->defaultID); - $template->set_attribute('defaultName', $this->defaultName); - $template->set_attribute('withAttributes', $this->withAttributes ? $this->withAttributes : []); - $template->set_attribute('jsfunction', $this->jsfunction); - $template->set_attribute('autocomplete_disabled', Config::get()->getValue("AJAX_AUTOCOMPLETE_DISABLED") || $this->autocomplete_disabled); - $template->set_attribute('count_QS', self::$count_QS); - $template->set_attribute('id', $this->getId()); - $template->set_attribute('query_id', $query_id); - $template->set_attribute('minLength', $this->minLength); - $template->set_attribute('search_button_name', $this->search_button_name); - $template->set_attribute('reset_button_name', $this->reset_button_name); - $template->set_attribute('extendedLayout', $this->hasExtendedLayout()); - return $template->render(); - } - } - - /** - * Convert quicksearch to string by rendering it - * - * @return string rendered html - */ - public function __toString() - { - return $this->render(); - } - - /** - * returns the id string used for the input field - * - * @return string - */ - public function getId() - { - return "qs_".md5($this->name) . '_' . (int)self::$count_QS; - } - - ////////////////////////////////////////////////////////////////////////////// - // private-methods // - ////////////////////////////////////////////////////////////////////////////// - - /** - * private method to get a result-array in the way of array(array(item_id, item-name)). - * - * @param string $request the request from the searchfield typed by the user. - * - * @return array array(array(item_id, item-name), ...). - */ - private function searchresults($request) - { - if ($this->search instanceof SearchType) { - try { - $results = $this->search->getResults($request, $_REQUEST); - } catch (Exception $exception) { - //Der Programmierer will ja seine Fehler sehen: - return [["", $exception->getMessage()]]; - } - return $results; - } else { - $result = [["", _("Kein korrektes Suchobjekt angegeben.")]]; - return $result; - } - } - - /** - * get the label of the searchfield that is written in javascript and disappears - * when the user focusses on the searchfield. - * - * @return string localized-string - */ - private function beschriftung() - { - if ($this->search instanceof SearchType) { - return $this->search->getTitle(); - } else { - return ""; - } - } - - /** - * Abfrage in der Session speichern - * - * @return string - */ - protected function storeSearchInSession(): string - { - $query_id = md5(serialize($this->search)); - - // Prepare object - $item = [ - 'time' => time(), - ]; - - if ($this->search instanceof SearchType) { - $item['object'] = serialize($this->search); - if ($this->search instanceof SearchType) { - $item['includePath'] = $this->search->includePath(); - } - } else { - $item['query'] = $this->search; - } - - // Actually storing in session - if (!isset($_SESSION['QuickSearches'])) { - $_SESSION['QuickSearches'] = []; - } - $_SESSION['QuickSearches'][$query_id] = $item; - - return $query_id; - } -} diff --git a/lib/classes/QuickSearch.php b/lib/classes/QuickSearch.php new file mode 100644 index 0000000..d3983a3 --- /dev/null +++ b/lib/classes/QuickSearch.php @@ -0,0 +1,503 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This class provides a small and intuitive GUI-element for an instant search of + * courses, persons, institutes or other items. Mainly the structure to include + * a QuickSearch-field is the following: + * //code-begin + * $sf = new QuickSearch("username"); + * $sf->withButton(); + * $sf->specialSQL("SELECT username, CONCAT(Vorname, \" \", Nachname) " . + * "FROM auth_user_md5 " . + * "WHERE CONCAT(Vorname, \" \", Nachname) LIKE :input " . + * "AND perms = 'dozent'", _("Dozenten suchen")); + * print $sf->render(); + * //code-end + * This code should be included into an html -tag. It will provide a nice looking + * input-field with a button (this is the $sf->withButton() command for), a javascript + * instant-search for your items (in this case 'Dozenten') and also a non-javascript + * oldschool version of a searchfield with a select-box in step 2 of the search (first + * write what you search, click on the button and then select in the select-box what + * you wanted to have). + * You can handle the searchfield in your form-tag as if you have written an + * ''. + * + * For most cases you may only want to search for persons, courses or institutes. + * Thus a shortcut is implemented in this class. You may write + * //code-begin + * $sf = new QuickSearch("username", "username"); + * $sf->withButton(); + * print $sf->render(); + * //code-end + * to receive a searchfield that is automatically searching for users and inserts + * the selected users's username in the searchfield. The first parameter of the + * constructor 'new Quicksearch' is the name of the variable in your form and is + * completely free to name. The (optional) second parameter describes what you are + * searching for: username, user_id, Seminar_id or Institut_id. + * + * Also you can do method-chaining with this class, so you can press everything + * you need infront of your semicolon. Watch this example: + * //code-begin + * print QuickSearch::get("username", "username")->withButton->render(); + * //code-end + * + * Lastly you can replace the second argument of the constructor (or get-method) + * by an object whose class extends the SearchType-class. This might be + * useful to create your own searches and handle them with oop-style or even + * use a totally different search-engine like lucene-index! All you need to + * do so is implement your searchclass and follow this example: + * //code-begin + * class TeacherSearch extends SearchType { + * ... + * } + * $searcher = new TeacherSearch(); + * print QuickSearch::get("username", $searcher)->withButton->render(); + * //code-end + * Watch the SearchType class in lib/classes/searchtypes/SearchType.class.php + * for details. + * Enjoy! + */ +class QuickSearch +{ + + const GC_LIFETIME = 10800; // = 3 * 60 * 60 = 3 hours + + static $count_QS = 0; //static counter of all instances of this class + + private $name; //name of the input/select field + private $search; //may be an object or a string + private $avatarLike; //like "user_id", "username", "Seminar_id" or stuff + private $withButton; //if true, the field will be displayed with a looking-glass-button to click on + private $selectBox = true; + private $withAttributes = []; + private $box_width = "233"; //width of the box withButton + private $box_align = "right";//align of the lookingglass in the withButton-box + private $autocomplete_disabled = false; + private $search_button_name; + private $reset_button_name; + private $defaultID = null; + private $defaultName = null; + private $jsfunction = null; + private $inputClass = null; + private $inputStyle = null; + private $specialQuery = null; + private $minLength = 3; + + + /** + * Deletes all older requests that have not been used for three hours + * from the session + * + * @return int Number of removed searches + */ + public static function garbageCollect() + { + if (empty($_SESSION['QuickSearches'])) { + return 0; + } + $count = count($_SESSION['QuickSearches']); + + $_SESSION['QuickSearches'] = array_filter($_SESSION['QuickSearches'], function ($query) { + return $query['time'] + QuickSearch::GC_LIFETIME > time(); + }); + + return $count - count($_SESSION['QuickSearches']); + } + + /** + * Retrieves the search object for the given id previously stored in + * the session. + * + * @param String $query_id Id of the quicksearch object + * @return SearchType Quicksearch object + * @throws RuntimeException when the given query does not exist in session + */ + public static function getFromSession($query_id) + { + self::garbageCollect(); + + if (!isset($_SESSION['QuickSearches'][$query_id])) { + throw new RuntimeException('Quicksearch id not in session'); + } + + // Store last access to search + $_SESSION['QuickSearches'][$query_id]['time'] = time(); + + $query = $_SESSION['QuickSearches'][$query_id]; + + if ($query['includePath']) { + include_once $query['includePath']; + } + + return unserialize($query['object']); + + } + + /** + * returns an instance of QuickSearch so you can use it as singleton + * + * @param string $name the name of the destinated variable in your html-form. Handle it + * as if it was an '' input. + * @param string $search if set to user_id, username, Seminar_id, Arbeitsgruppe_id or Institute_id + * the searchfield will automatically search for persons, courses, workgroups, institutes and + * you don't need to call the specialSearch-method. + * + * @return object of type QuickSearch + */ + public static function get($name, $search = NULL) + { + return new static($name, $search); + } + + + /** + * constructor which prepares a searchfield for persons, courses, institutes or + * special items you may want to search for. This is a GUI-class, see + * QuickSearch.class.php for further documentation. + * + * @param string $name the name of the destinated variable in your html-form. Handle it + * as if it was an '' input. + * @param string $search if set to user_id, username, Seminar_id, Arbeitsgruppe_id or Institute_id + * the searchfield will automatically search for persons, courses, workgroups, institutes and + * you don't need to call the specialSearch-method. + * + * @return void + */ + final public function __construct($name, $search = NULL) + { + self::$count_QS++; + $this->name = $name; + $this->withButton = false; + $this->avatarLike = ""; + if ($search instanceof SearchType) { + $this->search = $search; + } else { + $this->search = NULL; + } + $this->setAttributes([]); + } + + /** + * if set to true, the searchfield will be a nice-looking grey searchfield with + * a magnifier-symbol as a submit-button. Set this to false to create your own + * submit-button and style of the form. + * @param mixed $design associative array of params. + * + * @return QuickSearch + */ + public function withButton($design = []) + { + $this->withButton = true; + if (isset($design['width'])) { + $this->box_width = $design['width']; + } + $this->box_align = $design['align'] ?? "right"; + $this->search_button_name = $design['search_button_name'] ?? ''; + $this->reset_button_name = $design['reset_button_name'] ?? ''; + return $this; + } + + /** + * this will disable a submit button for the searchfield + * + * @return QuickSearch + */ + public function withoutButton() + { + $this->withButton = false; + return $this; + } + + /** + * Here you can set a default-value for the searchfield + * + * @param string $valueID the default-ID that should be stored + * @param string $valueName the default value, that should be displayed + * - remember that these may not be the same, they may be + * something like "ae2b1fca515949e5d54fb22b8ed95575", "test_dozent" + * + * @return QuickSearch + */ + public function defaultValue($valueID, $valueName) + { + $this->defaultID = $valueID; + $this->defaultName = $valueName; + return $this; + } + + /** + * defines a css class for the searchfield + * + * @param string $class any css class name for the "input type=text" tag + * + * @return QuickSearch + */ + public function setInputClass($class) + { + $this->withAttributes['class'] = $class; + return $this; + } + + /** + * defines css-proporties for searchfield that will be included as 'style="$style"' + * + * @param string $style one or more css-proporties separated with ";" + * + * @return QuickSearch + */ + public function setInputStyle($style) + { + $this->withAttributes['style'] = $style; + return $this; + } + + /** + * Set the minimum length to start searching + * + * @param int $minLength + * + * @return QuickSearch + */ + public function setMinLength(int $minLength) + { + $this->minLength = $minLength; + + return $this; + } + + /** + * disables the select-box, which is displayed for non-JS users who will + * choose with this box, which item they want to have. + * + * @param bool $set false if we DO want a select-box, false otherwise + * + * @return QuickSearch + */ + public function noSelectbox($set = true) + { + $this->selectBox = !$set; + return $this; + } + + /** + * disables the ajax autocomplete for this searchfield + * If you want to disable all QuickSearches, you better use the + * config variable global -> AJAX_AUTOCOMPLETE_DISABLED + * @param disable boolean: true (default) to disable, false to enable + * autocomplete via ajax. + * @return QuickSearch + */ + public function disableAutocomplete($disable = true) { + $this->autocomplete_disabled = $disable; + return $this; + } + + /** + * set a JavaScript-function to be fired after the user has selected a + * value in the QuickSearch field. Arguments are: + * function fireme(id_of_item, text_of_item) + * example setting: QS->fireJSFunctionOnSelect('fireme'); + * + * @param string $function_name the name of the javascript function + * + * @return QuickSearch + */ + public function fireJSFunctionOnSelect($function_name) + { + $this->jsfunction = $function_name; + return $this; + } + + /** + * assigns special attributes to the html-element of the searchfield + * + * @param array $ttr_array like array("title" => "hello world") + * + * @return QuickSearch + */ + public function setAttributes($attr_array) + { + if (is_array($attr_array)) { + $this->withAttributes = $attr_array; + } + if (!isset($this->withAttributes['aria-label']) + && !isset($this->withAttributes['aria-labelledby']) + && $this->search) { + $this->withAttributes['aria-label'] = $this->search->getTitle(); + } + return $this; + } + + /** + * Returns whether the underlying search type requires an extended + * layout. + * + * @return bool indicating whether an extended layout is required + */ + public function hasExtendedLayout() + { + return !empty($this->search->extendedLayout); + } + + /** + * last step: display everything and be happy! + * comment: the Ajax-Result (for the javascript-instant-search) will be also displayed here, + * but that does not need to concern you. + * + * @return string + */ + public function render() + { + if (trim(Request::get($this->name.'_parameter')) + && (Request::get($this->name.'_parameter') != $this->beschriftung()) + && !Request::get($this->name) + && $this->selectBox) { + //No Javascript activated and having searched: + $searchresults = $this->searchresults(Request::get($this->name.'_parameter')); + + $template = $GLOBALS['template_factory']->open('quicksearch/selectbox.php'); + $template->set_attribute('withButton', $this->withButton); + $template->set_attribute('box_align', $this->box_align); + $template->set_attribute('box_width', $this->box_width); + $template->set_attribute('withAttributes', $this->withAttributes); + $template->set_attribute('searchresults', $searchresults); + $template->set_attribute('name', $this->name); + $template->set_attribute('search_button_name', $this->search_button_name); + $template->set_attribute('reset_button_name', $this->reset_button_name); + $template->set_attribute('extendedLayout', $this->hasExtendedLayout()); + return $template->render(); + + } else { + $query_id = $this->storeSearchInSession(); + + //Ausgabe: + $template = $GLOBALS['template_factory']->open('quicksearch/inputfield.php'); + $template->set_attribute('withButton', $this->withButton); + $template->set_attribute('box_align', $this->box_align); + $template->set_attribute('box_width', $this->box_width); + $template->set_attribute('inputStyle', $this->inputStyle ?? ''); + $template->set_attribute('beschriftung', $this->beschriftung()); + $template->set_attribute('name', $this->name); + $template->set_attribute('defaultID', $this->defaultID); + $template->set_attribute('defaultName', $this->defaultName); + $template->set_attribute('withAttributes', $this->withAttributes ? $this->withAttributes : []); + $template->set_attribute('jsfunction', $this->jsfunction); + $template->set_attribute('autocomplete_disabled', Config::get()->getValue("AJAX_AUTOCOMPLETE_DISABLED") || $this->autocomplete_disabled); + $template->set_attribute('count_QS', self::$count_QS); + $template->set_attribute('id', $this->getId()); + $template->set_attribute('query_id', $query_id); + $template->set_attribute('minLength', $this->minLength); + $template->set_attribute('search_button_name', $this->search_button_name); + $template->set_attribute('reset_button_name', $this->reset_button_name); + $template->set_attribute('extendedLayout', $this->hasExtendedLayout()); + return $template->render(); + } + } + + /** + * Convert quicksearch to string by rendering it + * + * @return string rendered html + */ + public function __toString() + { + return $this->render(); + } + + /** + * returns the id string used for the input field + * + * @return string + */ + public function getId() + { + return "qs_".md5($this->name) . '_' . (int)self::$count_QS; + } + + ////////////////////////////////////////////////////////////////////////////// + // private-methods // + ////////////////////////////////////////////////////////////////////////////// + + /** + * private method to get a result-array in the way of array(array(item_id, item-name)). + * + * @param string $request the request from the searchfield typed by the user. + * + * @return array array(array(item_id, item-name), ...). + */ + private function searchresults($request) + { + if ($this->search instanceof SearchType) { + try { + $results = $this->search->getResults($request, $_REQUEST); + } catch (Exception $exception) { + //Der Programmierer will ja seine Fehler sehen: + return [["", $exception->getMessage()]]; + } + return $results; + } else { + $result = [["", _("Kein korrektes Suchobjekt angegeben.")]]; + return $result; + } + } + + /** + * get the label of the searchfield that is written in javascript and disappears + * when the user focusses on the searchfield. + * + * @return string localized-string + */ + private function beschriftung() + { + if ($this->search instanceof SearchType) { + return $this->search->getTitle(); + } else { + return ""; + } + } + + /** + * Abfrage in der Session speichern + * + * @return string + */ + protected function storeSearchInSession(): string + { + $query_id = md5(serialize($this->search)); + + // Prepare object + $item = [ + 'time' => time(), + ]; + + if ($this->search instanceof SearchType) { + $item['object'] = serialize($this->search); + if ($this->search instanceof SearchType) { + $item['includePath'] = $this->search->includePath(); + } + } else { + $item['query'] = $this->search; + } + + // Actually storing in session + if (!isset($_SESSION['QuickSearches'])) { + $_SESSION['QuickSearches'] = []; + } + $_SESSION['QuickSearches'][$query_id] = $item; + + return $query_id; + } +} diff --git a/lib/classes/Range.interface.php b/lib/classes/Range.interface.php deleted file mode 100644 index 97aa074..0000000 --- a/lib/classes/Range.interface.php +++ /dev/null @@ -1,66 +0,0 @@ - - * @license GPL2 or any later version - */ -interface Range -{ - /** - * Returns a descriptive text for the range type. - * - * @return string - */ - public function describeRange(); - - /** - * Returns a unique identificator for the range type. - * - * @return string - */ - public function getRangeType(); - - /** - * Returns the id of the current range - * - * @return mixed (string|int) - */ - public function getRangeId(); - - /** - * Returns the full name of the range (in given format). - * - * @param string $format - * @return string - */ - public function getFullName($format = 'default'); - - /** - * Returns the configuration object for this range. - * @return RangeConfig - */ - public function getConfiguration(); - - /** - * Decides whether the user may access the range. - * - * @param string|null $user_id Optional id of a user, defaults to current user - * @return bool - */ - public function isAccessibleToUser($user_id = null); - - /** - * Decides whether the user may edit/alter the range. - * - * @param string|null $user_id Optional id of a user, defaults to current user - * @return bool - */ - public function isEditableByUser($user_id = null); - - - /** - * @return string A string representation of the Range instance. - */ - public function __toString() : string; -} diff --git a/lib/classes/Range.php b/lib/classes/Range.php new file mode 100644 index 0000000..97aa074 --- /dev/null +++ b/lib/classes/Range.php @@ -0,0 +1,66 @@ + + * @license GPL2 or any later version + */ +interface Range +{ + /** + * Returns a descriptive text for the range type. + * + * @return string + */ + public function describeRange(); + + /** + * Returns a unique identificator for the range type. + * + * @return string + */ + public function getRangeType(); + + /** + * Returns the id of the current range + * + * @return mixed (string|int) + */ + public function getRangeId(); + + /** + * Returns the full name of the range (in given format). + * + * @param string $format + * @return string + */ + public function getFullName($format = 'default'); + + /** + * Returns the configuration object for this range. + * @return RangeConfig + */ + public function getConfiguration(); + + /** + * Decides whether the user may access the range. + * + * @param string|null $user_id Optional id of a user, defaults to current user + * @return bool + */ + public function isAccessibleToUser($user_id = null); + + /** + * Decides whether the user may edit/alter the range. + * + * @param string|null $user_id Optional id of a user, defaults to current user + * @return bool + */ + public function isEditableByUser($user_id = null); + + + /** + * @return string A string representation of the Range instance. + */ + public function __toString() : string; +} diff --git a/lib/classes/RangeConfig.class.php b/lib/classes/RangeConfig.class.php deleted file mode 100644 index b1030e3..0000000 --- a/lib/classes/RangeConfig.class.php +++ /dev/null @@ -1,223 +0,0 @@ - - * @author Jan-Hendrik Willms > - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -class RangeConfig extends Config -{ - /** - * range type - */ - const RANGE_TYPE = 'range'; - - /** - * cache of created RangeConfig instances - * @var array - */ - protected static $instances = []; - - /** - * range_id - * @var string - */ - private $range_id; - - /** - * returns cached instance for given range_id - * creates new objects if needed - * @param string $range_id - * @return RangeConfig - */ - public static function get() - { - if (func_num_args() === 0 || func_get_arg(0) === null) { - return new static(true); - } - - $range_id = func_get_arg(0); - - if ($range_id instanceof Range) { - $range_id = $range_id->getRangeId(); - } - - if (!isset(static::$instances[$range_id])) { - static::$instances[$range_id] = new static($range_id); - } - return static::$instances[$range_id]; - } - - /** - * set cached instance for given range_id - * use for testing or to unset cached instance by passing - * null as second param - * @param string $range_id - * @param RangeConfig $my_instance - */ - public static function set() - { - list($range_id, $my_instance) = func_get_args(); - self::$instances[$range_id] = $my_instance; - } - - /** - * passing null as first param is for compatibility and - * should be considered deprecated. - * passing data array as second param only for testing - * @param string $range_id - * @param array $data - */ - final public function __construct($range_id = null, $data = null) - { - $this->range_id = $range_id; - if ($range_id !== null || $data !== null) { - $this->fetchData($data); - } - } - - /** - * {@inheritdoc} - */ - protected function fetchData($data = null) - { - if ($data !== null) { - $this->data = $data; - } else { - $this->data = []; - foreach (Config::get()->getFields(static::RANGE_TYPE) as $field) { - $this->data[$field] = Config::get()->$field; - $this->metadata[$field] = Config::get()->getMetadata($field); - } - try { - $query = "SELECT `config_values`.`field`, `config_values`.`value` - FROM `config_values` - LEFT JOIN `config` USING (`field`) - WHERE `range_id` = :id - AND ( - `field` IN (:fields) - OR `config`.`field` IS NULL - )"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':id', $this->range_id); - $statement->bindValue(':fields', array_keys($this->data)); - $statement->execute(); - } catch (Exception $e) { - //in case we have not migrated 226 yet: - $query = 'SELECT field, value FROM user_config WHERE user_id = :id'; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':id', $this->range_id); - $statement->execute(); - } - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $this->data[$row['field']] = $this->convertFromDatabase( - $this->metadata[$row['field']]['type'] ?? 'string', - $row['value'], - $row['field'] - ); - } - } - } - - /** - * returns the range id - * - * @return string - */ - public function getRangeId() - { - return $this->range_id; - } - - /** - * @see lib/classes/Config::getValue() - */ - public function getValue($field) - { - if (array_key_exists($field, $this->data)) { - return $this->data[$field]; - } - return null; - } - - /** - * @param string $field - * @return bool - */ - public function unsetValue($field) - { - return $this->delete($field); - } - - /** - * @see lib/classes/Config::store() - */ - public function store($field, $value) - { - $entry = new ConfigValue([$field, $this->range_id]); - $this->data[$field] = $value; - - // Check if entry is default and if so, delete it - if (Config::get()->getValue($field) == $value) { - $entry->delete(); - return 1; - } - - // Otherwise convert it to an appropriate format and store it - $metadata = Config::get()->getMetadata($field); - $entry->value = $this->convertForDatabase($metadata['type'] ?? 'string', $value, $field); - - $ret = $entry->store(); - if ($ret) { - $this->fetchData(); - } - - return $ret; - } - - /** - * {@inheritdoc} - */ - public function create($field, $data = []) - { - if (!isset($data['range'])) { - $data['range'] = static::RANGE_TYPE; - } - - return parent::create($field, $data); - } - - /** - * {@inheritdoc} - */ - public function delete($field) - { - $entry = ConfigValue::find([$field, $this->range_id]); - if (!$entry) { - return null; - } - - if ($ret = $entry->delete()) { - $this->data[$field] = Config::get()->$field; - } - return $ret; - } - - /** - * {@inheritdoc} - */ - protected function getI18NIdentifier($field) - { - return md5("{$field}|{$this->range_id}"); - } -} diff --git a/lib/classes/RangeConfig.php b/lib/classes/RangeConfig.php new file mode 100644 index 0000000..b1030e3 --- /dev/null +++ b/lib/classes/RangeConfig.php @@ -0,0 +1,223 @@ + + * @author Jan-Hendrik Willms > + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class RangeConfig extends Config +{ + /** + * range type + */ + const RANGE_TYPE = 'range'; + + /** + * cache of created RangeConfig instances + * @var array + */ + protected static $instances = []; + + /** + * range_id + * @var string + */ + private $range_id; + + /** + * returns cached instance for given range_id + * creates new objects if needed + * @param string $range_id + * @return RangeConfig + */ + public static function get() + { + if (func_num_args() === 0 || func_get_arg(0) === null) { + return new static(true); + } + + $range_id = func_get_arg(0); + + if ($range_id instanceof Range) { + $range_id = $range_id->getRangeId(); + } + + if (!isset(static::$instances[$range_id])) { + static::$instances[$range_id] = new static($range_id); + } + return static::$instances[$range_id]; + } + + /** + * set cached instance for given range_id + * use for testing or to unset cached instance by passing + * null as second param + * @param string $range_id + * @param RangeConfig $my_instance + */ + public static function set() + { + list($range_id, $my_instance) = func_get_args(); + self::$instances[$range_id] = $my_instance; + } + + /** + * passing null as first param is for compatibility and + * should be considered deprecated. + * passing data array as second param only for testing + * @param string $range_id + * @param array $data + */ + final public function __construct($range_id = null, $data = null) + { + $this->range_id = $range_id; + if ($range_id !== null || $data !== null) { + $this->fetchData($data); + } + } + + /** + * {@inheritdoc} + */ + protected function fetchData($data = null) + { + if ($data !== null) { + $this->data = $data; + } else { + $this->data = []; + foreach (Config::get()->getFields(static::RANGE_TYPE) as $field) { + $this->data[$field] = Config::get()->$field; + $this->metadata[$field] = Config::get()->getMetadata($field); + } + try { + $query = "SELECT `config_values`.`field`, `config_values`.`value` + FROM `config_values` + LEFT JOIN `config` USING (`field`) + WHERE `range_id` = :id + AND ( + `field` IN (:fields) + OR `config`.`field` IS NULL + )"; + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':id', $this->range_id); + $statement->bindValue(':fields', array_keys($this->data)); + $statement->execute(); + } catch (Exception $e) { + //in case we have not migrated 226 yet: + $query = 'SELECT field, value FROM user_config WHERE user_id = :id'; + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':id', $this->range_id); + $statement->execute(); + } + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $this->data[$row['field']] = $this->convertFromDatabase( + $this->metadata[$row['field']]['type'] ?? 'string', + $row['value'], + $row['field'] + ); + } + } + } + + /** + * returns the range id + * + * @return string + */ + public function getRangeId() + { + return $this->range_id; + } + + /** + * @see lib/classes/Config::getValue() + */ + public function getValue($field) + { + if (array_key_exists($field, $this->data)) { + return $this->data[$field]; + } + return null; + } + + /** + * @param string $field + * @return bool + */ + public function unsetValue($field) + { + return $this->delete($field); + } + + /** + * @see lib/classes/Config::store() + */ + public function store($field, $value) + { + $entry = new ConfigValue([$field, $this->range_id]); + $this->data[$field] = $value; + + // Check if entry is default and if so, delete it + if (Config::get()->getValue($field) == $value) { + $entry->delete(); + return 1; + } + + // Otherwise convert it to an appropriate format and store it + $metadata = Config::get()->getMetadata($field); + $entry->value = $this->convertForDatabase($metadata['type'] ?? 'string', $value, $field); + + $ret = $entry->store(); + if ($ret) { + $this->fetchData(); + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function create($field, $data = []) + { + if (!isset($data['range'])) { + $data['range'] = static::RANGE_TYPE; + } + + return parent::create($field, $data); + } + + /** + * {@inheritdoc} + */ + public function delete($field) + { + $entry = ConfigValue::find([$field, $this->range_id]); + if (!$entry) { + return null; + } + + if ($ret = $entry->delete()) { + $this->data[$field] = Config::get()->$field; + } + return $ret; + } + + /** + * {@inheritdoc} + */ + protected function getI18NIdentifier($field) + { + return md5("{$field}|{$this->range_id}"); + } +} diff --git a/lib/classes/RangeTreeObject.class.php b/lib/classes/RangeTreeObject.class.php deleted file mode 100644 index 579db43..0000000 --- a/lib/classes/RangeTreeObject.class.php +++ /dev/null @@ -1,294 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** -* base class for items in the "range tree" -* -* This class is used for items -* -* @access public -* @author André Noack -* @package -*/ -class RangeTreeObject { - - /** - * item_id in range_tree - * - * - * @access public - * @var string $tree_item_id - */ - var $tree_item_id; - /** - * References the tree object - * - * - * @access private - * @var object StudipRangeTree $tree - */ - var $tree; - /** - * associative array with data from database fields - * - * - * @access public - * @var array $item_data - */ - var $item_data = null; - - /** - * associative array with mapping for database fields - * - * - * @access public - * @var array $item_data_mapping - */ - var $item_data_mapping = null; - - /** - * Factory method - * - * - * @access public - * @static - * @param string $item_id - * @return object RangeTreeObject - */ - public static function GetInstance($item_id){ - $tree = TreeAbstract::GetInstance("StudipRangeTree", false); - $class_name = "RangeTreeObject" . ucfirst($tree->tree_data[$item_id]['studip_object']); - return new $class_name($item_id); - } - - /** - * Constructor - * - * Do not use directly, call factory method instead - * @access private - * @param string $item_id - */ - function __construct($item_id) { - $this->tree = TreeAbstract::GetInstance("StudipRangeTree", false); - $this->tree_item_id = $item_id; - $this->item_data = $this->tree->tree_data[$item_id] ?? null; - } - - /** - * Returns all tree items which are kids of this object - * - * - * @access public - * @param boolean $as_value_list - * @return mixed returns numeric array if param is false, else comma separated string - */ - function getAllItemKids($as_value_list = false){ - return ($as_value_list) ? $this->getValueList($this->tree->getKidsKids($this->tree_item_id)) : $this->tree->getKidsKids($this->tree_item_id); - } - - /** - * Returns all tree items which are kids of this object and are "real" Stud.IP objects - * - * - * @access public - * @param boolean $as_value_list - * @return mixed returns numeric array if param is false, else comma separated string - */ - function getAllObjectKids($as_value_list = false){ - $all_object_kids = array_merge((array)$this->getInstKids(), (array)$this->getFakKids()); - return ($as_value_list) ? $this->getValueList($all_object_kids) : $all_object_kids; - } - - /** - * Returns all tree items which are kids of this object and are Stud.IP "Einrichtungen" - * - * - * @access public - * @param boolean $as_value_list - * @return mixed returns numeric array if param is false, else comma separated string - */ - function getInstKids($as_value_list = false){ - $all_kids = $this->tree->getKidsKids($this->tree_item_id); - $inst_kids = []; - for ($i = 0; $i < count($all_kids); ++$i){ - if ($this->tree->tree_data[$all_kids[$i]]['studip_object'] == 'inst'){ - $inst_kids[] = $this->tree->tree_data[$all_kids[$i]]['studip_object_id']; - } - } - return ($as_value_list) ? $this->getValueList($inst_kids) : $inst_kids; - } - - /** - * Returns all tree items which are kids of this object and are Stud.IP "Fakultaeten" - * - * - * @access public - * @param boolean $as_value_list - * @return mixed returns numeric array if param is false, else comma separated string - */ - function getFakKids($as_value_list = false){ - $all_kids = $this->tree->getKidsKids($this->tree_item_id); - $fak_kids = []; - for ($i = 0; $i < count($all_kids); ++$i){ - if ($this->tree->tree_data[$all_kids[$i]]['studip_object'] == 'fak'){ - $inst_kids[] = $this->tree->tree_data[$all_kids[$i]]['studip_object_id']; - } - } - return ($as_value_list) ? $this->getValueList($fak_kids) : $fak_kids; - } - - /** - * Returns array of Stud.IP range_ids of "real" objects - * - * This function is a wrapper for the according function in StudipRangeTree - * @see StudipRangeTree::getAdminRange() - * @access public - * @return array of primary keys from table "institute" - */ - function getAdminRange(){ - return $this->tree->getAdminRange($this->tree_item_id); - } - - /** - * Only useful in RangeTreeObjectInst ,all other items are always in the correct branch - * - * @access public - * @return bool - */ - function isInCorrectBranch(){ - return true; - } - - /** - * Returns tree path of the current object - * - * This function is a wrapper for the according function in StudipRangeTree - * @see StudipRangeTree::getItemPath() - * @access public - * @return string - */ - function getItemPath(){ - return $this->tree->getItemPath($this->tree_item_id); - } - - /** - * extends the $item_data array - * - * This function fills the $item_data array with fields from the according database table (is of no use in the base class) - * @abstract - * @access private - */ - function initItemDetail(){ - if ($type = $this->item_data['studip_object']){ - $view = DbView::getView('range_tree'); - $view->params = [$this->tree->studip_objects[$type]['table'], - $this->tree->studip_objects[$type]['pk'], - $this->item_data['studip_object_id']]; - $snap = new DbSnapshot($view->get_query("view:TREE_OBJECT_DETAIL")); - if ($snap->numRows){ - $fields = $snap->getFieldList(); - $snap->nextRow(); - for ($i = 0; $i < count($fields); ++$i){ - $this->item_data[$fields[$i]] = $snap->getField($fields[$i]); - } - } - return true; - } - return false; - } - - /** - * fetch categories of this object from database - * - * the categories are appended to the $item_data array, key 'categories', value is object of type DbSnapshot - * @access private - * @return boolean true if categories were found - */ - function fetchCategories(){ - $view = DbView::getView('range_tree'); - $view->params[] = $this->tree_item_id; - $rs = $view->get_query("view:TREE_OBJECT_CAT"); - if (is_object($rs)){ - $this->item_data['categories'] = new DbSnapshot($rs); - return true; - } - return false; - } - - /** - * getter method for categories of this object - * - * - * @access public - * @return object DbSnapshot - */ - function &getCategories(){ - if (empty($this->item_data['categories']) || !is_object($this->item_data['categories'])){ - $this->fetchCategories(); - } - return $this->item_data['categories']; - } - - function fetchNumStaff(){ - $view = DbView::getView('range_tree'); - if (!($view->params[0] = $this->item_data['studip_object_id'])) - $view->params[0] = $this->tree_item_id; - $rs = $view->get_query("view:STATUS_COUNT"); - if ($rs->next_record()){ - $this->item_data['num_staff'] = $rs->f(0); - return true; - } - return false; - } - - function getNumStaff(){ - if(!isset($this->item_data['num_staff'])){ - $this->fetchNumStaff(); - } - return $this->item_data['num_staff']; - } - - - /** - * transform numerical array into a comma separated string - * - * the result could be used in a SQL query - * @access private - * @param array $list - * @return string - */ - - function getValueList($list){ - $value_list = false; - if (count($list) == 1) - $value_list = "'$list[0]'"; - else - $value_list = "'".join("','",$list)."'"; - return $value_list; - } - -} diff --git a/lib/classes/RangeTreeObject.php b/lib/classes/RangeTreeObject.php new file mode 100644 index 0000000..579db43 --- /dev/null +++ b/lib/classes/RangeTreeObject.php @@ -0,0 +1,294 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* base class for items in the "range tree" +* +* This class is used for items +* +* @access public +* @author André Noack +* @package +*/ +class RangeTreeObject { + + /** + * item_id in range_tree + * + * + * @access public + * @var string $tree_item_id + */ + var $tree_item_id; + /** + * References the tree object + * + * + * @access private + * @var object StudipRangeTree $tree + */ + var $tree; + /** + * associative array with data from database fields + * + * + * @access public + * @var array $item_data + */ + var $item_data = null; + + /** + * associative array with mapping for database fields + * + * + * @access public + * @var array $item_data_mapping + */ + var $item_data_mapping = null; + + /** + * Factory method + * + * + * @access public + * @static + * @param string $item_id + * @return object RangeTreeObject + */ + public static function GetInstance($item_id){ + $tree = TreeAbstract::GetInstance("StudipRangeTree", false); + $class_name = "RangeTreeObject" . ucfirst($tree->tree_data[$item_id]['studip_object']); + return new $class_name($item_id); + } + + /** + * Constructor + * + * Do not use directly, call factory method instead + * @access private + * @param string $item_id + */ + function __construct($item_id) { + $this->tree = TreeAbstract::GetInstance("StudipRangeTree", false); + $this->tree_item_id = $item_id; + $this->item_data = $this->tree->tree_data[$item_id] ?? null; + } + + /** + * Returns all tree items which are kids of this object + * + * + * @access public + * @param boolean $as_value_list + * @return mixed returns numeric array if param is false, else comma separated string + */ + function getAllItemKids($as_value_list = false){ + return ($as_value_list) ? $this->getValueList($this->tree->getKidsKids($this->tree_item_id)) : $this->tree->getKidsKids($this->tree_item_id); + } + + /** + * Returns all tree items which are kids of this object and are "real" Stud.IP objects + * + * + * @access public + * @param boolean $as_value_list + * @return mixed returns numeric array if param is false, else comma separated string + */ + function getAllObjectKids($as_value_list = false){ + $all_object_kids = array_merge((array)$this->getInstKids(), (array)$this->getFakKids()); + return ($as_value_list) ? $this->getValueList($all_object_kids) : $all_object_kids; + } + + /** + * Returns all tree items which are kids of this object and are Stud.IP "Einrichtungen" + * + * + * @access public + * @param boolean $as_value_list + * @return mixed returns numeric array if param is false, else comma separated string + */ + function getInstKids($as_value_list = false){ + $all_kids = $this->tree->getKidsKids($this->tree_item_id); + $inst_kids = []; + for ($i = 0; $i < count($all_kids); ++$i){ + if ($this->tree->tree_data[$all_kids[$i]]['studip_object'] == 'inst'){ + $inst_kids[] = $this->tree->tree_data[$all_kids[$i]]['studip_object_id']; + } + } + return ($as_value_list) ? $this->getValueList($inst_kids) : $inst_kids; + } + + /** + * Returns all tree items which are kids of this object and are Stud.IP "Fakultaeten" + * + * + * @access public + * @param boolean $as_value_list + * @return mixed returns numeric array if param is false, else comma separated string + */ + function getFakKids($as_value_list = false){ + $all_kids = $this->tree->getKidsKids($this->tree_item_id); + $fak_kids = []; + for ($i = 0; $i < count($all_kids); ++$i){ + if ($this->tree->tree_data[$all_kids[$i]]['studip_object'] == 'fak'){ + $inst_kids[] = $this->tree->tree_data[$all_kids[$i]]['studip_object_id']; + } + } + return ($as_value_list) ? $this->getValueList($fak_kids) : $fak_kids; + } + + /** + * Returns array of Stud.IP range_ids of "real" objects + * + * This function is a wrapper for the according function in StudipRangeTree + * @see StudipRangeTree::getAdminRange() + * @access public + * @return array of primary keys from table "institute" + */ + function getAdminRange(){ + return $this->tree->getAdminRange($this->tree_item_id); + } + + /** + * Only useful in RangeTreeObjectInst ,all other items are always in the correct branch + * + * @access public + * @return bool + */ + function isInCorrectBranch(){ + return true; + } + + /** + * Returns tree path of the current object + * + * This function is a wrapper for the according function in StudipRangeTree + * @see StudipRangeTree::getItemPath() + * @access public + * @return string + */ + function getItemPath(){ + return $this->tree->getItemPath($this->tree_item_id); + } + + /** + * extends the $item_data array + * + * This function fills the $item_data array with fields from the according database table (is of no use in the base class) + * @abstract + * @access private + */ + function initItemDetail(){ + if ($type = $this->item_data['studip_object']){ + $view = DbView::getView('range_tree'); + $view->params = [$this->tree->studip_objects[$type]['table'], + $this->tree->studip_objects[$type]['pk'], + $this->item_data['studip_object_id']]; + $snap = new DbSnapshot($view->get_query("view:TREE_OBJECT_DETAIL")); + if ($snap->numRows){ + $fields = $snap->getFieldList(); + $snap->nextRow(); + for ($i = 0; $i < count($fields); ++$i){ + $this->item_data[$fields[$i]] = $snap->getField($fields[$i]); + } + } + return true; + } + return false; + } + + /** + * fetch categories of this object from database + * + * the categories are appended to the $item_data array, key 'categories', value is object of type DbSnapshot + * @access private + * @return boolean true if categories were found + */ + function fetchCategories(){ + $view = DbView::getView('range_tree'); + $view->params[] = $this->tree_item_id; + $rs = $view->get_query("view:TREE_OBJECT_CAT"); + if (is_object($rs)){ + $this->item_data['categories'] = new DbSnapshot($rs); + return true; + } + return false; + } + + /** + * getter method for categories of this object + * + * + * @access public + * @return object DbSnapshot + */ + function &getCategories(){ + if (empty($this->item_data['categories']) || !is_object($this->item_data['categories'])){ + $this->fetchCategories(); + } + return $this->item_data['categories']; + } + + function fetchNumStaff(){ + $view = DbView::getView('range_tree'); + if (!($view->params[0] = $this->item_data['studip_object_id'])) + $view->params[0] = $this->tree_item_id; + $rs = $view->get_query("view:STATUS_COUNT"); + if ($rs->next_record()){ + $this->item_data['num_staff'] = $rs->f(0); + return true; + } + return false; + } + + function getNumStaff(){ + if(!isset($this->item_data['num_staff'])){ + $this->fetchNumStaff(); + } + return $this->item_data['num_staff']; + } + + + /** + * transform numerical array into a comma separated string + * + * the result could be used in a SQL query + * @access private + * @param array $list + * @return string + */ + + function getValueList($list){ + $value_list = false; + if (count($list) == 1) + $value_list = "'$list[0]'"; + else + $value_list = "'".join("','",$list)."'"; + return $value_list; + } + +} diff --git a/lib/classes/RangeTreeObjectFak.class.php b/lib/classes/RangeTreeObjectFak.class.php deleted file mode 100644 index 76cc366..0000000 --- a/lib/classes/RangeTreeObjectFak.class.php +++ /dev/null @@ -1,56 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** -* class for items in the "range tree" -* -* This class is used for items in the tree which are "Fakultäten" -* -* @access public -* @author André Noack -* @package -*/ -class RangeTreeObjectFak extends RangeTreeObject { - - /** - * Constructor - * - * Do not use directly, call factory method in base class instead - * @access private - * @param string $item_id - */ - function __construct($item_id) { - parent::__construct($item_id); //calling the baseclass constructor - $this->initItemDetail(); - $this->item_data_mapping = ['Strasse' => _("Straße"), 'Plz' => _("Ort"), 'telefon' => _("Tel."), 'fax' => _("Fax"), - 'url' => _("Homepage"), 'email' => _("Kontakt")]; - $this->item_data['type_num'] = $this->item_data['type']; - $this->item_data['type'] = ($this->item_data['type']) ? $GLOBALS['INST_TYPE'][$this->item_data['type']]['name'] : $GLOBALS['INST_TYPE'][1]['name']; - - } -} -?> diff --git a/lib/classes/RangeTreeObjectFak.php b/lib/classes/RangeTreeObjectFak.php new file mode 100644 index 0000000..76cc366 --- /dev/null +++ b/lib/classes/RangeTreeObjectFak.php @@ -0,0 +1,56 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* class for items in the "range tree" +* +* This class is used for items in the tree which are "Fakultäten" +* +* @access public +* @author André Noack +* @package +*/ +class RangeTreeObjectFak extends RangeTreeObject { + + /** + * Constructor + * + * Do not use directly, call factory method in base class instead + * @access private + * @param string $item_id + */ + function __construct($item_id) { + parent::__construct($item_id); //calling the baseclass constructor + $this->initItemDetail(); + $this->item_data_mapping = ['Strasse' => _("Straße"), 'Plz' => _("Ort"), 'telefon' => _("Tel."), 'fax' => _("Fax"), + 'url' => _("Homepage"), 'email' => _("Kontakt")]; + $this->item_data['type_num'] = $this->item_data['type']; + $this->item_data['type'] = ($this->item_data['type']) ? $GLOBALS['INST_TYPE'][$this->item_data['type']]['name'] : $GLOBALS['INST_TYPE'][1]['name']; + + } +} +?> diff --git a/lib/classes/RangeTreeObjectInst.class.php b/lib/classes/RangeTreeObjectInst.class.php deleted file mode 100644 index c291dfc..0000000 --- a/lib/classes/RangeTreeObjectInst.class.php +++ /dev/null @@ -1,71 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ -/** -* class for items in the "range tree" -* -* This class is used for items which are "Einrichtungen" -* -* @access public -* @author André Noack -* @package -*/ -class RangeTreeObjectInst extends RangeTreeObject { - - /** - * Constructor - * - * Do not use directly, call factory method in base class instead - * @access private - * @param string $item_id - */ - function __construct($item_id) { - parent::__construct($item_id); //calling the baseclass constructor - $this->initItemDetail(); - $this->item_data_mapping = ['Strasse' => _("Straße"), 'Plz' => _("Ort"), 'telefon' => _("Tel."), 'fax' => _("Fax"), - 'url' => _("Homepage"), 'email' => _("Kontakt")]; - $this->item_data['type_num'] = $this->item_data['type']; - $this->item_data['type'] = ($this->item_data['type']) ? $GLOBALS['INST_TYPE'][$this->item_data['type']]['name'] : $GLOBALS['INST_TYPE'][1]['name']; - - } - - /** - * Returns true, if fakultaets_id of the item is found in its parents - * - * @access public - * @return bool - */ - function isInCorrectBranch(){ - $parents = $this->tree->getParents($this->tree_item_id); - if (is_array($parents) && in_array($this->item_data['fakultaets_id'],$parents)){ - return true; - } else { - return false; - } - } - -} -?> diff --git a/lib/classes/RangeTreeObjectInst.php b/lib/classes/RangeTreeObjectInst.php new file mode 100644 index 0000000..c291dfc --- /dev/null +++ b/lib/classes/RangeTreeObjectInst.php @@ -0,0 +1,71 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ +/** +* class for items in the "range tree" +* +* This class is used for items which are "Einrichtungen" +* +* @access public +* @author André Noack +* @package +*/ +class RangeTreeObjectInst extends RangeTreeObject { + + /** + * Constructor + * + * Do not use directly, call factory method in base class instead + * @access private + * @param string $item_id + */ + function __construct($item_id) { + parent::__construct($item_id); //calling the baseclass constructor + $this->initItemDetail(); + $this->item_data_mapping = ['Strasse' => _("Straße"), 'Plz' => _("Ort"), 'telefon' => _("Tel."), 'fax' => _("Fax"), + 'url' => _("Homepage"), 'email' => _("Kontakt")]; + $this->item_data['type_num'] = $this->item_data['type']; + $this->item_data['type'] = ($this->item_data['type']) ? $GLOBALS['INST_TYPE'][$this->item_data['type']]['name'] : $GLOBALS['INST_TYPE'][1]['name']; + + } + + /** + * Returns true, if fakultaets_id of the item is found in its parents + * + * @access public + * @return bool + */ + function isInCorrectBranch(){ + $parents = $this->tree->getParents($this->tree_item_id); + if (is_array($parents) && in_array($this->item_data['fakultaets_id'],$parents)){ + return true; + } else { + return false; + } + } + +} +?> diff --git a/lib/classes/Request.class.php b/lib/classes/Request.class.php deleted file mode 100644 index d72a6a9..0000000 --- a/lib/classes/Request.class.php +++ /dev/null @@ -1,828 +0,0 @@ -params = array_merge($_GET, $_POST); - } - - /** - * Return the Request singleton instance. - */ - public static function getInstance() - { - static $instance; - - if (isset($instance)) { - return $instance; - } - - return $instance = new Request(); - } - - /** - * ArrayAccess: Check whether the given offset exists. - */ - public function offsetExists($offset): bool - { - return isset($this->params[$offset]); - } - - /** - * ArrayAccess: Get the value at the given offset. - */ - public function offsetGet($offset): mixed - { - return $this->params[$offset] ?? null; - } - - /** - * ArrayAccess: Set the value at the given offset. - */ - public function offsetSet($offset, $value): void - { - $this->params[$offset] = $value; - } - - /** - * ArrayAccess: Delete the value at the given offset. - */ - public function offsetUnset($offset): void - { - unset($this->params[$offset]); - } - - /** - * IteratorAggregate: Create iterator for request parameters. - */ - public function getIterator(): Traversable - { - return new ArrayIterator((array)$this->params); - } - - /** - * Return the current URL, including query parameters. - */ - public static function url() - { - return self::protocol() . '://' . self::server() . self::path(); - } - - /** - * Return the current protocol ('http' or 'https'). - */ - public static function protocol() - { - - // If a reverse proxy tells us the required protocol we should respect that - if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { - return $_SERVER['HTTP_X_FORWARDED_PROTO']; - } - - return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') - ? 'https' - : 'http'; - } - - /** - * Return the current server name and port (host:port). - */ - public static function server() - { - $host = $_SERVER['SERVER_NAME']; - $port = $_SERVER['SERVER_PORT']; - $ssl = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'; - - if ($ssl && $port != 443 || !$ssl && $port != 80) { - $host .= ':' . $port; - } - - return $host; - } - - /** - * Return the current request path, relative to the server root. - */ - public static function path() - { - return $_SERVER['REQUEST_URI']; - } - - /** - * Return the filename of the currently executing script. - * @return string - */ - public static function scriptName(): string - { - return $_SERVER['SCRIPT_NAME']; - } - - /** - * Returns the complete path info including duplicated slashes. - * $_SERVER['PATH_INFO'] will remove them. - */ - public static function pathInfo(): string - { - $script_name = self::scriptName(); - $script_name = preg_quote($script_name, '#'); - $script_name = str_replace('/', '/+', $script_name); - - $path_info = preg_replace( - "#^{$script_name}#", - '', - urldecode(self::path()) - ); - return parse_url($path_info, PHP_URL_PATH) ?? ''; - } - - /** - * Set the selected query parameter to a specific value. - * - * @param string $param parameter name - * @param mixed $value parameter value (string or array) - */ - public static function set($param, $value) - { - $request = self::getInstance(); - - $request->params[$param] = $value; - } - - /** - * Return the value of the selected query parameter as a string. - * - * @param string $param parameter name - * @param string $default default value if parameter is not set - * - * @return string parameter value as string (if set), else NULL - */ - public static function get($param, $default = NULL) - { - $request = self::getInstance(); - - return (isset($request[$param]) && is_string($request[$param])) - ? $request[$param] - : $default; - } - - /** - * Return the value of the selected query parameter as an I18NString. - * - * @param string $param parameter name - * @param string $default default value if parameter is not set - * @param Callable $op Operation to perform on each text string - * - * @return I18NString parameter value as string (if set), else NULL - */ - public static function i18n($param, $default = NULL, Callable $op = null) - { - $value = self::get($param, $default); - - if (isset($value)) { - $lang = self::getArray($param . '_i18n'); - - if ($op) { - $value = $op($value); - $lang = array_map($op, $lang); - } - - $value = new I18NString($value, $lang); - } - - return $value; - } - - /** - * Return the value of the selected query parameter as a string. - * The contents of the string is quoted with addslashes(). - * - * @param string $param parameter name - * @param string $default default value if parameter is not set - * - * @return string parameter value as string (if set), else NULL - */ - public static function quoted($param, $default = NULL) - { - $value = self::get($param, $default); - - if (isset($value)) { - $value = addslashes($value); - } - - return $value; - } - - /** - * Return the value of the selected query parameter as an alphanumeric - * string (consisting of only digits, letters and underscores). - * - * @param string $param parameter name - * @param string $default default value if parameter is not set - * - * @return string parameter value as string (if set), else NULL - */ - public static function option($param, $default = NULL) - { - $value = self::get($param, $default); - - if (!isset($value) || preg_match('/\\W/', $value)) { - $value = $default; - } - - return $value; - } - - /** - * Return the value of the selected query parameter as an integer. - * - * @param string $param parameter name - * @param int $default default value if parameter is not set - * - * @return int parameter value as integer (if set), else NULL - */ - public static function int($param, $default = NULL) - { - $value = self::get($param, $default); - - if (isset($value)) { - $value = (int) $value; - } - - return $value; - } - - /** - * Return the value of the selected query parameter as a float. - * - * @param string $param parameter name - * @param float $default default value if parameter is not set - * - * @return float parameter value as float (if set), else NULL - */ - public static function float($param, $default = NULL) - { - $value = self::get($param, $default); - - if (isset($value)) { - $value = (float) strtr($value, ',', '.'); - } - - return $value; - } - - - /** - * Returns the date and time values from one or two fields - * as a DateTime object. The $second_param and $second_format - * parameters are handy in case the date and time values - * come from different fields. - * - * @param string $param The name of the date/time field. - * @param string $format The date format of the date/time field. - * @param string $second_param The name of the second field, if used. - * This parameter is optional. - * @param string $second_format The time format of the second field, if used. - * This parameter is optional. - * @param DateTime|null $default Either a default DateTime object - * or null if no default shall be set. In the latter case a DateTime - * object representing the unix timestamp 0 is returned. - * - * @returns DateTime|bool A DateTime object containing the - * date and time values of the specified date and time field. - * In case something went wrong the boolean value false is returned. - * - * @see the following PHP documentation page for a list of - * accepted date and time formats: - * https://secure.php.net/manual/en/datetime.createfromformat.php - */ - public static function getDateTime( - $param = 'date', - $format = 'Y-m-d', - $second_param = null, - $second_format = null, - $default = null - ) - { - $value = self::get($param); - - if (!$value) { - //In case the first field is not set - //use the default value, if any: - if ($default instanceof DateTime) { - return $default; - } else { - $datetime = new DateTime(); - $datetime->setTimestamp(0); - return $datetime; - } - } - - //Combine the format specifications and the - //values into one string each. - $combined_format = $format; - $combined_value = $value; - - //The second format and value is only added - //when $second_param and $second_format are set - //and a second value could be retrieved. - if ($second_param && $second_format) { - $second_value = Request::get($second_param); - if ($second_value) { - $combined_format .= ' ' . $second_format; - $combined_value .= ' ' . $second_value; - } - } - - //The time zone may not be set in the fields - //so we use the default timezone from a new - //DateTime object: - $value = new DateTime(); - $time_zone = $value->getTimezone(); - - //Now we return a DateTime object created from the - //specified date value(s): - $result = DateTime::createFromFormat( - $combined_format, - $combined_value, - $time_zone - ); - - if ( - $result - && !$second_param - && !$second_format - && !preg_match('/[aAghGHisvu]/', $format) // Ensure no time in format - ) { - $result->setTime(0, 0); - } - - return $result; - } - - - /** - * Retrieves a parameter that stores time data and converts the time data - * to a DateTime object. If a date is specified using an existing DateTime - * object, the time will be set on the existing DateTime object and the - * modified object is returned. - * - * @param string $param The name of the time field. - * - * @param string $format The time format of the time field. - * - * @param DateTime|null $date An optional DateTime object whose time - * shall be set from the specified parameter. - * - * @returns DateTime|bool A DateTime object containing the - * time value of the specified date and time field. - * In case something went wrong the boolean value false is returned. - * - */ - public static function getTime( - $param = 'time', - $format = 'H:i', - $date = null - ) - { - $value = Request::get($param); - - //Get the timezone before parsing the time - //so that the resulting DateTime object - //will have the current timezone set. - $tz_get = new DateTime(); - $time_zone = $tz_get->getTimezone(); - $converted_value = DateTime::createFromFormat( - $format, - $value, - $time_zone - ); - - if ($date instanceof DateTime) { - //Modify the time information of the specified - //DateTime object and return the modified object. - $date->setTime( - intval($converted_value->format('H')), - intval($converted_value->format('i')), - intval($converted_value->format('s')) - ); - - return $date; - } - - //Return the time value. - return $converted_value; - } - - - /** - * Return the value of the selected query parameter as a string - * consisting only of allowed characters for usernames. - * - * @param string $param parameter name - * @param string $default default value if parameter is not set - * - * @return string parameter value (if set), else NULL - */ - public static function username($param, $default = NULL) - { - $value = self::get($param, $default); - - if (!isset($value) || !preg_match(Config::get()->USERNAME_REGULAR_EXPRESSION, $value)) { - $value = $default; - } - - return $value; - } - - /** - * Return the value of the selected query parameter as an array. - * - * @param string $param parameter name - * - * @return array parameter value as array (if set), else an empty array - */ - public static function getArray($param) - { - $request = self::getInstance(); - - return (isset($request[$param]) && is_array($request[$param])) - ? $request[$param] - : []; - } - - /** - * Return the value of the selected query parameter as a string array. - * The contents of each element is quoted with addslashes(). - * - * @param string $param parameter name - * - * @return array parameter value as array (if set), else an empty array - */ - public static function quotedArray($param) - { - $array = self::getArray($param); - - return self::addslashes($array); - } - - /** - * Return the value of the selected query parameter as an array of - * alphanumeric strings (consisting of only digits, letters and - * underscores). - * - * @param string $param parameter name - * - * @return array parameter value as array (if set), else an empty array - */ - public static function optionArray($param) - { - $array = self::getArray($param); - - foreach ($array as $key => $value) { - if (preg_match('/\\W/', $value)) { - unset($array[$key]); - } - } - - return $array; - } - - /** - * Return the value of the selected query parameter as an integer array. - * - * @param string $param parameter name - * - * @return array parameter value as array (if set), else an empty array - */ - public static function intArray($param) - { - $array = self::getArray($param); - - foreach ($array as $key => $value) { - $array[$key] = (int) $value; - } - - return $array; - } - - /** - * Return the value of the selected query parameter as a float array. - * - * @param string $param parameter name - * - * @return array parameter value as array (if set), else an empty array - */ - public static function floatArray($param) - { - $array = self::getArray($param); - - foreach ($array as $key => $value) { - $array[$key] = (float) strtr($value, ',', '.'); - } - - return $array; - } - - /** - * Return the value of the selected query parameter as a boolean. - * - * @param string $param parameter name - * @param bool $default default value if parameter is not set - * - * @return bool parameter value as bool (if set), else NULL - * - * @since Stud.IP 4.4 - */ - public static function bool($param, $default = null) - { - $value = self::get($param, $default); - - if (isset($value)) { - $value = (bool) $value; - } - - return $value; - } - - /** - * Return the value of the selected query parameter as a boolean array. - * - * @param string $param parameter name - * - * @return array parameter value as array (if set), else an empty array - * - * @since Stud.IP 4.4 - */ - public static function boolArray($param) - { - $array = self::getArray($param); - - foreach ($array as $key => $value) { - $array[$key] = (bool) $value; - } - - return $array; - } - - /** - * Return the value of the selected query parameter as an array of - * strings consisting only of allowed characters for usernames. - * - * @param string $param parameter name - * - * @return array parameter value as array (if set), else an empty array - */ - public static function usernameArray($param) - { - $array = self::getArray($param); - - foreach ($array as $key => $value) { - if (!preg_match(Config::get()->USERNAME_REGULAR_EXPRESSION, $value)) { - unset($array[$key]); - } - } - - return $array; - } - /** - * Check whether a form submit button has been pressed. This works for - * both image and text submit buttons. - * - * @param string $param submit button name - * - * @returns boolean true if the button has been submitted, else false - */ - public static function submitted($param) - { - $request = self::getInstance(); - - return isset($request[$param]) - || isset($request[$param . '_x']); - } - - /** - * Check whether one of the form submit buttons has been - * pressed. This works for both image and text submit buttons. - * - * @param string ... - * a variable argument list of submit button names - * - * @returns boolean true if any button has been submitted, else false - */ - public static function submittedSome($param/*, ... */) - { - foreach(func_get_args() as $button) { - if (self::submitted($button)) { - return TRUE; - } - } - return FALSE; - } - - /** - * Quote a given string or array using addslashes(). If the parameter - * is an array, the quoting is applied recursively. - * - * @param mixed $value string or array value to be quoted - * - * @return mixed quoted string or array - */ - public static function addslashes($value) - { - if (is_array($value)) { - foreach ($value as $key => $val) { - $value[$key] = self::addslashes($val); - } - } else { - $value = addslashes($value); - } - - return $value; - } - - /** - * Returns the (uppercase) request method. - * - * @return string the uppercased method of the request - */ - public static function method() - { - return mb_strtoupper($_SERVER['X_HTTP_METHOD_OVERRIDE'] ?? $_SERVER['REQUEST_METHOD']); - } - - /** - * @return boolean true if this a GET request - */ - public static function isGet() - { - return self::method() === 'GET'; - } - - /** - * @return boolean true if this a POST request - */ - public static function isPost() - { - return self::method() === 'POST'; - } - - /** - * @return boolean true if this a PUT request - */ - public static function isPut() - { - return self::method() === 'PUT'; - } - - /** - * @return boolean true if this a DELETE request - */ - public static function isDelete() - { - return self::method() === 'DELETE'; - } - - - /** - * @return boolean true if this an XmlHttpRequest sent by jQuery/prototype - */ - public static function isXhr() - { - return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && - strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], 'xmlhttprequest') === 0; - } - - /** - * This is an alias of Request::isXhr - * - * @return boolean true if this an XmlHttpRequest sent by jQuery/prototype - */ - public static function isAjax() - { - return self::isXhr(); - } - - /** - * extracts some params from request, the desired params must be a comma separated list - * for each param, the type of used extraction method can be specified after the name, - * default is get - * null values are not returned - * - * e.g.: - * $data = Request::extract('admission_prelim int, admission_binding submitted, admission_prelim_txt'); - * will yield - * array(3) { - * ["admission_prelim"]=> - * int(1) - * ["admission_binding"]=> - * bool(false) - * ["admission_prelim_txt"]=> - * string(0) "" - * } - * @param string $what comma separated list of param names and types - * @return array assoc array with extracted data - */ - public static function extract($what) - { - $extract = []; - $return = []; - foreach (explode(',', $what) as $one) { - $extract[] = array_values(array_filter(array_map('trim', explode(' ', $one)))); - } - foreach ($extract as $one) { - [$param, $func] = $one; - if (!$func) { - $func = 'get'; - } - $value = self::$func($param); - if ($value !== null) { - $return[$param] = $value; - } - } - return $return; - } - - /** - * returns true if http header indicates that the response will be rendered as dialog - * - * @return bool - */ - public static function isDialog() - { - return self::isXhr() && isset($_SERVER['HTTP_X_DIALOG']); - } - - /** - * Returns an object that has previously been serialized using the - * ObjectBuilder. - * - * @param String $param parameter name - * @param mixed $expected_class Expected class name of object (optional) - * @param bool $allow_null If true, return null on error; otherwise an - * exception is thrown - * @return mixed Object of arbitrary type or null on error and $allow_null - * @throws Exception when an error occurs and $allow_null = false - * @see ObjectBuilder - */ - public static function getObject($param, $expected_class = null, $allow_null = true) - { - try { - return ObjectBuilder::build(Request::get($param), $expected_class); - } catch (Exception $e) { - if ($allow_null) { - return null; - } - - throw $e; - } - } - - /** - * Returns a collection of objects that have previously been serialized - * using the ObjectBuilder. - * - * @param String $param parameter name - * @param mixed $expected_class Expected class name of objects (optional) - * @param bool $allow_null If true, return empty array on error; - * otherwise an exception is thrown - * @return array as collection of objects - * @throws Exception when an error occurs and $allow_null = false - * @see ObjectBuilder - */ - public static function getManyObjects($param, $expected_class = null, $allow_null = true) - { - try { - $request = self::getInstance(); - return ObjectBuilder::buildMany($request[$param] ?? null, $expected_class); - } catch (Exception $e) { - if ($allow_null) { - return []; - } - - throw $e; - } - } -} diff --git a/lib/classes/Request.php b/lib/classes/Request.php new file mode 100644 index 0000000..d72a6a9 --- /dev/null +++ b/lib/classes/Request.php @@ -0,0 +1,828 @@ +params = array_merge($_GET, $_POST); + } + + /** + * Return the Request singleton instance. + */ + public static function getInstance() + { + static $instance; + + if (isset($instance)) { + return $instance; + } + + return $instance = new Request(); + } + + /** + * ArrayAccess: Check whether the given offset exists. + */ + public function offsetExists($offset): bool + { + return isset($this->params[$offset]); + } + + /** + * ArrayAccess: Get the value at the given offset. + */ + public function offsetGet($offset): mixed + { + return $this->params[$offset] ?? null; + } + + /** + * ArrayAccess: Set the value at the given offset. + */ + public function offsetSet($offset, $value): void + { + $this->params[$offset] = $value; + } + + /** + * ArrayAccess: Delete the value at the given offset. + */ + public function offsetUnset($offset): void + { + unset($this->params[$offset]); + } + + /** + * IteratorAggregate: Create iterator for request parameters. + */ + public function getIterator(): Traversable + { + return new ArrayIterator((array)$this->params); + } + + /** + * Return the current URL, including query parameters. + */ + public static function url() + { + return self::protocol() . '://' . self::server() . self::path(); + } + + /** + * Return the current protocol ('http' or 'https'). + */ + public static function protocol() + { + + // If a reverse proxy tells us the required protocol we should respect that + if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + return $_SERVER['HTTP_X_FORWARDED_PROTO']; + } + + return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') + ? 'https' + : 'http'; + } + + /** + * Return the current server name and port (host:port). + */ + public static function server() + { + $host = $_SERVER['SERVER_NAME']; + $port = $_SERVER['SERVER_PORT']; + $ssl = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'; + + if ($ssl && $port != 443 || !$ssl && $port != 80) { + $host .= ':' . $port; + } + + return $host; + } + + /** + * Return the current request path, relative to the server root. + */ + public static function path() + { + return $_SERVER['REQUEST_URI']; + } + + /** + * Return the filename of the currently executing script. + * @return string + */ + public static function scriptName(): string + { + return $_SERVER['SCRIPT_NAME']; + } + + /** + * Returns the complete path info including duplicated slashes. + * $_SERVER['PATH_INFO'] will remove them. + */ + public static function pathInfo(): string + { + $script_name = self::scriptName(); + $script_name = preg_quote($script_name, '#'); + $script_name = str_replace('/', '/+', $script_name); + + $path_info = preg_replace( + "#^{$script_name}#", + '', + urldecode(self::path()) + ); + return parse_url($path_info, PHP_URL_PATH) ?? ''; + } + + /** + * Set the selected query parameter to a specific value. + * + * @param string $param parameter name + * @param mixed $value parameter value (string or array) + */ + public static function set($param, $value) + { + $request = self::getInstance(); + + $request->params[$param] = $value; + } + + /** + * Return the value of the selected query parameter as a string. + * + * @param string $param parameter name + * @param string $default default value if parameter is not set + * + * @return string parameter value as string (if set), else NULL + */ + public static function get($param, $default = NULL) + { + $request = self::getInstance(); + + return (isset($request[$param]) && is_string($request[$param])) + ? $request[$param] + : $default; + } + + /** + * Return the value of the selected query parameter as an I18NString. + * + * @param string $param parameter name + * @param string $default default value if parameter is not set + * @param Callable $op Operation to perform on each text string + * + * @return I18NString parameter value as string (if set), else NULL + */ + public static function i18n($param, $default = NULL, Callable $op = null) + { + $value = self::get($param, $default); + + if (isset($value)) { + $lang = self::getArray($param . '_i18n'); + + if ($op) { + $value = $op($value); + $lang = array_map($op, $lang); + } + + $value = new I18NString($value, $lang); + } + + return $value; + } + + /** + * Return the value of the selected query parameter as a string. + * The contents of the string is quoted with addslashes(). + * + * @param string $param parameter name + * @param string $default default value if parameter is not set + * + * @return string parameter value as string (if set), else NULL + */ + public static function quoted($param, $default = NULL) + { + $value = self::get($param, $default); + + if (isset($value)) { + $value = addslashes($value); + } + + return $value; + } + + /** + * Return the value of the selected query parameter as an alphanumeric + * string (consisting of only digits, letters and underscores). + * + * @param string $param parameter name + * @param string $default default value if parameter is not set + * + * @return string parameter value as string (if set), else NULL + */ + public static function option($param, $default = NULL) + { + $value = self::get($param, $default); + + if (!isset($value) || preg_match('/\\W/', $value)) { + $value = $default; + } + + return $value; + } + + /** + * Return the value of the selected query parameter as an integer. + * + * @param string $param parameter name + * @param int $default default value if parameter is not set + * + * @return int parameter value as integer (if set), else NULL + */ + public static function int($param, $default = NULL) + { + $value = self::get($param, $default); + + if (isset($value)) { + $value = (int) $value; + } + + return $value; + } + + /** + * Return the value of the selected query parameter as a float. + * + * @param string $param parameter name + * @param float $default default value if parameter is not set + * + * @return float parameter value as float (if set), else NULL + */ + public static function float($param, $default = NULL) + { + $value = self::get($param, $default); + + if (isset($value)) { + $value = (float) strtr($value, ',', '.'); + } + + return $value; + } + + + /** + * Returns the date and time values from one or two fields + * as a DateTime object. The $second_param and $second_format + * parameters are handy in case the date and time values + * come from different fields. + * + * @param string $param The name of the date/time field. + * @param string $format The date format of the date/time field. + * @param string $second_param The name of the second field, if used. + * This parameter is optional. + * @param string $second_format The time format of the second field, if used. + * This parameter is optional. + * @param DateTime|null $default Either a default DateTime object + * or null if no default shall be set. In the latter case a DateTime + * object representing the unix timestamp 0 is returned. + * + * @returns DateTime|bool A DateTime object containing the + * date and time values of the specified date and time field. + * In case something went wrong the boolean value false is returned. + * + * @see the following PHP documentation page for a list of + * accepted date and time formats: + * https://secure.php.net/manual/en/datetime.createfromformat.php + */ + public static function getDateTime( + $param = 'date', + $format = 'Y-m-d', + $second_param = null, + $second_format = null, + $default = null + ) + { + $value = self::get($param); + + if (!$value) { + //In case the first field is not set + //use the default value, if any: + if ($default instanceof DateTime) { + return $default; + } else { + $datetime = new DateTime(); + $datetime->setTimestamp(0); + return $datetime; + } + } + + //Combine the format specifications and the + //values into one string each. + $combined_format = $format; + $combined_value = $value; + + //The second format and value is only added + //when $second_param and $second_format are set + //and a second value could be retrieved. + if ($second_param && $second_format) { + $second_value = Request::get($second_param); + if ($second_value) { + $combined_format .= ' ' . $second_format; + $combined_value .= ' ' . $second_value; + } + } + + //The time zone may not be set in the fields + //so we use the default timezone from a new + //DateTime object: + $value = new DateTime(); + $time_zone = $value->getTimezone(); + + //Now we return a DateTime object created from the + //specified date value(s): + $result = DateTime::createFromFormat( + $combined_format, + $combined_value, + $time_zone + ); + + if ( + $result + && !$second_param + && !$second_format + && !preg_match('/[aAghGHisvu]/', $format) // Ensure no time in format + ) { + $result->setTime(0, 0); + } + + return $result; + } + + + /** + * Retrieves a parameter that stores time data and converts the time data + * to a DateTime object. If a date is specified using an existing DateTime + * object, the time will be set on the existing DateTime object and the + * modified object is returned. + * + * @param string $param The name of the time field. + * + * @param string $format The time format of the time field. + * + * @param DateTime|null $date An optional DateTime object whose time + * shall be set from the specified parameter. + * + * @returns DateTime|bool A DateTime object containing the + * time value of the specified date and time field. + * In case something went wrong the boolean value false is returned. + * + */ + public static function getTime( + $param = 'time', + $format = 'H:i', + $date = null + ) + { + $value = Request::get($param); + + //Get the timezone before parsing the time + //so that the resulting DateTime object + //will have the current timezone set. + $tz_get = new DateTime(); + $time_zone = $tz_get->getTimezone(); + $converted_value = DateTime::createFromFormat( + $format, + $value, + $time_zone + ); + + if ($date instanceof DateTime) { + //Modify the time information of the specified + //DateTime object and return the modified object. + $date->setTime( + intval($converted_value->format('H')), + intval($converted_value->format('i')), + intval($converted_value->format('s')) + ); + + return $date; + } + + //Return the time value. + return $converted_value; + } + + + /** + * Return the value of the selected query parameter as a string + * consisting only of allowed characters for usernames. + * + * @param string $param parameter name + * @param string $default default value if parameter is not set + * + * @return string parameter value (if set), else NULL + */ + public static function username($param, $default = NULL) + { + $value = self::get($param, $default); + + if (!isset($value) || !preg_match(Config::get()->USERNAME_REGULAR_EXPRESSION, $value)) { + $value = $default; + } + + return $value; + } + + /** + * Return the value of the selected query parameter as an array. + * + * @param string $param parameter name + * + * @return array parameter value as array (if set), else an empty array + */ + public static function getArray($param) + { + $request = self::getInstance(); + + return (isset($request[$param]) && is_array($request[$param])) + ? $request[$param] + : []; + } + + /** + * Return the value of the selected query parameter as a string array. + * The contents of each element is quoted with addslashes(). + * + * @param string $param parameter name + * + * @return array parameter value as array (if set), else an empty array + */ + public static function quotedArray($param) + { + $array = self::getArray($param); + + return self::addslashes($array); + } + + /** + * Return the value of the selected query parameter as an array of + * alphanumeric strings (consisting of only digits, letters and + * underscores). + * + * @param string $param parameter name + * + * @return array parameter value as array (if set), else an empty array + */ + public static function optionArray($param) + { + $array = self::getArray($param); + + foreach ($array as $key => $value) { + if (preg_match('/\\W/', $value)) { + unset($array[$key]); + } + } + + return $array; + } + + /** + * Return the value of the selected query parameter as an integer array. + * + * @param string $param parameter name + * + * @return array parameter value as array (if set), else an empty array + */ + public static function intArray($param) + { + $array = self::getArray($param); + + foreach ($array as $key => $value) { + $array[$key] = (int) $value; + } + + return $array; + } + + /** + * Return the value of the selected query parameter as a float array. + * + * @param string $param parameter name + * + * @return array parameter value as array (if set), else an empty array + */ + public static function floatArray($param) + { + $array = self::getArray($param); + + foreach ($array as $key => $value) { + $array[$key] = (float) strtr($value, ',', '.'); + } + + return $array; + } + + /** + * Return the value of the selected query parameter as a boolean. + * + * @param string $param parameter name + * @param bool $default default value if parameter is not set + * + * @return bool parameter value as bool (if set), else NULL + * + * @since Stud.IP 4.4 + */ + public static function bool($param, $default = null) + { + $value = self::get($param, $default); + + if (isset($value)) { + $value = (bool) $value; + } + + return $value; + } + + /** + * Return the value of the selected query parameter as a boolean array. + * + * @param string $param parameter name + * + * @return array parameter value as array (if set), else an empty array + * + * @since Stud.IP 4.4 + */ + public static function boolArray($param) + { + $array = self::getArray($param); + + foreach ($array as $key => $value) { + $array[$key] = (bool) $value; + } + + return $array; + } + + /** + * Return the value of the selected query parameter as an array of + * strings consisting only of allowed characters for usernames. + * + * @param string $param parameter name + * + * @return array parameter value as array (if set), else an empty array + */ + public static function usernameArray($param) + { + $array = self::getArray($param); + + foreach ($array as $key => $value) { + if (!preg_match(Config::get()->USERNAME_REGULAR_EXPRESSION, $value)) { + unset($array[$key]); + } + } + + return $array; + } + /** + * Check whether a form submit button has been pressed. This works for + * both image and text submit buttons. + * + * @param string $param submit button name + * + * @returns boolean true if the button has been submitted, else false + */ + public static function submitted($param) + { + $request = self::getInstance(); + + return isset($request[$param]) + || isset($request[$param . '_x']); + } + + /** + * Check whether one of the form submit buttons has been + * pressed. This works for both image and text submit buttons. + * + * @param string ... + * a variable argument list of submit button names + * + * @returns boolean true if any button has been submitted, else false + */ + public static function submittedSome($param/*, ... */) + { + foreach(func_get_args() as $button) { + if (self::submitted($button)) { + return TRUE; + } + } + return FALSE; + } + + /** + * Quote a given string or array using addslashes(). If the parameter + * is an array, the quoting is applied recursively. + * + * @param mixed $value string or array value to be quoted + * + * @return mixed quoted string or array + */ + public static function addslashes($value) + { + if (is_array($value)) { + foreach ($value as $key => $val) { + $value[$key] = self::addslashes($val); + } + } else { + $value = addslashes($value); + } + + return $value; + } + + /** + * Returns the (uppercase) request method. + * + * @return string the uppercased method of the request + */ + public static function method() + { + return mb_strtoupper($_SERVER['X_HTTP_METHOD_OVERRIDE'] ?? $_SERVER['REQUEST_METHOD']); + } + + /** + * @return boolean true if this a GET request + */ + public static function isGet() + { + return self::method() === 'GET'; + } + + /** + * @return boolean true if this a POST request + */ + public static function isPost() + { + return self::method() === 'POST'; + } + + /** + * @return boolean true if this a PUT request + */ + public static function isPut() + { + return self::method() === 'PUT'; + } + + /** + * @return boolean true if this a DELETE request + */ + public static function isDelete() + { + return self::method() === 'DELETE'; + } + + + /** + * @return boolean true if this an XmlHttpRequest sent by jQuery/prototype + */ + public static function isXhr() + { + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && + strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], 'xmlhttprequest') === 0; + } + + /** + * This is an alias of Request::isXhr + * + * @return boolean true if this an XmlHttpRequest sent by jQuery/prototype + */ + public static function isAjax() + { + return self::isXhr(); + } + + /** + * extracts some params from request, the desired params must be a comma separated list + * for each param, the type of used extraction method can be specified after the name, + * default is get + * null values are not returned + * + * e.g.: + * $data = Request::extract('admission_prelim int, admission_binding submitted, admission_prelim_txt'); + * will yield + * array(3) { + * ["admission_prelim"]=> + * int(1) + * ["admission_binding"]=> + * bool(false) + * ["admission_prelim_txt"]=> + * string(0) "" + * } + * @param string $what comma separated list of param names and types + * @return array assoc array with extracted data + */ + public static function extract($what) + { + $extract = []; + $return = []; + foreach (explode(',', $what) as $one) { + $extract[] = array_values(array_filter(array_map('trim', explode(' ', $one)))); + } + foreach ($extract as $one) { + [$param, $func] = $one; + if (!$func) { + $func = 'get'; + } + $value = self::$func($param); + if ($value !== null) { + $return[$param] = $value; + } + } + return $return; + } + + /** + * returns true if http header indicates that the response will be rendered as dialog + * + * @return bool + */ + public static function isDialog() + { + return self::isXhr() && isset($_SERVER['HTTP_X_DIALOG']); + } + + /** + * Returns an object that has previously been serialized using the + * ObjectBuilder. + * + * @param String $param parameter name + * @param mixed $expected_class Expected class name of object (optional) + * @param bool $allow_null If true, return null on error; otherwise an + * exception is thrown + * @return mixed Object of arbitrary type or null on error and $allow_null + * @throws Exception when an error occurs and $allow_null = false + * @see ObjectBuilder + */ + public static function getObject($param, $expected_class = null, $allow_null = true) + { + try { + return ObjectBuilder::build(Request::get($param), $expected_class); + } catch (Exception $e) { + if ($allow_null) { + return null; + } + + throw $e; + } + } + + /** + * Returns a collection of objects that have previously been serialized + * using the ObjectBuilder. + * + * @param String $param parameter name + * @param mixed $expected_class Expected class name of objects (optional) + * @param bool $allow_null If true, return empty array on error; + * otherwise an exception is thrown + * @return array as collection of objects + * @throws Exception when an error occurs and $allow_null = false + * @see ObjectBuilder + */ + public static function getManyObjects($param, $expected_class = null, $allow_null = true) + { + try { + $request = self::getInstance(); + return ObjectBuilder::buildMany($request[$param] ?? null, $expected_class); + } catch (Exception $e) { + if ($allow_null) { + return []; + } + + throw $e; + } + } +} diff --git a/lib/classes/ResetButton.class.php b/lib/classes/ResetButton.class.php deleted file mode 100644 index 8702c4b..0000000 --- a/lib/classes/ResetButton.class.php +++ /dev/null @@ -1,53 +0,0 @@ - HTML element. - * - * @param string $label the label of the button element - * @param string $name the @name element of the button element - * @param array $attributes the attributes of the button element - */ - protected function initialize($label, $name, $attributes) - { -// $this->attributes['name'] = $name ?: $this->label; - } - - /** - * @return string returns a HTML representation of this button. - */ - public function __toString() - { - // add "button" to attribute @class - @$this->attributes["class"] .= " button"; - - $attributes = []; - ksort($this->attributes); - foreach ($this->attributes as $k => $v) { - $attributes[] = sprintf(' %s="%s"', $k, htmlReady($v)); - } - - return sprintf('', - join('', $attributes), - htmlReady($this->label)); - } -} diff --git a/lib/classes/ResetButton.php b/lib/classes/ResetButton.php new file mode 100644 index 0000000..8702c4b --- /dev/null +++ b/lib/classes/ResetButton.php @@ -0,0 +1,53 @@ + HTML element. + * + * @param string $label the label of the button element + * @param string $name the @name element of the button element + * @param array $attributes the attributes of the button element + */ + protected function initialize($label, $name, $attributes) + { +// $this->attributes['name'] = $name ?: $this->label; + } + + /** + * @return string returns a HTML representation of this button. + */ + public function __toString() + { + // add "button" to attribute @class + @$this->attributes["class"] .= " button"; + + $attributes = []; + ksort($this->attributes); + foreach ($this->attributes as $k => $v) { + $attributes[] = sprintf(' %s="%s"', $k, htmlReady($v)); + } + + return sprintf('', + join('', $attributes), + htmlReady($this->label)); + } +} diff --git a/lib/classes/Score.class.php b/lib/classes/Score.class.php deleted file mode 100644 index 4d114d5..0000000 --- a/lib/classes/Score.class.php +++ /dev/null @@ -1,225 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class Score -{ - // How long is the duration of a score-block? - const MEASURING_STEP = 1800; // half an hour - - public static function getScoreContent($persons) - { - $user_ids = array_keys($persons); - - // News - $query = "SELECT nr.range_id as user_id, COUNT(*) AS newscount - FROM news_range AS nr - INNER JOIN news AS n ON (nr.news_id = n.news_id) - WHERE nr.range_id IN (?) AND UNIX_TIMESTAMP() <= n.date + n.expire - GROUP BY nr.range_id - ORDER BY NULL"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$user_ids]); - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $persons[$row['user_id']]['newscount'] = $row['newscount']; - } - - // Events - $query = "SELECT `range_id` AS user_id, COUNT(*) AS eventcount - FROM `calendar_date_assignments` - INNER JOIN `calendar_dates` - ON (`calendar_date_assignments`.`calendar_date_id` = `calendar_dates`.`id` AND `access` = 'PUBLIC') - WHERE `range_id` IN (?) AND UNIX_TIMESTAMP() <= `end` - GROUP BY `range_id` - ORDER BY NULL"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$user_ids]); - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $persons[$row['user_id']]['eventcount'] = $row['eventcount']; - } - - // Votes - if (Config::get()->VOTE_ENABLE){ - $query = "SELECT questionnaire_assignments.range_id as user_id, COUNT(*) AS votecount - FROM questionnaire_assignments - WHERE questionnaire_assignments.range_id IN (?) - AND questionnaire_assignments.range_type = 'user' - GROUP BY questionnaire_assignments.range_id - ORDER BY NULL"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$user_ids]); - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $persons[$row['user_id']]['votecount'] = $row['votecount']; - } - } - - return $persons; - } - - /** - * Retrieves the titel for a given studip score - * - * @param integer a score value - * @param integer gender (0: unknown, 1: male; 2: female) - * @return string the titel - * - */ - public static function getTitel($score, $gender = 0) - { - if ($score) { - $logscore = floor(log10($score) / log10(2)); - } else { - $logscore = 0; - } - - if ($logscore > 20) { - $logscore = 20; - } - - $titel = []; - $titel[0] = [_('Unbeschriebenes Blatt'), _('Unbeschriebenes Blatt')]; - $titel[1] = [_('Unbeschriebenes Blatt'), _('Unbeschriebenes Blatt')]; - $titel[2] = [_('Unbeschriebenes Blatt'), _('Unbeschriebenes Blatt')]; - $titel[3] = [_('Neuling'), _('Neuling')]; - $titel[4] = [_('Greenhorn'), _('Greenhorn')]; - $titel[5] = [_('Anfänger'), _('Anfängerin')]; - $titel[6] = [_('Einsteiger'), _('Einsteigerin')]; - $titel[7] = [_('Beginner'), _('Beginnerin')]; - $titel[8] = [_('Novize'), _('Novizin')]; - $titel[9] = [_('Fortgeschrittener'), _('Fortgeschrittene')]; - $titel[10] = [_('Kenner'), _('Kennerin')]; - $titel[11] = [_('Könner'), _('Könnerin')]; - $titel[12] = [_('Profi'), _('Profi')]; - $titel[13] = [_('Experte'), _('Expertin')]; - $titel[14] = [_('Meister'), _('Meisterin')]; - $titel[15] = [_('Großmeister'), _('Großmeisterin')]; - $titel[16] = [_('Idol'), _('Idol')]; - $titel[17] = [_('Guru'), _('Hohepriesterin')]; - $titel[18] = [_('Lichtgestalt'), _('Lichtgestalt')]; - $titel[19] = [_('Halbgott'), _('Halbgöttin')]; - $titel[20] = [_('Gott'), _('Göttin')]; - - return $titel[$logscore][$gender == 2 ? 1 : 0]; - } - - /** - * Retrieves the score for the current user - * - * @return integer the score - * - */ - public static function GetMyScore($user_or_id = null) - { - $user = $user_or_id ? User::toObject($user_or_id) : User::findCurrent(); - $cache = \Studip\Cache\Factory::getCache(); - if ($cache->read("user_score_of_".$user->id)) { - return $cache->read("user_score_of_".$user->id); - } - //Behold! The all new mighty score algorithm! - //Step 1: Select all activities as mkdate-timestamps. - //Step 2: Group these activities to timeslots of halfhours - // with COUNT(*) as a weigh of the timeslot. - //Step 3: Calculate the measurement of the timeslot from the weigh of it. - // This makes the first activity count fully, the second - // almost half and so on. We use log_n to make huge amounts of - // activities to not count so much. - //Step 4: Calculate a single score for each timeslot depending on the - // measurement and the mkdate-timestamp. Use arctan as the function - // here so that older activities tend to zero. - //Step 5: Sum all scores from all timeslots together. - $sql = " - SELECT round(SUM((-atan(measurement / " . round(31556926 / self::MEASURING_STEP) . ") / PI() + 0.5) * 200)) as score - FROM ( - SELECT ((unix_timestamp() / " . self::MEASURING_STEP . ") - timeslot) / (LN(weigh) + 1) AS measurement - FROM ( - SELECT (round(mkdate / " . self::MEASURING_STEP . ")) as timeslot, COUNT(*) AS weigh - FROM ( - " . self::createTimestampQuery() . " - ) as mkdates - GROUP BY timeslot - ) as measurements - ) as dates - "; - $stmt = DBManager::get()->prepare($sql); - $stmt->execute([':user' => $user->id]); - $score = $stmt->fetchColumn(); - if ($user->score && $user->score != $score) { - $user->score = $score; - $user->store(); - } - $cache->write("user_score_of_{$user->id}", $score, 60 * 5); - - return $score; - } - - protected static function createTimestampQuery() - { - $statements = []; - foreach (self::getActivityTables() as $table) { - $statements[] = "SELECT " - . ($table['date_column'] ?? 'mkdate') - . " AS mkdate FROM " - . $table['table'] - . " WHERE " - . ($table['user_id_column'] ?? 'user_id') - . " = :user " - . (!empty($table['where']) ? (' AND ' . $table['where']) : ''); - } - return join(' UNION ', $statements); - } - - protected static function getActivityTables() - { - $tables = []; - $tables[] = ['table' => 'user_info']; - $tables[] = ['table' => 'comments']; - $tables[] = ['table' => 'file_refs']; - $tables[] = ['table' => 'forum_entries']; - $tables[] = ['table' => 'news']; - $tables[] = ['table' => 'seminar_user']; - $tables[] = [ - 'table' => 'blubber_threads' - ]; - $tables[] = [ - 'table' => 'blubber_comments' - ]; - $tables[] = [ - 'table' => 'kategorien', - 'user_id_column' => 'range_id', - ]; - $tables[] = [ - 'table' => 'message', - 'user_id_column' => 'autor_id' - ]; - $tables[] = ['table' => 'questionnaires']; - $tables[] = [ - 'table' => 'questionnaire_answers', - 'date_column' => 'chdate', - ]; - $tables[] = ['table' => 'questionnaire_anonymous_answers']; - $tables[] = [ - 'table' => 'wiki_pages', - 'date_column' => 'chdate' - ]; - - foreach (PluginManager::getInstance()->getPlugins(ScorePlugin::class) as $plugin) { - foreach ((array) $plugin->getPluginActivityTables() as $table) { - if ($table['table']) { - $tables[] = $table; - } - } - } - - return $tables; - } -} diff --git a/lib/classes/Score.php b/lib/classes/Score.php new file mode 100644 index 0000000..4d114d5 --- /dev/null +++ b/lib/classes/Score.php @@ -0,0 +1,225 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class Score +{ + // How long is the duration of a score-block? + const MEASURING_STEP = 1800; // half an hour + + public static function getScoreContent($persons) + { + $user_ids = array_keys($persons); + + // News + $query = "SELECT nr.range_id as user_id, COUNT(*) AS newscount + FROM news_range AS nr + INNER JOIN news AS n ON (nr.news_id = n.news_id) + WHERE nr.range_id IN (?) AND UNIX_TIMESTAMP() <= n.date + n.expire + GROUP BY nr.range_id + ORDER BY NULL"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_ids]); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $persons[$row['user_id']]['newscount'] = $row['newscount']; + } + + // Events + $query = "SELECT `range_id` AS user_id, COUNT(*) AS eventcount + FROM `calendar_date_assignments` + INNER JOIN `calendar_dates` + ON (`calendar_date_assignments`.`calendar_date_id` = `calendar_dates`.`id` AND `access` = 'PUBLIC') + WHERE `range_id` IN (?) AND UNIX_TIMESTAMP() <= `end` + GROUP BY `range_id` + ORDER BY NULL"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_ids]); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $persons[$row['user_id']]['eventcount'] = $row['eventcount']; + } + + // Votes + if (Config::get()->VOTE_ENABLE){ + $query = "SELECT questionnaire_assignments.range_id as user_id, COUNT(*) AS votecount + FROM questionnaire_assignments + WHERE questionnaire_assignments.range_id IN (?) + AND questionnaire_assignments.range_type = 'user' + GROUP BY questionnaire_assignments.range_id + ORDER BY NULL"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_ids]); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $persons[$row['user_id']]['votecount'] = $row['votecount']; + } + } + + return $persons; + } + + /** + * Retrieves the titel for a given studip score + * + * @param integer a score value + * @param integer gender (0: unknown, 1: male; 2: female) + * @return string the titel + * + */ + public static function getTitel($score, $gender = 0) + { + if ($score) { + $logscore = floor(log10($score) / log10(2)); + } else { + $logscore = 0; + } + + if ($logscore > 20) { + $logscore = 20; + } + + $titel = []; + $titel[0] = [_('Unbeschriebenes Blatt'), _('Unbeschriebenes Blatt')]; + $titel[1] = [_('Unbeschriebenes Blatt'), _('Unbeschriebenes Blatt')]; + $titel[2] = [_('Unbeschriebenes Blatt'), _('Unbeschriebenes Blatt')]; + $titel[3] = [_('Neuling'), _('Neuling')]; + $titel[4] = [_('Greenhorn'), _('Greenhorn')]; + $titel[5] = [_('Anfänger'), _('Anfängerin')]; + $titel[6] = [_('Einsteiger'), _('Einsteigerin')]; + $titel[7] = [_('Beginner'), _('Beginnerin')]; + $titel[8] = [_('Novize'), _('Novizin')]; + $titel[9] = [_('Fortgeschrittener'), _('Fortgeschrittene')]; + $titel[10] = [_('Kenner'), _('Kennerin')]; + $titel[11] = [_('Könner'), _('Könnerin')]; + $titel[12] = [_('Profi'), _('Profi')]; + $titel[13] = [_('Experte'), _('Expertin')]; + $titel[14] = [_('Meister'), _('Meisterin')]; + $titel[15] = [_('Großmeister'), _('Großmeisterin')]; + $titel[16] = [_('Idol'), _('Idol')]; + $titel[17] = [_('Guru'), _('Hohepriesterin')]; + $titel[18] = [_('Lichtgestalt'), _('Lichtgestalt')]; + $titel[19] = [_('Halbgott'), _('Halbgöttin')]; + $titel[20] = [_('Gott'), _('Göttin')]; + + return $titel[$logscore][$gender == 2 ? 1 : 0]; + } + + /** + * Retrieves the score for the current user + * + * @return integer the score + * + */ + public static function GetMyScore($user_or_id = null) + { + $user = $user_or_id ? User::toObject($user_or_id) : User::findCurrent(); + $cache = \Studip\Cache\Factory::getCache(); + if ($cache->read("user_score_of_".$user->id)) { + return $cache->read("user_score_of_".$user->id); + } + //Behold! The all new mighty score algorithm! + //Step 1: Select all activities as mkdate-timestamps. + //Step 2: Group these activities to timeslots of halfhours + // with COUNT(*) as a weigh of the timeslot. + //Step 3: Calculate the measurement of the timeslot from the weigh of it. + // This makes the first activity count fully, the second + // almost half and so on. We use log_n to make huge amounts of + // activities to not count so much. + //Step 4: Calculate a single score for each timeslot depending on the + // measurement and the mkdate-timestamp. Use arctan as the function + // here so that older activities tend to zero. + //Step 5: Sum all scores from all timeslots together. + $sql = " + SELECT round(SUM((-atan(measurement / " . round(31556926 / self::MEASURING_STEP) . ") / PI() + 0.5) * 200)) as score + FROM ( + SELECT ((unix_timestamp() / " . self::MEASURING_STEP . ") - timeslot) / (LN(weigh) + 1) AS measurement + FROM ( + SELECT (round(mkdate / " . self::MEASURING_STEP . ")) as timeslot, COUNT(*) AS weigh + FROM ( + " . self::createTimestampQuery() . " + ) as mkdates + GROUP BY timeslot + ) as measurements + ) as dates + "; + $stmt = DBManager::get()->prepare($sql); + $stmt->execute([':user' => $user->id]); + $score = $stmt->fetchColumn(); + if ($user->score && $user->score != $score) { + $user->score = $score; + $user->store(); + } + $cache->write("user_score_of_{$user->id}", $score, 60 * 5); + + return $score; + } + + protected static function createTimestampQuery() + { + $statements = []; + foreach (self::getActivityTables() as $table) { + $statements[] = "SELECT " + . ($table['date_column'] ?? 'mkdate') + . " AS mkdate FROM " + . $table['table'] + . " WHERE " + . ($table['user_id_column'] ?? 'user_id') + . " = :user " + . (!empty($table['where']) ? (' AND ' . $table['where']) : ''); + } + return join(' UNION ', $statements); + } + + protected static function getActivityTables() + { + $tables = []; + $tables[] = ['table' => 'user_info']; + $tables[] = ['table' => 'comments']; + $tables[] = ['table' => 'file_refs']; + $tables[] = ['table' => 'forum_entries']; + $tables[] = ['table' => 'news']; + $tables[] = ['table' => 'seminar_user']; + $tables[] = [ + 'table' => 'blubber_threads' + ]; + $tables[] = [ + 'table' => 'blubber_comments' + ]; + $tables[] = [ + 'table' => 'kategorien', + 'user_id_column' => 'range_id', + ]; + $tables[] = [ + 'table' => 'message', + 'user_id_column' => 'autor_id' + ]; + $tables[] = ['table' => 'questionnaires']; + $tables[] = [ + 'table' => 'questionnaire_answers', + 'date_column' => 'chdate', + ]; + $tables[] = ['table' => 'questionnaire_anonymous_answers']; + $tables[] = [ + 'table' => 'wiki_pages', + 'date_column' => 'chdate' + ]; + + foreach (PluginManager::getInstance()->getPlugins(ScorePlugin::class) as $plugin) { + foreach ((array) $plugin->getPluginActivityTables() as $table) { + if ($table['table']) { + $tables[] = $table; + } + } + } + + return $tables; + } +} diff --git a/lib/classes/SemBrowse.class.php b/lib/classes/SemBrowse.class.php deleted file mode 100644 index 536b7e3..0000000 --- a/lib/classes/SemBrowse.class.php +++ /dev/null @@ -1,1264 +0,0 @@ -group_by_fields = - [ - [ - 'name' => _('Semester'), - 'group_field' => 'sem_number' - ], - [ - 'name' => _('Bereich'), - 'group_field' => 'bereich' - ], - [ - 'name' => _('Lehrende'), - 'group_field' => 'fullname', - 'unique_field' => 'username' - ], - [ - 'name' => _('Typ'), - 'group_field' => 'status' - ], - [ - 'name' => _('Einrichtung'), - 'group_field' => 'Institut', - 'unique_field' => 'Institut_id' - ] - ]; - - if (empty($_SESSION['sem_browse_data'])) { - $_SESSION['sem_browse_data'] = $sem_browse_data_init; - } - $this->sem_browse_data =& $_SESSION['sem_browse_data']; - - $level_change = Request::option('start_item_id') || Request::submitted('search_sem_sem_change'); - - for ($i = 0; $i < count($this->persistent_fields); ++$i){ - $persistend_field = $this->persistent_fields[$i]; - if (Request::get($persistend_field) != null) { - $this->sem_browse_data[$persistend_field] = Request::option($persistend_field); - } - } - $this->search_obj = new StudipSemSearch( - 'search_sem', - false, - !(is_object($GLOBALS['perm']) && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)), - $this->sem_browse_data['show_class'] ?? null - ); - - - if (Request::get($this->search_obj->form_name . '_scope_choose')) { - $this->sem_browse_data['start_item_id'] = - Request::option($this->search_obj->form_name . '_scope_choose'); - } - if (Request::get($this->search_obj->form_name . '_range_choose')) { - $this->sem_browse_data['start_item_id'] = - Request::option($this->search_obj->form_name . '_range_choose'); - } - if (Request::get($this->search_obj->form_name . '_sem')) { - $this->sem_browse_data['default_sem'] = - Request::option($this->search_obj->form_name . '_sem'); - } - - if ( - Request::get('keep_result_set') - || !empty($this->sem_browse_data['sset']) - || (!empty($this->sem_browse_data['search_result']) && !empty($this->sem_browse_data['show_entries'])) - ) { - $this->show_result = true; - } - - if (isset($this->sem_browse_data['cmd']) && $this->sem_browse_data['cmd'] === 'xts') { - if ($this->search_obj->new_search_button_clicked) { - $this->show_result = false; - $this->sem_browse_data['sset'] = false; - $this->sem_browse_data['search_result'] = []; - } - } - - if (!isset($this->sem_browse_data['default_sem'])) { - $this->sem_number[0] = 0; - } elseif ($this->sem_browse_data['default_sem'] != 'all') { - $this->sem_number[0] = intval($this->sem_browse_data['default_sem']); - } else { - $this->sem_number = false; - } - - $sem_status = (!empty($this->sem_browse_data['sem_status']) && is_array($this->sem_browse_data['sem_status'])) ? $this->sem_browse_data['sem_status'] : false; - - if ($this->sem_browse_data['level'] == 'vv') { - if (empty($this->sem_browse_data['start_item_id'])) { - $this->sem_browse_data['start_item_id'] = 'root'; - } - $this->sem_tree = new StudipSemTreeViewSimple( - $this->sem_browse_data['start_item_id'], - $this->sem_number, $sem_status, - !(is_object($GLOBALS['perm']) - && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM))); - if (Request::option('cmd') != 'show_sem_range' - && $level_change - && !$this->search_obj->search_button_clicked ) { - $this->get_sem_range($this->sem_browse_data['start_item_id'], false); - $this->show_result = true; - $this->sem_browse_data['show_entries'] = 'level'; - $this->sem_browse_data['sset'] = false; - } - if ($this->search_obj->sem_change_button_clicked) { - $this->get_sem_range($this->sem_browse_data['start_item_id'], - ($this->sem_browse_data['show_entries'] == 'sublevels')); - $this->show_result = true; - } - } - - if ($this->sem_browse_data['level'] == 'ev'){ - if (!$this->sem_browse_data['start_item_id']) { - $this->sem_browse_data['start_item_id'] = 'root'; - } - $this->range_tree = new StudipSemRangeTreeViewSimple( - $this->sem_browse_data['start_item_id'], - $this->sem_number, - $sem_status, - !(is_object($GLOBALS['perm']) - && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM))); - if (Request::option('cmd') != 'show_sem_range_tree' - && $level_change - && !$this->search_obj->search_button_clicked ) { - $this->get_sem_range_tree($this->sem_browse_data['start_item_id'], false); - $this->show_result = true; - $this->sem_browse_data['show_entries'] = 'level'; - $this->sem_browse_data['sset'] = false; - } - if ($this->search_obj->sem_change_button_clicked) { - $this->get_sem_range_tree($this->sem_browse_data['start_item_id'], - ($this->sem_browse_data['show_entries'] == 'sublevels')); - $this->show_result = true; - } - } - - if ($this->search_obj->search_button_clicked - && !$this->search_obj->new_search_button_clicked) { - $this->search_obj->override_sem = $this->sem_number; - $this->search_obj->doSearch(); - if ($this->search_obj->found_rows) { - $this->sem_browse_data['search_result'] = array_flip($this->search_obj->search_result->getRows('seminar_id')); - } else { - $this->sem_browse_data['search_result'] = []; - } - $this->show_result = true; - $this->sem_browse_data['show_entries'] = false; - $this->sem_browse_data['sset'] = Request::get($this->search_obj->form_name . "_quick_search_parameter"); - } - - - if (Request::option('cmd') == 'show_sem_range') { - $tmp = explode('_', Request::option('item_id')); - $this->get_sem_range($tmp[0], isset($tmp[1])); - $this->show_result = true; - $this->sem_browse_data['show_entries'] = (isset($tmp[1])) ? 'sublevels' : 'level'; - $this->sem_browse_data['sset'] = false; - } - - if (Request::option('cmd') == 'show_sem_range_tree') { - $tmp = explode('_', Request::option('item_id')); - $this->get_sem_range_tree($tmp[0],isset($tmp[1])); - $this->show_result = true; - $this->sem_browse_data['show_entries'] = (isset($tmp[1])) ? 'sublevels' : 'level'; - $this->sem_browse_data['sset'] = false; - } - - if (Request::option('do_show_class') - && count($this->sem_browse_data['sem_status'])) { - $this->get_sem_class(); - } - - } - - /** - * Returns whether the search for modules has to be displayed. - * - * @return boolean True if search for modules has to be displayed. - */ - private function showModules() - { - if ($this->sem_browse_data['show_class'] == 'all') { - return true; - } - if (!is_array($this->classes_show_module)) { - $this->classes_show_class = []; - foreach ($GLOBALS['SEM_CLASS'] as $sem_class_key => $sem_class){ - if ($sem_class['module']) { - $this->classes_show_module[] = $sem_class_key; - } - } - } - return in_array($this->sem_browse_data['show_class'], $this->classes_show_class); - } - - public function show_class() - { - if ($this->sem_browse_data['show_class'] == 'all') { - return true; - } - if (!is_array($this->classes_show_class)) { - $this->classes_show_class = []; - foreach ($GLOBALS['SEM_CLASS'] as $sem_class_key => $sem_class) { - if ($sem_class['bereiche']) { - $this->classes_show_class[] = $sem_class_key; - } - } - } - return in_array($this->sem_browse_data['show_class'], $this->classes_show_class); - } - - public function get_sem_class() - { - $query = "SELECT `Seminar_id` - FROM `seminare` - WHERE `status` IN (?)"; - - $show_all = is_object($GLOBALS['perm']) - && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM); - if (!$show_all) { - $query .= ' AND visible = 1'; - } - - $sem_ids = DBManager::get()->fetchFirst($query); - if (is_array($sem_ids)) { - $this->sem_browse_data['search_result'] = array_flip($sem_ids); - } - $this->sem_browse_data['sset'] = true; - $this->show_result = true; - } - - public function get_sem_range($item_id, $with_kids) - { - if (!is_object($this->sem_tree)) { - $sem_status = (is_array($this->sem_browse_data['sem_status'])) ? $this->sem_browse_data['sem_status'] : false; - $this->sem_tree = new StudipSemTreeViewSimple( - $this->sem_browse_data['start_item_id'], - $this->sem_number, - $sem_status, - !(is_object($GLOBALS['perm']) - && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM))); - } - $sem_ids = $this->sem_tree->tree->getSemIds($item_id,$with_kids); - if (is_array($sem_ids)) { - $this->sem_browse_data['search_result'] = array_flip($sem_ids); - } else { - $this->sem_browse_data['search_result'] = []; - } - } - - public function get_sem_range_tree($item_id, $with_kids) - { - $range_object = RangeTreeObject::GetInstance($item_id); - if ($with_kids) { - $inst_ids = $range_object->getAllObjectKids(); - } - $inst_ids[] = $range_object->item_data['studip_object_id']; - $db_view = DbView::getView('sem_tree'); - $db_view->params[0] = $inst_ids; - $db_view->params[1] = (is_object($GLOBALS['perm']) && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) ? '' : ' AND c.visible=1'; - $db_view->params[1] .= !empty($this->sem_browse_data['sem_status']) && is_array($this->sem_browse_data['sem_status']) - ? " AND c.status IN('" . join("','", $this->sem_browse_data['sem_status']) ."')" - : ''; - $db_view->params[2] = is_array($this->sem_number) - ? ' HAVING sem_number IN (' - . join(',', $this->sem_number) - . ') OR (sem_number <= ' - . $this->sem_number[count($this->sem_number) - 1] - . ' AND (sem_number_end >= ' - . $this->sem_number[count($this->sem_number) - 1] - . ' OR sem_number_end = -1)) ' - : ''; - $db_snap = new DbSnapshot($db_view->get_query('view:SEM_INST_GET_SEM')); - if ($db_snap->numRows) { - $sem_ids = $db_snap->getRows('Seminar_id'); - $this->sem_browse_data['search_result'] = array_flip($sem_ids); - } else { - $this->sem_browse_data['search_result'] = []; - } - } - - /** - * Prints the quicksearch form. - */ - private function printQuickSearch() - { - if ($this->sem_browse_data['level'] === 'vv') { - $this->search_obj->sem_tree =& $this->sem_tree->tree; - if ($this->sem_tree->start_item_id !== 'root') { - $this->search_obj->search_scopes[] = $this->sem_tree->start_item_id; - } - } elseif ($this->sem_browse_data['level'] === 'ev') { - $this->search_obj->range_tree =& $this->range_tree->tree; - if ($this->range_tree->start_item_id !== 'root'){ - $this->search_obj->search_ranges[] = $this->range_tree->start_item_id; - } - } - - $template = $GLOBALS['template_factory']->open('sembrowse/quick-search.php'); - $template->search_obj = $this->search_obj; - $template->sem_browse_data = $this->sem_browse_data; - $template->sem_tree = $this->sem_tree; - $template->range_tree = $this->range_tree; - $template->quicksearch = $this->getQuicksearch(); - - echo $template->render(); - } - - private function getQuicksearch() - { - $quicksearch = QuickSearch::get( - $this->search_obj->form_name . '_quick_search', - new SeminarSearch() - ); - - $quicksearch->setAttributes([ - 'aria-label' => _('Suchbegriff'), - 'autofocus' => '', - ]); - $quicksearch->fireJSFunctionOnSelect('selectSem'); - $quicksearch->noSelectbox(); - $quicksearch->defaultValue( - $this->sem_browse_data['sset'] ?: '', - $this->sem_browse_data['sset'] ?: '' - ); - - return $quicksearch; - } - - private function printExtendedSearch() - { - $template = $GLOBALS['template_factory']->open('sembrowse/extended-search.php'); - $template->search_obj = $this->search_obj; - $template->sem_browse_data = $this->sem_browse_data; - $template->show_class = $this->show_class(); - echo $template->render(); - } - - public function do_output() - { - if ($this->sem_browse_data['cmd'] == 'xts') { - $this->printExtendedSearch(); - } - $path_id = Request::option('path_id'); - URLHelper::addLinkParam('path_id', $path_id); - $this->print_level($path_id); - } - - public function print_level($start_id = null) - { - ob_start(); - - echo "\n" . '' . "\n"; - if ($this->sem_browse_data['level'] == 'vv') { - echo "\n" . ''; - ob_end_flush(); - } - - public function print_result() - { - ob_start(); - global $SEM_TYPE, $SEM_CLASS; - - if (is_array($this->sem_browse_data['search_result']) - && count($this->sem_browse_data['search_result'])) { - if (!is_object($this->sem_tree)) { - $this->sem_tree = new StudipSemTreeViewSimple( - $this->sem_browse_data['start_item_id'] ?? null, - $this->sem_number, - !empty($this->sem_browse_data['sem_status']) && is_array($this->sem_browse_data['sem_status']) - ? $this->sem_browse_data['sem_status'] : false, - !(is_object($GLOBALS['perm']) && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) - ); - } - $the_tree = $this->sem_tree->tree; - - list($group_by_data, $sem_data) = $this->get_result(); - - $visibles = $sem_data; - if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { - $visibles = array_filter($visibles, function ($c) { - return key($c['visible']) == 1; - }); - } - - echo ''; - foreach ($group_by_data as $group_field => $sem_ids) { - if (Config::get()->COURSE_SEARCH_SHOW_ADMISSION_STATE) { - echo ''; - ob_end_flush(); - ob_start(); - if (is_array($sem_ids['Seminar_id'])) { - foreach(array_keys($sem_ids['Seminar_id']) as $seminar_id){ - echo $this->printCourseRow($seminar_id, $sem_data); - } - } - } - echo '
'; - } else { - echo '
'; - } - switch ($this->sem_browse_data['group_by'] ?? null) { - case 0: - echo htmlReady($this->search_obj->sem_dates[$group_field]['name'] ?? ''); - break; - case 1: - if ($the_tree->tree_data[$group_field]) { - echo htmlReady($the_tree->getShortPath($group_field)); - if (is_object($this->sem_tree)){ - echo $this->sem_tree->getInfoIcon($group_field); - } - } else { - echo _('keine Studienbereiche eingetragen'); - } - break; - case 3: - echo htmlReady($SEM_TYPE[$group_field]['name'] - . ' (' - . $SEM_CLASS[$SEM_TYPE[$group_field]['class']]['name'] - . ')'); - break; - default: - echo htmlReady($group_field); - } - echo '
'; - } elseif ($this->search_obj->search_button_clicked - && !$this->search_obj->new_search_button_clicked) { - $details = []; - if ($this->search_obj->found_rows === false) { - $details = [_('Der Suchbegriff fehlt oder ist zu kurz')]; - } - if ($details) { - PageLayout::postError(_('Ihre Suche ergab keine Treffer'), $details); - } else { - PageLayout::postInfo(_('Ihre Suche ergab keine Treffer')); - } - $this->sem_browse_data['sset'] = 0; - } - ob_end_flush(); - } - - public function get_result() - { - global $_fullname_sql, $user; - if ($this->sem_browse_data['group_by'] == 1) { - if (!is_object($this->sem_tree)) { - $the_tree = TreeAbstract::GetInstance('StudipSemTree', false); - } else { - $the_tree = $this->sem_tree->tree; - } - $sem_tree_query = ''; - if ($this->sem_browse_data['start_item_id'] != 'root' - && ($this->sem_browse_data['level'] == 'vv' - || $this->sem_browse_data['level'] == 'sbb')) { - $allowed_ranges = $the_tree->getKidsKids($this->sem_browse_data['start_item_id']); - $allowed_ranges[] = $this->sem_browse_data['start_item_id']; - $sem_tree_query = " AND sem_tree_id IN('" . join("','", $allowed_ranges) . "') "; - } - $add_fields = 'seminar_sem_tree.sem_tree_id AS bereich,'; - $add_query = "LEFT JOIN seminar_sem_tree ON (seminare.Seminar_id = seminar_sem_tree.seminar_id $sem_tree_query)"; - } else if ($this->sem_browse_data['group_by'] == 4){ - $add_fields = 'Institute.Name AS Institut,Institute.Institut_id,'; - $add_query = 'LEFT JOIN seminar_inst - ON (seminare.Seminar_id = seminar_inst.Seminar_id) - LEFT JOIN Institute - ON (Institute.Institut_id = seminar_inst.institut_id)'; - } else { - $add_fields = ''; - $add_query = ''; - } - - $dbv = DbView::getView('sem_tree'); - - $query = " - SELECT seminare.Seminar_id, VeranstaltungsNummer, seminare.status, - IF(seminare.visible = 0, CONCAT(seminare.Name, ' " . _('(versteckt)') - . "'), seminare.Name) AS Name," - . $add_fields - . $_fullname_sql['full'] . " AS fullname, - auth_user_md5.username," - . $dbv->sem_number_sql . ' AS sem_number, ' - . $dbv->sem_number_end_sql . ' AS sem_number_end, - seminar_user.position AS position, seminare.parent_course, seminare.visible - FROM seminare - LEFT JOIN seminar_user - ON (seminare.Seminar_id=seminar_user.Seminar_id AND seminar_user.status = ' . "'dozent'" . ') - LEFT JOIN auth_user_md5 - USING (user_id) - LEFT JOIN user_info - USING (user_id) ' - . $add_query . " - WHERE (seminare.Seminar_id IN('" . join("','", array_keys($this->sem_browse_data['search_result'])) . "') - OR seminare.parent_course IN ('" . join("','", array_keys($this->sem_browse_data['search_result'])) . "'))"; - - // don't show Studiengruppen if user not logged in - if (!$GLOBALS['user'] || $GLOBALS['user']->id == 'nobody') { - $studygroup_types = DBManager::get()->quote(studygroup_sem_types()); - $query .= " AND seminare.status NOT IN ({$studygroup_types})"; - } - - $db = new DB_Seminar($query); - $snap = new DbSnapshot($db); - $group_field = $this->group_by_fields[$this->sem_browse_data['group_by']]['group_field']; - $data_fields[0] = 'Seminar_id'; - if (!empty($this->group_by_fields[$this->sem_browse_data['group_by']]['unique_field'])) { - $data_fields[1] = $this->group_by_fields[$this->sem_browse_data['group_by']]['unique_field']; - } - if($user->id == 'nobody' && $snap->numRows == 0){ - $group_by_data = $sem_data = []; - }else{ - $group_by_data = $snap->getGroupedResult($group_field, $data_fields); - $sem_data = $snap->getGroupedResult('Seminar_id'); - } - - if ($this->sem_browse_data['group_by'] == 0) { - if($user->id == 'nobody' && $snap->numRows == 0){ - $group_by_duration = []; - }else{ - $group_by_duration = $snap->getGroupedResult('sem_number_end', ['sem_number', 'Seminar_id']); - } - foreach ($group_by_duration as $sem_number_end => $detail) { - if ($sem_number_end != -1 - && ($detail['sem_number'][$sem_number_end] - && count($detail['sem_number']) == 1)) { - continue; - } - - $current_semester_index = Semester::getIndexById(Semester::findCurrent()->semester_id, true, true); - foreach (array_keys($detail['Seminar_id']) as $seminar_id) { - $start_sem = key($sem_data[$seminar_id]['sem_number']); - if ($sem_number_end == -1) { - if ($this->sem_number === false) { - $sem_number_end = $current_semester_index && isset($this->search_obj->sem_dates[$current_semester_index + 1]) ? $current_semester_index + 1 : count($this->search_obj->sem_dates) -1; - } else { - $sem_number_end = $this->sem_number[0]; - } - } - for ($i = $start_sem; $i <= $sem_number_end; ++$i) { - if ($this->sem_number === false - || is_array($this->sem_number) - && in_array($i, $this->sem_number)) { - if (!empty($group_by_data[$i]) && empty($tmp_group_by_data[$i])) { - foreach (array_keys($group_by_data[$i]['Seminar_id']) as $id) { - $tmp_group_by_data[$i]['Seminar_id'][$id] = true; - } - } - $tmp_group_by_data[$i]['Seminar_id'][$seminar_id] = true; - } - } - } - } - if (!empty($tmp_group_by_data) && is_array($tmp_group_by_data)) { - if ($this->sem_number !== false) { - unset($group_by_data); - } - foreach ($tmp_group_by_data as $start_sem => $detail) { - $group_by_data[$start_sem] = $detail; - } - } - } - - //release memory - unset($snap); - unset($tmp_group_by_data); - - foreach ($group_by_data as $group_field => $sem_ids) { - foreach ($sem_ids['Seminar_id'] as $seminar_id => $foo) { - $name = mb_strtolower(key($sem_data[$seminar_id]['Name'])); - $name = str_replace(['ä', 'ö', 'ü'], ['ae', 'oe', 'ue'], $name); - if (Config::get()->IMPORTANT_SEMNUMBER && key($sem_data[$seminar_id]['VeranstaltungsNummer'])) { - $name = key($sem_data[$seminar_id]['VeranstaltungsNummer']) . ' ' . $name; - } - $group_by_data[$group_field]['Seminar_id'][$seminar_id] = $name; - } - uasort($group_by_data[$group_field]['Seminar_id'], 'strnatcmp'); - } - - switch ($this->sem_browse_data['group_by']) { - case 0: - krsort($group_by_data, SORT_NUMERIC); - break; - case 1: - uksort($group_by_data, function($a,$b) { - $the_tree = TreeAbstract::GetInstance('StudipSemTree', false); - $the_tree->buildIndex(); - return $the_tree->tree_data[$a]['index'] - $the_tree->tree_data[$b]['index']; - }); - break; - case 3: - uksort($group_by_data, function ($a,$b) { - global $SEM_CLASS,$SEM_TYPE; - return strnatcasecmp($SEM_TYPE[$a]['name'], $SEM_TYPE[$b]['name']) - ?: strnatcasecmp( - $SEM_CLASS[$SEM_TYPE[$a]['class']]['name'], - $SEM_CLASS[$SEM_TYPE[$b]["class"]]['name'] - ); - }); - break; - default: - uksort($group_by_data, 'strnatcasecmp'); - } - - return [$group_by_data, $sem_data]; - } - - /** - * Creates HTML code for a single course row. This has been extracted - * into a separate function as that makes handling and outputting - * course children easier. - * - * @param string $seminar_id a single course id to output - * @param mixed $sem_data collected data for all found courses - * @param bool $child call in "child mode" -> force output because here children are listed - * @return string A HTML table row. - */ - private function printCourseRow($seminar_id, &$sem_data, $child = false) - { - global $SEM_TYPE; - - $row = ''; - - /* - * As we include child courses now, we need an extra check for visibility. - * Child courses are not shown extra, but summarized under their parent if - * the parent is part of the search result. - */ - if (($GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM) - || key($sem_data[$seminar_id]['visible']) == 1) - && (empty($sem_data[key($sem_data[$seminar_id]['parent_course'])]) - || $child)) { - // create instance of seminar-object - $seminar_obj = new Seminar($seminar_id); - // is this sem a studygroup? - $studygroup_mode = SeminarCategories::GetByTypeId($seminar_obj->getStatus())->studygroup_mode; - - $sem_name = $seminar_obj->getFullName('type-name'); - $seminar_number = key($sem_data[$seminar_id]['VeranstaltungsNummer']); - - $visibleChildren = []; - - $row .= 'admission_prelim) $sem_name .= ', ' . _('Zutritt auf Anfrage'); - $sem_name .= ')'; - $row .= ''; - $row .= StudygroupAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $seminar_obj->getName()]); - $row .= ''; - } else { - $sem_number_start = key($sem_data[$seminar_id]['sem_number']); - $sem_number_end = key($sem_data[$seminar_id]['sem_number_end']); - if ($sem_number_start != $sem_number_end) { - $sem_name .= ' (' . $this->search_obj->sem_dates[$sem_number_start]['name'] . ' - '; - $sem_name .= (($sem_number_end == -1) - ? _('unbegrenzt') - : $this->search_obj->sem_dates[$sem_number_end]['name']) . ')'; - } elseif ($this->sem_browse_data['group_by']) { - $sem_name .= " (" . $this->search_obj->sem_dates[$sem_number_start]['name'] . ')'; - } - $row .= ''; - $row .= CourseAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $seminar_obj->getName()]); - $row .= ''; - - } - $send_from_search = URLHelper::getUrl(basename($_SERVER['PHP_SELF']), ['keep_result_set' => 1, 'cid' => null]); - $send_from_search_link = URLHelper::getLink($this->target_url, - [ - $this->target_id => $seminar_id, - 'cid' => null, - 'send_from_search' => 1, - 'send_from_search_page' => $send_from_search - ]); - $row .= ''; - - // Show the "more" icon only if there are visible children. - if (count($seminar_obj->children) > 0) { - - // If you are not root, perhaps not all available subcourses are visible. - $visibleChildren = $seminar_obj->children; - if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { - $visibleChildren = $visibleChildren->filter(function($c) { - return $c->visible; - }); - } - if (count($visibleChildren) > 0) { - $row .= Icon::create('add', Icon::ROLE_CLICKABLE ,[ - 'id' => 'show-subcourses-' . $seminar_id, - 'title' => sprintf(_('%u Unterveranstaltungen anzeigen'), count($visibleChildren)), - 'onclick' => "jQuery('tr.subcourses-" . $seminar_id . "').removeClass('hidden-js');jQuery(this).closest('tr').addClass('has-subcourses');jQuery(this).hide();jQuery('#hide-subcourses-" . $seminar_id . "').show();" - ])->asImg(12) . ' '; - $row .= Icon::create('remove', Icon::ROLE_CLICKABLE ,[ - 'id' => 'hide-subcourses-' . $seminar_id, - 'style' => 'display:none', - 'title' => sprintf(_('%u Unterveranstaltungen ausblenden'), count($visibleChildren)), - 'onclick' => "jQuery('tr.subcourses-" . $seminar_id . "').addClass('hidden-js'); jQuery(this).closest('tr').removeClass('has-subcourses');jQuery(this).hide();jQuery('#show-subcourses-" . $seminar_id . "').show();" - ])->asImg(12) . ' '; - } - } - - $row .= ''; - if (Config::get()->IMPORTANT_SEMNUMBER && $seminar_number) { - $row .= htmlReady($seminar_number) . " "; - } - $row .= htmlReady($sem_name) . '
'; - //create Turnus field - if ($studygroup_mode) { - $row .= '
' - . htmlReady(mb_substr($seminar_obj->description, 0, 100)) - . '
'; - } else { - $temp_turnus_string = $seminar_obj->getDatesExport( - [ - 'short' => true, - 'shrink' => true, - 'semester_id' => '' - ] - ); - //Shorten, if string too long (add link for details.php) - if (mb_strlen($temp_turnus_string) > 70) { - $temp_turnus_string = htmlReady(mb_substr($temp_turnus_string, 0, mb_strpos(mb_substr($temp_turnus_string, 70, mb_strlen($temp_turnus_string)), ',') + 71)); - $temp_turnus_string .= ' ... (' . _('mehr') . ')'; - } else { - $temp_turnus_string = htmlReady($temp_turnus_string); - } - if (!Config::get()->IMPORTANT_SEMNUMBER) { - $row .= '
' . htmlReady($seminar_number) . '
'; - } - $row .= '
' . $temp_turnus_string . '
'; - if (count($seminar_obj->children) > 0 && count($visibleChildren) > 0) { - $row .= '
'; - $row .= sprintf(_('%u Unterveranstaltungen'), count($visibleChildren)); - $row .= '
'; - } - } - $row .= ''; - $row .= '('; - $doz_name = []; - $c = 0; - reset($sem_data[$seminar_id]['fullname']); - foreach ($sem_data[$seminar_id]['username'] as $anzahl1) { - if ($c == 0) { - $d_name = key($sem_data[$seminar_id]['fullname']); - $anzahl2 = current($sem_data[$seminar_id]['fullname']); - next($sem_data[$seminar_id]['fullname']); - $c = $anzahl2 / $anzahl1; - $doz_name = array_merge($doz_name, array_fill(0, $c, $d_name)); - } - --$c; - } - $doz_uname = array_keys($sem_data[$seminar_id]['username']); - $doz_position = array_keys($sem_data[$seminar_id]['position']); - if (count($doz_name)) { - if (count($doz_position) != count($doz_uname)) { - $doz_position = range(1, count($doz_uname)); - } - array_multisort($doz_position, $doz_name, $doz_uname); - $i = 0; - foreach ($doz_name as $index => $value) { - if ($value) { // hide dozenten with empty username - if ($i == 4) { - $row .= '... (' . _('mehr') . ')'; - break; - } - $row .= '' . htmlReady($value) . ''; - if ($i != count($doz_name) - 1) { - $row .= ', '; - } - } - ++$i; - } - $row .= ')'; - if (Config::get()->COURSE_SEARCH_SHOW_ADMISSION_STATE) { - $row .= ''; - switch (self::getStatusCourseAdmission($seminar_id, - $seminar_obj->admission_prelim)) { - case 1: - $row .= Icon::create( - 'info-circle', - Icon::ROLE_STATUS_YELLOW, - tooltip2(_('Eingeschränkter Zugang')) - ); - break; - case 2: - $row .= Icon::create( - 'decline-circle', - Icon::ROLE_STATUS_RED, - tooltip2(_('Kein Zugang')) - ); - break; - default: - $row .= Icon::create( - 'check-circle', - Icon::ROLE_STATUS_GREEN, - tooltip2(_('Uneingeschränkter Zugang')) - ); - } - $row .= ''; - } - $row .= ''; - } - - // Process children. - foreach ($seminar_obj->children as $child) { - $row .= $this->printCourseRow($child->id, $sem_data, true); - } - - } - - return $row; - } - - - /** - * Returns a new navigation object corresponding to the given target and - * name of the option. The target has two possibel values "sidebar" and - * "course" and indicates the place where the navigation is shown. - * The option name is the key of an entry in the array with the navigation - * options. - * - * The navigation options are configured in the global configuration as an - * array. For further details see documentation of entry - * COURSE_SEARCH_NAVIGATION_OPTIONS in global configuration. - * - * This is an example with all possible options: - * - * { - * // "courses", "semtree" and "rangetree" are the "old" search options. - * // The link text is fixed. - * "courses":{ - * "visible":true, - * // The target indicates where the link to this search option is - * // placed. Possible values are "sidebar" for a link in the sidebar - * // or "courses" to show a link (maybe with picture) below the - * // "course search". - * "target":"sidebar" - * }, - * "semtree":{ - * "visible":true, - * "target":"sidebar" - * }, - * "rangetree":{ - * "visible":false, - * "target":"sidebar" - * }, - * // New option to acivate the search for modules and the systematic - * // search in studycourses, field of study and degrees. - * "module":{ - * "visible":true, - * "target":"sidebar" - * }, - * // This option shows a direct link in the sidebar to an entry (level) - * // in the range tree. The link text is the name of the level. - * "fb3_hist":{ - * "visible":true, - * "target":"sidebar", - * "range_tree_id":"d1a07cf0c8057c664279214cc070b580" - * }, - * // The same for an entry in the sem tree. - * "grundstudium":{ - * "visible":true, - * "target":"sidebar", - * "sem_tree_id":"e1a07cf0c8057c664279214cc070b580" - * }, - * // This shows a link in the sidebar to the course search. The text is - * // availlable in two languages. - * "vvz":{ - * "visible":true, - * "target":"sidebar", - * "url":"dispatch.php/search/courses?level=f&option=vav", - * "title":{ - * "de_DE":"Veranstaltungsverzeichnis", - * "en_GB":"Course Catalogue" - * } - * }, - * // This option uses an url with search option and shows a link in the - * // sidebar to an entry in the range tree with all courses. - * "test":{ - * "visible":true, - * "target":"sidebar", - * "url":"dispatch.php/search/courses?start_item_id=d1a07cf0c8057c664279214cc070b580&cmd=show_sem_range_tree&item_id=d1a07cf0c8057c664279214cc070b580_withkids&level=ev", - * "title":{ - * "de_DE":"Historisches Institut", - * "en_GB":"Historical Institute" - * } - * }, - * // This option shows a link to the sem tree with picture below the - * // course search (target: courses). - * // This is the behaviour of Stud.IP < 4.2. - * "csemtree":{ - * "visible":true, - * "target":"courses", - * "url":"dispatch.php/search/courses?level=vv", - * "img":{ - * "filename":"directory-search.png", - * "attributes":{ - * "size":"260@100" - * } - * }, - * "title":{ - * "de_DE":"Suche im Vorlesungsverzeichnis", - * "en_GB":"Search course directory" - * } - * }, - * // This option shows a link to the range tree with picture below the - * // course search (target: courses). - * // This is the behaviour of Stud.IP < 4.2. - * "crangetree":{ - * "visible":true, - * "target":"courses", - * "url":"dispatch.php/search/courses?level=ev", - * "img":{ - * "filename":"institute-search.png", - * "attributes":{ - * "size":"260@100" - * } - * }, - * "title":{ - * "de_DE":"Suche in Einrichtungen", - * "en_GB":"Search institutes" - * } - * } - * } - * - * - * @param string $target - * @param string $option_name - * @return Navigation|null - */ - public static function getSearchOptionNavigation($target, $option_name = null): ?Navigation - { - // return first visible search option - if (is_null($option_name)) { - $options = Config::get()->COURSE_SEARCH_NAVIGATION_OPTIONS; - foreach ($options as $name => $option) { - if ($option['visible'] && $option['target'] === $target) { - return self::getSearchOptionNavigation($target, $name); - } - } - return null; - } - - $installed_languages = array_keys(Config::get()->INSTALLED_LANGUAGES); - $language = $_SESSION['_language'] ?? reset($installed_languages); - $option = Config::get()->COURSE_SEARCH_NAVIGATION_OPTIONS[$option_name]; - if (!$option['visible'] || $option['target'] !== $target) { - return null; - } - if (empty($option['url'])) { - switch ($option_name) { - case 'courses': - case 'semtree': - return new Navigation(_('Vorlesungsverzeichnis'), - URLHelper::getURL('dispatch.php/search/courses', - [ - 'type' => 'semtree' - ], true)); - case 'rangetree': - return new Navigation(_('Einrichtungsverzeichnis'), - URLHelper::getURL('dispatch.php/search/courses', - [ - 'type' => 'rangetree' - ], true)); - case 'module': - return new MVVSearchNavigation(_('Modulverzeichnis'), - URLHelper::getURL('dispatch.php/search/module'),null, true); - } - } else { - return new Navigation($option['title'][$language], - URLHelper::getURL($option['url'], ['option' => $option_name], true)); - } - if (!empty($option['sem_tree_id'])) { - $study_area = StudipStudyArea::find($option['sem_tree_id']); - return new Navigation($study_area->name, - URLHelper::getURL('dispatch.php/search/courses', - [ - 'start_item_id' => $option['sem_tree_id'], - 'path_id' => $option['sem_tree_id'], - 'cmd' => 'show_sem_range', - 'item_id' => $option['sem_tree_id'] . '_withkids', - 'level' => 'vv', - 'option' => $option_name - ], true)); - } - if (!empty($option['range_tree_id'])) { - $item_name = DBManager::get()->fetchColumn(' - SELECT `name` - FROM `range_tree` - WHERE item_id = ?', - [$option['range_tree_id']]); - return new Navigation($item_name, - URLHelper::getURL('dispatch.php/search/courses', - [ - 'start_item_id' => $option['range_tree_id'], - 'path_id' => $option['range_tree_id'], - 'cmd' => 'show_sem_range_tree', - 'item_id' => $option['range_tree_id'] . '_withkids', - 'level' => 'ev', - 'option' => $option_name - ], true)); - } - - return null; - } - - /** - * The class SemBrowse uses a vast number of variables stored in the - * session. This function sets the default values or transfers some - * of them to url parameters if a filter in the sidebar has been changed. - * - * @see SemBrowse::setClassesSelector() - * @see SemBrowse::setSemesterSelector() - */ - public static function transferSessionData() - { - if (empty($_SESSION['sem_browse_data']) || Request::option('reset_all')) { - $_SESSION['sem_browse_data'] = []; - } - - $_SESSION['sem_browse_data']['qs_choose'] = Request::get('search_sem_qs_choose', - $_SESSION['sem_browse_data']['qs_choose'] ?? null); - - // simulate button clicked if semester was changed - $old_default_sem = $_SESSION['sem_browse_data']['default_sem'] ?? null; - if (Request::option('search_sem_sem', $old_default_sem) != $old_default_sem) { - $_SESSION['sem_browse_data']['default_sem'] = Request::option('search_sem_sem'); - if ($_SESSION['sem_browse_data']['sset']) { - Request::set('search_sem_quick_search_parameter', $_SESSION['sem_browse_data']['sset']); - Request::set('search_sem_quick_search', $_SESSION['sem_browse_data']['sset']); - Request::set('search_sem_qs_choose', $_SESSION['sem_browse_data']['qs_choose']); - Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); - Request::set('search_sem_do_search', '1'); - Request::set('search_sem_' . md5('is_sended'), '1'); - } else { - Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); - Request::set('search_sem_sem_change', '1'); - Request::set('search_sem_sem_select', '1'); - } - } - - // simulate button clicked if class was changed - $old_show_class = $_SESSION['sem_browse_data']['show_class'] ?? null; - if (Request::option('show_class', $old_show_class) != $old_show_class) { - $_SESSION['sem_browse_data']['show_class'] = Request::option('show_class'); - - if ($_SESSION['sem_browse_data']['show_class'] - && $_SESSION['sem_browse_data']['show_class'] != 'all') { - $class = $GLOBALS['SEM_CLASS'][$_SESSION['sem_browse_data']['show_class']]; - $_SESSION['sem_browse_data']['sem_status'] = array_keys($class->getSemTypes()); - } else { - $_SESSION['sem_browse_data']['sem_status'] = false; - } - - if ($_SESSION['sem_browse_data']['sset']) { - Request::set('search_sem_quick_search_parameter', $_SESSION['sem_browse_data']['sset']); - Request::set('search_sem_quick_search', $_SESSION['sem_browse_data']['sset']); - Request::set('search_sem_qs_choose', $_SESSION['sem_browse_data']['qs_choose']); - Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); - Request::set('search_sem_do_search', '1'); - Request::set('search_sem_' . md5('is_sended'), '1'); - } else { - Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); - Request::set('search_sem_sem_change', '1'); - Request::set('search_sem_sem_select', '1'); - } - } - - // set default values - if (empty($_SESSION['sem_browse_data']['default_sem'])) { - $_SESSION['sem_browse_data']['default_sem'] = - Semester::getIndexById(self::getDefaultSemester(), true, true) - ?: 'all'; - } - $_SESSION['sem_browse_data']['show_class'] = - $_SESSION['sem_browse_data']['show_class'] ?? 'all'; - $_SESSION['sem_browse_data']['group_by'] = - $_SESSION['sem_browse_data']['group_by'] ?? '0'; - } - - /** - * Retrieves the default semester from session or calculate it considering - * the value from SEMESTER_TIME_SWITCH. - * - * @return Semester The semester object of the default semester. - */ - public static function getDefaultSemester() - { - $default_sem = $_SESSION['_default_sem']; - if (!$default_sem) { - $current_sem = Semester::findDefault(); - $default_sem = $current_sem->id; - } - - return $default_sem; - } - - /** - * Adds a widget to the sidebar to select a course class. The result set is - * filtered by this class. - * - * @param string $submit_url The submit url. - */ - public static function setClassesSelector($submit_url) - { - $classes_filter = new SelectWidget(_('Veranstaltungsklassen'), - $submit_url, 'show_class'); - $classes_filter->addElement(new SelectElement('all', _('Alle'), - ($_SESSION['sem_browse_data']['show_class'] ?: 'all') === 'all')); - foreach ($GLOBALS['SEM_CLASS'] as $key => $val) { - $classes_filter->addElement(new SelectElement($key, $val['name'], - ($_SESSION['sem_browse_data']['show_class'] == $key))); - } - Sidebar::Get()->addWidget($classes_filter); - } - - /** - * Adds a widget to the sidebar to select a semester. The result set is - * filtered by this semester. - * - * @param string $submit_url The submit url. - */ - public static function setSemesterSelector($submit_url) - { - $semesters = Semester::findAllVisible(); - $sidebar = Sidebar::Get(); - $list = new SelectWidget(_('Semester'), - $submit_url, 'search_sem_sem'); - $list->addElement(new SelectElement('all', _('Alle'), - ($_SESSION['sem_browse_data']['default_sem']) === 'all')); - foreach(array_reverse($semesters, true) as $i => $semester_data) { - $list->addElement(new SelectElement( - $i, - $semester_data['name'], - ($_SESSION['sem_browse_data']['default_sem'] !== 'all' - && intval($_SESSION['sem_browse_data']['default_sem']) === $i) - )); - } - $sidebar->addWidget($list, 'filter_semester'); - } - - /** - * Returns the admission status for a course. - * - * @param string $seminar_id Id of the course - * @param bool $prelim State of preliminary setting - * @return int - */ - public static function getStatusCourseAdmission($seminar_id, $prelim) - { - $sql = "SELECT COUNT(`type`) AS `types`, - SUM(IF(`type` = 'LockedAdmission', 1, 0)) AS `type_locked` - FROM `seminar_courseset` - INNER JOIN `courseset_rule` USING (`set_id`) - WHERE `seminar_id` = ? - GROUP BY `set_id`"; - - $stmt = DBManager::get()->prepare($sql); - $stmt->execute([$seminar_id]); - $result = $stmt->fetch(); - - if (!empty($result['types'])) { - if ($result['type_locked']) { - return 2; - } - return 1; - } - - if ($prelim) { - return 1; - } - return 0; - } - - /** - * Returns a Quick Search form to put inside a WidgetElement. - * - * @param string $level The Level of search , expected ('f', 'vv', 'ev') - * @return string $search_form_content Contains a form element with a quick search input, predefined (hidden) inputs and search button - */ - public function getQuickSearchForm() - { - if ($this->sem_browse_data['level'] === 'vv') { - $this->search_obj->sem_tree =& $this->sem_tree->tree; - if ($this->sem_tree->start_item_id !== 'root') { - $this->search_obj->search_scopes[] = $this->sem_tree->start_item_id; - } - } elseif ($this->sem_browse_data['level'] === 'ev') { - $this->search_obj->range_tree =& $this->range_tree->tree; - if ($this->range_tree->start_item_id !== 'root'){ - $this->search_obj->search_ranges[] = $this->range_tree->start_item_id; - } - } - - $search_form_content = $this->search_obj->getFormStart(URLHelper::getLink(), ['class' => '']); - $quicksearch = $this->getQuicksearch(); - $quicksearch->setInputStyle('height:22px;width: 100%;'); - $quicksearch->withButton(['search_button_name'=> 'course_search_button']); - $quicksearch->disableAutocomplete(); - $search_form_content .= $quicksearch->render(); - $search_form_content .= $this->search_obj->getHiddenField('qs_choose','title_lecturer_number'); - - if($this->sem_browse_data['level'] == 'vv') - $search_form_content .= $this->search_obj->getHiddenField('scope_choose', $this->sem_tree->start_item_id); - - if($this->sem_browse_data['level'] == 'ev') - $search_form_content .= $this->search_obj->getHiddenField('range_choose', $this->range_tree->start_item_id); - - $search_form_content .= $this->search_obj->getHiddenField('level', $this->sem_browse_data['level']); - $search_form_content .= $this->search_obj->getHiddenField('sem', htmlReady($_SESSION['sem_browse_data']['default_sem'])); - - $search_form_content .= $this->search_obj->getFormEnd(); - return $search_form_content; - } -} diff --git a/lib/classes/SemBrowse.php b/lib/classes/SemBrowse.php new file mode 100644 index 0000000..536b7e3 --- /dev/null +++ b/lib/classes/SemBrowse.php @@ -0,0 +1,1264 @@ +group_by_fields = + [ + [ + 'name' => _('Semester'), + 'group_field' => 'sem_number' + ], + [ + 'name' => _('Bereich'), + 'group_field' => 'bereich' + ], + [ + 'name' => _('Lehrende'), + 'group_field' => 'fullname', + 'unique_field' => 'username' + ], + [ + 'name' => _('Typ'), + 'group_field' => 'status' + ], + [ + 'name' => _('Einrichtung'), + 'group_field' => 'Institut', + 'unique_field' => 'Institut_id' + ] + ]; + + if (empty($_SESSION['sem_browse_data'])) { + $_SESSION['sem_browse_data'] = $sem_browse_data_init; + } + $this->sem_browse_data =& $_SESSION['sem_browse_data']; + + $level_change = Request::option('start_item_id') || Request::submitted('search_sem_sem_change'); + + for ($i = 0; $i < count($this->persistent_fields); ++$i){ + $persistend_field = $this->persistent_fields[$i]; + if (Request::get($persistend_field) != null) { + $this->sem_browse_data[$persistend_field] = Request::option($persistend_field); + } + } + $this->search_obj = new StudipSemSearch( + 'search_sem', + false, + !(is_object($GLOBALS['perm']) && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)), + $this->sem_browse_data['show_class'] ?? null + ); + + + if (Request::get($this->search_obj->form_name . '_scope_choose')) { + $this->sem_browse_data['start_item_id'] = + Request::option($this->search_obj->form_name . '_scope_choose'); + } + if (Request::get($this->search_obj->form_name . '_range_choose')) { + $this->sem_browse_data['start_item_id'] = + Request::option($this->search_obj->form_name . '_range_choose'); + } + if (Request::get($this->search_obj->form_name . '_sem')) { + $this->sem_browse_data['default_sem'] = + Request::option($this->search_obj->form_name . '_sem'); + } + + if ( + Request::get('keep_result_set') + || !empty($this->sem_browse_data['sset']) + || (!empty($this->sem_browse_data['search_result']) && !empty($this->sem_browse_data['show_entries'])) + ) { + $this->show_result = true; + } + + if (isset($this->sem_browse_data['cmd']) && $this->sem_browse_data['cmd'] === 'xts') { + if ($this->search_obj->new_search_button_clicked) { + $this->show_result = false; + $this->sem_browse_data['sset'] = false; + $this->sem_browse_data['search_result'] = []; + } + } + + if (!isset($this->sem_browse_data['default_sem'])) { + $this->sem_number[0] = 0; + } elseif ($this->sem_browse_data['default_sem'] != 'all') { + $this->sem_number[0] = intval($this->sem_browse_data['default_sem']); + } else { + $this->sem_number = false; + } + + $sem_status = (!empty($this->sem_browse_data['sem_status']) && is_array($this->sem_browse_data['sem_status'])) ? $this->sem_browse_data['sem_status'] : false; + + if ($this->sem_browse_data['level'] == 'vv') { + if (empty($this->sem_browse_data['start_item_id'])) { + $this->sem_browse_data['start_item_id'] = 'root'; + } + $this->sem_tree = new StudipSemTreeViewSimple( + $this->sem_browse_data['start_item_id'], + $this->sem_number, $sem_status, + !(is_object($GLOBALS['perm']) + && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM))); + if (Request::option('cmd') != 'show_sem_range' + && $level_change + && !$this->search_obj->search_button_clicked ) { + $this->get_sem_range($this->sem_browse_data['start_item_id'], false); + $this->show_result = true; + $this->sem_browse_data['show_entries'] = 'level'; + $this->sem_browse_data['sset'] = false; + } + if ($this->search_obj->sem_change_button_clicked) { + $this->get_sem_range($this->sem_browse_data['start_item_id'], + ($this->sem_browse_data['show_entries'] == 'sublevels')); + $this->show_result = true; + } + } + + if ($this->sem_browse_data['level'] == 'ev'){ + if (!$this->sem_browse_data['start_item_id']) { + $this->sem_browse_data['start_item_id'] = 'root'; + } + $this->range_tree = new StudipSemRangeTreeViewSimple( + $this->sem_browse_data['start_item_id'], + $this->sem_number, + $sem_status, + !(is_object($GLOBALS['perm']) + && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM))); + if (Request::option('cmd') != 'show_sem_range_tree' + && $level_change + && !$this->search_obj->search_button_clicked ) { + $this->get_sem_range_tree($this->sem_browse_data['start_item_id'], false); + $this->show_result = true; + $this->sem_browse_data['show_entries'] = 'level'; + $this->sem_browse_data['sset'] = false; + } + if ($this->search_obj->sem_change_button_clicked) { + $this->get_sem_range_tree($this->sem_browse_data['start_item_id'], + ($this->sem_browse_data['show_entries'] == 'sublevels')); + $this->show_result = true; + } + } + + if ($this->search_obj->search_button_clicked + && !$this->search_obj->new_search_button_clicked) { + $this->search_obj->override_sem = $this->sem_number; + $this->search_obj->doSearch(); + if ($this->search_obj->found_rows) { + $this->sem_browse_data['search_result'] = array_flip($this->search_obj->search_result->getRows('seminar_id')); + } else { + $this->sem_browse_data['search_result'] = []; + } + $this->show_result = true; + $this->sem_browse_data['show_entries'] = false; + $this->sem_browse_data['sset'] = Request::get($this->search_obj->form_name . "_quick_search_parameter"); + } + + + if (Request::option('cmd') == 'show_sem_range') { + $tmp = explode('_', Request::option('item_id')); + $this->get_sem_range($tmp[0], isset($tmp[1])); + $this->show_result = true; + $this->sem_browse_data['show_entries'] = (isset($tmp[1])) ? 'sublevels' : 'level'; + $this->sem_browse_data['sset'] = false; + } + + if (Request::option('cmd') == 'show_sem_range_tree') { + $tmp = explode('_', Request::option('item_id')); + $this->get_sem_range_tree($tmp[0],isset($tmp[1])); + $this->show_result = true; + $this->sem_browse_data['show_entries'] = (isset($tmp[1])) ? 'sublevels' : 'level'; + $this->sem_browse_data['sset'] = false; + } + + if (Request::option('do_show_class') + && count($this->sem_browse_data['sem_status'])) { + $this->get_sem_class(); + } + + } + + /** + * Returns whether the search for modules has to be displayed. + * + * @return boolean True if search for modules has to be displayed. + */ + private function showModules() + { + if ($this->sem_browse_data['show_class'] == 'all') { + return true; + } + if (!is_array($this->classes_show_module)) { + $this->classes_show_class = []; + foreach ($GLOBALS['SEM_CLASS'] as $sem_class_key => $sem_class){ + if ($sem_class['module']) { + $this->classes_show_module[] = $sem_class_key; + } + } + } + return in_array($this->sem_browse_data['show_class'], $this->classes_show_class); + } + + public function show_class() + { + if ($this->sem_browse_data['show_class'] == 'all') { + return true; + } + if (!is_array($this->classes_show_class)) { + $this->classes_show_class = []; + foreach ($GLOBALS['SEM_CLASS'] as $sem_class_key => $sem_class) { + if ($sem_class['bereiche']) { + $this->classes_show_class[] = $sem_class_key; + } + } + } + return in_array($this->sem_browse_data['show_class'], $this->classes_show_class); + } + + public function get_sem_class() + { + $query = "SELECT `Seminar_id` + FROM `seminare` + WHERE `status` IN (?)"; + + $show_all = is_object($GLOBALS['perm']) + && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM); + if (!$show_all) { + $query .= ' AND visible = 1'; + } + + $sem_ids = DBManager::get()->fetchFirst($query); + if (is_array($sem_ids)) { + $this->sem_browse_data['search_result'] = array_flip($sem_ids); + } + $this->sem_browse_data['sset'] = true; + $this->show_result = true; + } + + public function get_sem_range($item_id, $with_kids) + { + if (!is_object($this->sem_tree)) { + $sem_status = (is_array($this->sem_browse_data['sem_status'])) ? $this->sem_browse_data['sem_status'] : false; + $this->sem_tree = new StudipSemTreeViewSimple( + $this->sem_browse_data['start_item_id'], + $this->sem_number, + $sem_status, + !(is_object($GLOBALS['perm']) + && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM))); + } + $sem_ids = $this->sem_tree->tree->getSemIds($item_id,$with_kids); + if (is_array($sem_ids)) { + $this->sem_browse_data['search_result'] = array_flip($sem_ids); + } else { + $this->sem_browse_data['search_result'] = []; + } + } + + public function get_sem_range_tree($item_id, $with_kids) + { + $range_object = RangeTreeObject::GetInstance($item_id); + if ($with_kids) { + $inst_ids = $range_object->getAllObjectKids(); + } + $inst_ids[] = $range_object->item_data['studip_object_id']; + $db_view = DbView::getView('sem_tree'); + $db_view->params[0] = $inst_ids; + $db_view->params[1] = (is_object($GLOBALS['perm']) && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) ? '' : ' AND c.visible=1'; + $db_view->params[1] .= !empty($this->sem_browse_data['sem_status']) && is_array($this->sem_browse_data['sem_status']) + ? " AND c.status IN('" . join("','", $this->sem_browse_data['sem_status']) ."')" + : ''; + $db_view->params[2] = is_array($this->sem_number) + ? ' HAVING sem_number IN (' + . join(',', $this->sem_number) + . ') OR (sem_number <= ' + . $this->sem_number[count($this->sem_number) - 1] + . ' AND (sem_number_end >= ' + . $this->sem_number[count($this->sem_number) - 1] + . ' OR sem_number_end = -1)) ' + : ''; + $db_snap = new DbSnapshot($db_view->get_query('view:SEM_INST_GET_SEM')); + if ($db_snap->numRows) { + $sem_ids = $db_snap->getRows('Seminar_id'); + $this->sem_browse_data['search_result'] = array_flip($sem_ids); + } else { + $this->sem_browse_data['search_result'] = []; + } + } + + /** + * Prints the quicksearch form. + */ + private function printQuickSearch() + { + if ($this->sem_browse_data['level'] === 'vv') { + $this->search_obj->sem_tree =& $this->sem_tree->tree; + if ($this->sem_tree->start_item_id !== 'root') { + $this->search_obj->search_scopes[] = $this->sem_tree->start_item_id; + } + } elseif ($this->sem_browse_data['level'] === 'ev') { + $this->search_obj->range_tree =& $this->range_tree->tree; + if ($this->range_tree->start_item_id !== 'root'){ + $this->search_obj->search_ranges[] = $this->range_tree->start_item_id; + } + } + + $template = $GLOBALS['template_factory']->open('sembrowse/quick-search.php'); + $template->search_obj = $this->search_obj; + $template->sem_browse_data = $this->sem_browse_data; + $template->sem_tree = $this->sem_tree; + $template->range_tree = $this->range_tree; + $template->quicksearch = $this->getQuicksearch(); + + echo $template->render(); + } + + private function getQuicksearch() + { + $quicksearch = QuickSearch::get( + $this->search_obj->form_name . '_quick_search', + new SeminarSearch() + ); + + $quicksearch->setAttributes([ + 'aria-label' => _('Suchbegriff'), + 'autofocus' => '', + ]); + $quicksearch->fireJSFunctionOnSelect('selectSem'); + $quicksearch->noSelectbox(); + $quicksearch->defaultValue( + $this->sem_browse_data['sset'] ?: '', + $this->sem_browse_data['sset'] ?: '' + ); + + return $quicksearch; + } + + private function printExtendedSearch() + { + $template = $GLOBALS['template_factory']->open('sembrowse/extended-search.php'); + $template->search_obj = $this->search_obj; + $template->sem_browse_data = $this->sem_browse_data; + $template->show_class = $this->show_class(); + echo $template->render(); + } + + public function do_output() + { + if ($this->sem_browse_data['cmd'] == 'xts') { + $this->printExtendedSearch(); + } + $path_id = Request::option('path_id'); + URLHelper::addLinkParam('path_id', $path_id); + $this->print_level($path_id); + } + + public function print_level($start_id = null) + { + ob_start(); + + echo "\n" . '' . "\n"; + if ($this->sem_browse_data['level'] == 'vv') { + echo "\n" . ''; + ob_end_flush(); + } + + public function print_result() + { + ob_start(); + global $SEM_TYPE, $SEM_CLASS; + + if (is_array($this->sem_browse_data['search_result']) + && count($this->sem_browse_data['search_result'])) { + if (!is_object($this->sem_tree)) { + $this->sem_tree = new StudipSemTreeViewSimple( + $this->sem_browse_data['start_item_id'] ?? null, + $this->sem_number, + !empty($this->sem_browse_data['sem_status']) && is_array($this->sem_browse_data['sem_status']) + ? $this->sem_browse_data['sem_status'] : false, + !(is_object($GLOBALS['perm']) && $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) + ); + } + $the_tree = $this->sem_tree->tree; + + list($group_by_data, $sem_data) = $this->get_result(); + + $visibles = $sem_data; + if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { + $visibles = array_filter($visibles, function ($c) { + return key($c['visible']) == 1; + }); + } + + echo ''; + foreach ($group_by_data as $group_field => $sem_ids) { + if (Config::get()->COURSE_SEARCH_SHOW_ADMISSION_STATE) { + echo ''; + ob_end_flush(); + ob_start(); + if (is_array($sem_ids['Seminar_id'])) { + foreach(array_keys($sem_ids['Seminar_id']) as $seminar_id){ + echo $this->printCourseRow($seminar_id, $sem_data); + } + } + } + echo '
'; + } else { + echo '
'; + } + switch ($this->sem_browse_data['group_by'] ?? null) { + case 0: + echo htmlReady($this->search_obj->sem_dates[$group_field]['name'] ?? ''); + break; + case 1: + if ($the_tree->tree_data[$group_field]) { + echo htmlReady($the_tree->getShortPath($group_field)); + if (is_object($this->sem_tree)){ + echo $this->sem_tree->getInfoIcon($group_field); + } + } else { + echo _('keine Studienbereiche eingetragen'); + } + break; + case 3: + echo htmlReady($SEM_TYPE[$group_field]['name'] + . ' (' + . $SEM_CLASS[$SEM_TYPE[$group_field]['class']]['name'] + . ')'); + break; + default: + echo htmlReady($group_field); + } + echo '
'; + } elseif ($this->search_obj->search_button_clicked + && !$this->search_obj->new_search_button_clicked) { + $details = []; + if ($this->search_obj->found_rows === false) { + $details = [_('Der Suchbegriff fehlt oder ist zu kurz')]; + } + if ($details) { + PageLayout::postError(_('Ihre Suche ergab keine Treffer'), $details); + } else { + PageLayout::postInfo(_('Ihre Suche ergab keine Treffer')); + } + $this->sem_browse_data['sset'] = 0; + } + ob_end_flush(); + } + + public function get_result() + { + global $_fullname_sql, $user; + if ($this->sem_browse_data['group_by'] == 1) { + if (!is_object($this->sem_tree)) { + $the_tree = TreeAbstract::GetInstance('StudipSemTree', false); + } else { + $the_tree = $this->sem_tree->tree; + } + $sem_tree_query = ''; + if ($this->sem_browse_data['start_item_id'] != 'root' + && ($this->sem_browse_data['level'] == 'vv' + || $this->sem_browse_data['level'] == 'sbb')) { + $allowed_ranges = $the_tree->getKidsKids($this->sem_browse_data['start_item_id']); + $allowed_ranges[] = $this->sem_browse_data['start_item_id']; + $sem_tree_query = " AND sem_tree_id IN('" . join("','", $allowed_ranges) . "') "; + } + $add_fields = 'seminar_sem_tree.sem_tree_id AS bereich,'; + $add_query = "LEFT JOIN seminar_sem_tree ON (seminare.Seminar_id = seminar_sem_tree.seminar_id $sem_tree_query)"; + } else if ($this->sem_browse_data['group_by'] == 4){ + $add_fields = 'Institute.Name AS Institut,Institute.Institut_id,'; + $add_query = 'LEFT JOIN seminar_inst + ON (seminare.Seminar_id = seminar_inst.Seminar_id) + LEFT JOIN Institute + ON (Institute.Institut_id = seminar_inst.institut_id)'; + } else { + $add_fields = ''; + $add_query = ''; + } + + $dbv = DbView::getView('sem_tree'); + + $query = " + SELECT seminare.Seminar_id, VeranstaltungsNummer, seminare.status, + IF(seminare.visible = 0, CONCAT(seminare.Name, ' " . _('(versteckt)') + . "'), seminare.Name) AS Name," + . $add_fields + . $_fullname_sql['full'] . " AS fullname, + auth_user_md5.username," + . $dbv->sem_number_sql . ' AS sem_number, ' + . $dbv->sem_number_end_sql . ' AS sem_number_end, + seminar_user.position AS position, seminare.parent_course, seminare.visible + FROM seminare + LEFT JOIN seminar_user + ON (seminare.Seminar_id=seminar_user.Seminar_id AND seminar_user.status = ' . "'dozent'" . ') + LEFT JOIN auth_user_md5 + USING (user_id) + LEFT JOIN user_info + USING (user_id) ' + . $add_query . " + WHERE (seminare.Seminar_id IN('" . join("','", array_keys($this->sem_browse_data['search_result'])) . "') + OR seminare.parent_course IN ('" . join("','", array_keys($this->sem_browse_data['search_result'])) . "'))"; + + // don't show Studiengruppen if user not logged in + if (!$GLOBALS['user'] || $GLOBALS['user']->id == 'nobody') { + $studygroup_types = DBManager::get()->quote(studygroup_sem_types()); + $query .= " AND seminare.status NOT IN ({$studygroup_types})"; + } + + $db = new DB_Seminar($query); + $snap = new DbSnapshot($db); + $group_field = $this->group_by_fields[$this->sem_browse_data['group_by']]['group_field']; + $data_fields[0] = 'Seminar_id'; + if (!empty($this->group_by_fields[$this->sem_browse_data['group_by']]['unique_field'])) { + $data_fields[1] = $this->group_by_fields[$this->sem_browse_data['group_by']]['unique_field']; + } + if($user->id == 'nobody' && $snap->numRows == 0){ + $group_by_data = $sem_data = []; + }else{ + $group_by_data = $snap->getGroupedResult($group_field, $data_fields); + $sem_data = $snap->getGroupedResult('Seminar_id'); + } + + if ($this->sem_browse_data['group_by'] == 0) { + if($user->id == 'nobody' && $snap->numRows == 0){ + $group_by_duration = []; + }else{ + $group_by_duration = $snap->getGroupedResult('sem_number_end', ['sem_number', 'Seminar_id']); + } + foreach ($group_by_duration as $sem_number_end => $detail) { + if ($sem_number_end != -1 + && ($detail['sem_number'][$sem_number_end] + && count($detail['sem_number']) == 1)) { + continue; + } + + $current_semester_index = Semester::getIndexById(Semester::findCurrent()->semester_id, true, true); + foreach (array_keys($detail['Seminar_id']) as $seminar_id) { + $start_sem = key($sem_data[$seminar_id]['sem_number']); + if ($sem_number_end == -1) { + if ($this->sem_number === false) { + $sem_number_end = $current_semester_index && isset($this->search_obj->sem_dates[$current_semester_index + 1]) ? $current_semester_index + 1 : count($this->search_obj->sem_dates) -1; + } else { + $sem_number_end = $this->sem_number[0]; + } + } + for ($i = $start_sem; $i <= $sem_number_end; ++$i) { + if ($this->sem_number === false + || is_array($this->sem_number) + && in_array($i, $this->sem_number)) { + if (!empty($group_by_data[$i]) && empty($tmp_group_by_data[$i])) { + foreach (array_keys($group_by_data[$i]['Seminar_id']) as $id) { + $tmp_group_by_data[$i]['Seminar_id'][$id] = true; + } + } + $tmp_group_by_data[$i]['Seminar_id'][$seminar_id] = true; + } + } + } + } + if (!empty($tmp_group_by_data) && is_array($tmp_group_by_data)) { + if ($this->sem_number !== false) { + unset($group_by_data); + } + foreach ($tmp_group_by_data as $start_sem => $detail) { + $group_by_data[$start_sem] = $detail; + } + } + } + + //release memory + unset($snap); + unset($tmp_group_by_data); + + foreach ($group_by_data as $group_field => $sem_ids) { + foreach ($sem_ids['Seminar_id'] as $seminar_id => $foo) { + $name = mb_strtolower(key($sem_data[$seminar_id]['Name'])); + $name = str_replace(['ä', 'ö', 'ü'], ['ae', 'oe', 'ue'], $name); + if (Config::get()->IMPORTANT_SEMNUMBER && key($sem_data[$seminar_id]['VeranstaltungsNummer'])) { + $name = key($sem_data[$seminar_id]['VeranstaltungsNummer']) . ' ' . $name; + } + $group_by_data[$group_field]['Seminar_id'][$seminar_id] = $name; + } + uasort($group_by_data[$group_field]['Seminar_id'], 'strnatcmp'); + } + + switch ($this->sem_browse_data['group_by']) { + case 0: + krsort($group_by_data, SORT_NUMERIC); + break; + case 1: + uksort($group_by_data, function($a,$b) { + $the_tree = TreeAbstract::GetInstance('StudipSemTree', false); + $the_tree->buildIndex(); + return $the_tree->tree_data[$a]['index'] - $the_tree->tree_data[$b]['index']; + }); + break; + case 3: + uksort($group_by_data, function ($a,$b) { + global $SEM_CLASS,$SEM_TYPE; + return strnatcasecmp($SEM_TYPE[$a]['name'], $SEM_TYPE[$b]['name']) + ?: strnatcasecmp( + $SEM_CLASS[$SEM_TYPE[$a]['class']]['name'], + $SEM_CLASS[$SEM_TYPE[$b]["class"]]['name'] + ); + }); + break; + default: + uksort($group_by_data, 'strnatcasecmp'); + } + + return [$group_by_data, $sem_data]; + } + + /** + * Creates HTML code for a single course row. This has been extracted + * into a separate function as that makes handling and outputting + * course children easier. + * + * @param string $seminar_id a single course id to output + * @param mixed $sem_data collected data for all found courses + * @param bool $child call in "child mode" -> force output because here children are listed + * @return string A HTML table row. + */ + private function printCourseRow($seminar_id, &$sem_data, $child = false) + { + global $SEM_TYPE; + + $row = ''; + + /* + * As we include child courses now, we need an extra check for visibility. + * Child courses are not shown extra, but summarized under their parent if + * the parent is part of the search result. + */ + if (($GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM) + || key($sem_data[$seminar_id]['visible']) == 1) + && (empty($sem_data[key($sem_data[$seminar_id]['parent_course'])]) + || $child)) { + // create instance of seminar-object + $seminar_obj = new Seminar($seminar_id); + // is this sem a studygroup? + $studygroup_mode = SeminarCategories::GetByTypeId($seminar_obj->getStatus())->studygroup_mode; + + $sem_name = $seminar_obj->getFullName('type-name'); + $seminar_number = key($sem_data[$seminar_id]['VeranstaltungsNummer']); + + $visibleChildren = []; + + $row .= 'admission_prelim) $sem_name .= ', ' . _('Zutritt auf Anfrage'); + $sem_name .= ')'; + $row .= ''; + $row .= StudygroupAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $seminar_obj->getName()]); + $row .= ''; + } else { + $sem_number_start = key($sem_data[$seminar_id]['sem_number']); + $sem_number_end = key($sem_data[$seminar_id]['sem_number_end']); + if ($sem_number_start != $sem_number_end) { + $sem_name .= ' (' . $this->search_obj->sem_dates[$sem_number_start]['name'] . ' - '; + $sem_name .= (($sem_number_end == -1) + ? _('unbegrenzt') + : $this->search_obj->sem_dates[$sem_number_end]['name']) . ')'; + } elseif ($this->sem_browse_data['group_by']) { + $sem_name .= " (" . $this->search_obj->sem_dates[$sem_number_start]['name'] . ')'; + } + $row .= ''; + $row .= CourseAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $seminar_obj->getName()]); + $row .= ''; + + } + $send_from_search = URLHelper::getUrl(basename($_SERVER['PHP_SELF']), ['keep_result_set' => 1, 'cid' => null]); + $send_from_search_link = URLHelper::getLink($this->target_url, + [ + $this->target_id => $seminar_id, + 'cid' => null, + 'send_from_search' => 1, + 'send_from_search_page' => $send_from_search + ]); + $row .= ''; + + // Show the "more" icon only if there are visible children. + if (count($seminar_obj->children) > 0) { + + // If you are not root, perhaps not all available subcourses are visible. + $visibleChildren = $seminar_obj->children; + if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { + $visibleChildren = $visibleChildren->filter(function($c) { + return $c->visible; + }); + } + if (count($visibleChildren) > 0) { + $row .= Icon::create('add', Icon::ROLE_CLICKABLE ,[ + 'id' => 'show-subcourses-' . $seminar_id, + 'title' => sprintf(_('%u Unterveranstaltungen anzeigen'), count($visibleChildren)), + 'onclick' => "jQuery('tr.subcourses-" . $seminar_id . "').removeClass('hidden-js');jQuery(this).closest('tr').addClass('has-subcourses');jQuery(this).hide();jQuery('#hide-subcourses-" . $seminar_id . "').show();" + ])->asImg(12) . ' '; + $row .= Icon::create('remove', Icon::ROLE_CLICKABLE ,[ + 'id' => 'hide-subcourses-' . $seminar_id, + 'style' => 'display:none', + 'title' => sprintf(_('%u Unterveranstaltungen ausblenden'), count($visibleChildren)), + 'onclick' => "jQuery('tr.subcourses-" . $seminar_id . "').addClass('hidden-js'); jQuery(this).closest('tr').removeClass('has-subcourses');jQuery(this).hide();jQuery('#show-subcourses-" . $seminar_id . "').show();" + ])->asImg(12) . ' '; + } + } + + $row .= ''; + if (Config::get()->IMPORTANT_SEMNUMBER && $seminar_number) { + $row .= htmlReady($seminar_number) . " "; + } + $row .= htmlReady($sem_name) . '
'; + //create Turnus field + if ($studygroup_mode) { + $row .= '
' + . htmlReady(mb_substr($seminar_obj->description, 0, 100)) + . '
'; + } else { + $temp_turnus_string = $seminar_obj->getDatesExport( + [ + 'short' => true, + 'shrink' => true, + 'semester_id' => '' + ] + ); + //Shorten, if string too long (add link for details.php) + if (mb_strlen($temp_turnus_string) > 70) { + $temp_turnus_string = htmlReady(mb_substr($temp_turnus_string, 0, mb_strpos(mb_substr($temp_turnus_string, 70, mb_strlen($temp_turnus_string)), ',') + 71)); + $temp_turnus_string .= ' ... (' . _('mehr') . ')'; + } else { + $temp_turnus_string = htmlReady($temp_turnus_string); + } + if (!Config::get()->IMPORTANT_SEMNUMBER) { + $row .= '
' . htmlReady($seminar_number) . '
'; + } + $row .= '
' . $temp_turnus_string . '
'; + if (count($seminar_obj->children) > 0 && count($visibleChildren) > 0) { + $row .= '
'; + $row .= sprintf(_('%u Unterveranstaltungen'), count($visibleChildren)); + $row .= '
'; + } + } + $row .= ''; + $row .= '('; + $doz_name = []; + $c = 0; + reset($sem_data[$seminar_id]['fullname']); + foreach ($sem_data[$seminar_id]['username'] as $anzahl1) { + if ($c == 0) { + $d_name = key($sem_data[$seminar_id]['fullname']); + $anzahl2 = current($sem_data[$seminar_id]['fullname']); + next($sem_data[$seminar_id]['fullname']); + $c = $anzahl2 / $anzahl1; + $doz_name = array_merge($doz_name, array_fill(0, $c, $d_name)); + } + --$c; + } + $doz_uname = array_keys($sem_data[$seminar_id]['username']); + $doz_position = array_keys($sem_data[$seminar_id]['position']); + if (count($doz_name)) { + if (count($doz_position) != count($doz_uname)) { + $doz_position = range(1, count($doz_uname)); + } + array_multisort($doz_position, $doz_name, $doz_uname); + $i = 0; + foreach ($doz_name as $index => $value) { + if ($value) { // hide dozenten with empty username + if ($i == 4) { + $row .= '... (' . _('mehr') . ')'; + break; + } + $row .= '' . htmlReady($value) . ''; + if ($i != count($doz_name) - 1) { + $row .= ', '; + } + } + ++$i; + } + $row .= ')'; + if (Config::get()->COURSE_SEARCH_SHOW_ADMISSION_STATE) { + $row .= ''; + switch (self::getStatusCourseAdmission($seminar_id, + $seminar_obj->admission_prelim)) { + case 1: + $row .= Icon::create( + 'info-circle', + Icon::ROLE_STATUS_YELLOW, + tooltip2(_('Eingeschränkter Zugang')) + ); + break; + case 2: + $row .= Icon::create( + 'decline-circle', + Icon::ROLE_STATUS_RED, + tooltip2(_('Kein Zugang')) + ); + break; + default: + $row .= Icon::create( + 'check-circle', + Icon::ROLE_STATUS_GREEN, + tooltip2(_('Uneingeschränkter Zugang')) + ); + } + $row .= ''; + } + $row .= ''; + } + + // Process children. + foreach ($seminar_obj->children as $child) { + $row .= $this->printCourseRow($child->id, $sem_data, true); + } + + } + + return $row; + } + + + /** + * Returns a new navigation object corresponding to the given target and + * name of the option. The target has two possibel values "sidebar" and + * "course" and indicates the place where the navigation is shown. + * The option name is the key of an entry in the array with the navigation + * options. + * + * The navigation options are configured in the global configuration as an + * array. For further details see documentation of entry + * COURSE_SEARCH_NAVIGATION_OPTIONS in global configuration. + * + * This is an example with all possible options: + * + * { + * // "courses", "semtree" and "rangetree" are the "old" search options. + * // The link text is fixed. + * "courses":{ + * "visible":true, + * // The target indicates where the link to this search option is + * // placed. Possible values are "sidebar" for a link in the sidebar + * // or "courses" to show a link (maybe with picture) below the + * // "course search". + * "target":"sidebar" + * }, + * "semtree":{ + * "visible":true, + * "target":"sidebar" + * }, + * "rangetree":{ + * "visible":false, + * "target":"sidebar" + * }, + * // New option to acivate the search for modules and the systematic + * // search in studycourses, field of study and degrees. + * "module":{ + * "visible":true, + * "target":"sidebar" + * }, + * // This option shows a direct link in the sidebar to an entry (level) + * // in the range tree. The link text is the name of the level. + * "fb3_hist":{ + * "visible":true, + * "target":"sidebar", + * "range_tree_id":"d1a07cf0c8057c664279214cc070b580" + * }, + * // The same for an entry in the sem tree. + * "grundstudium":{ + * "visible":true, + * "target":"sidebar", + * "sem_tree_id":"e1a07cf0c8057c664279214cc070b580" + * }, + * // This shows a link in the sidebar to the course search. The text is + * // availlable in two languages. + * "vvz":{ + * "visible":true, + * "target":"sidebar", + * "url":"dispatch.php/search/courses?level=f&option=vav", + * "title":{ + * "de_DE":"Veranstaltungsverzeichnis", + * "en_GB":"Course Catalogue" + * } + * }, + * // This option uses an url with search option and shows a link in the + * // sidebar to an entry in the range tree with all courses. + * "test":{ + * "visible":true, + * "target":"sidebar", + * "url":"dispatch.php/search/courses?start_item_id=d1a07cf0c8057c664279214cc070b580&cmd=show_sem_range_tree&item_id=d1a07cf0c8057c664279214cc070b580_withkids&level=ev", + * "title":{ + * "de_DE":"Historisches Institut", + * "en_GB":"Historical Institute" + * } + * }, + * // This option shows a link to the sem tree with picture below the + * // course search (target: courses). + * // This is the behaviour of Stud.IP < 4.2. + * "csemtree":{ + * "visible":true, + * "target":"courses", + * "url":"dispatch.php/search/courses?level=vv", + * "img":{ + * "filename":"directory-search.png", + * "attributes":{ + * "size":"260@100" + * } + * }, + * "title":{ + * "de_DE":"Suche im Vorlesungsverzeichnis", + * "en_GB":"Search course directory" + * } + * }, + * // This option shows a link to the range tree with picture below the + * // course search (target: courses). + * // This is the behaviour of Stud.IP < 4.2. + * "crangetree":{ + * "visible":true, + * "target":"courses", + * "url":"dispatch.php/search/courses?level=ev", + * "img":{ + * "filename":"institute-search.png", + * "attributes":{ + * "size":"260@100" + * } + * }, + * "title":{ + * "de_DE":"Suche in Einrichtungen", + * "en_GB":"Search institutes" + * } + * } + * } + * + * + * @param string $target + * @param string $option_name + * @return Navigation|null + */ + public static function getSearchOptionNavigation($target, $option_name = null): ?Navigation + { + // return first visible search option + if (is_null($option_name)) { + $options = Config::get()->COURSE_SEARCH_NAVIGATION_OPTIONS; + foreach ($options as $name => $option) { + if ($option['visible'] && $option['target'] === $target) { + return self::getSearchOptionNavigation($target, $name); + } + } + return null; + } + + $installed_languages = array_keys(Config::get()->INSTALLED_LANGUAGES); + $language = $_SESSION['_language'] ?? reset($installed_languages); + $option = Config::get()->COURSE_SEARCH_NAVIGATION_OPTIONS[$option_name]; + if (!$option['visible'] || $option['target'] !== $target) { + return null; + } + if (empty($option['url'])) { + switch ($option_name) { + case 'courses': + case 'semtree': + return new Navigation(_('Vorlesungsverzeichnis'), + URLHelper::getURL('dispatch.php/search/courses', + [ + 'type' => 'semtree' + ], true)); + case 'rangetree': + return new Navigation(_('Einrichtungsverzeichnis'), + URLHelper::getURL('dispatch.php/search/courses', + [ + 'type' => 'rangetree' + ], true)); + case 'module': + return new MVVSearchNavigation(_('Modulverzeichnis'), + URLHelper::getURL('dispatch.php/search/module'),null, true); + } + } else { + return new Navigation($option['title'][$language], + URLHelper::getURL($option['url'], ['option' => $option_name], true)); + } + if (!empty($option['sem_tree_id'])) { + $study_area = StudipStudyArea::find($option['sem_tree_id']); + return new Navigation($study_area->name, + URLHelper::getURL('dispatch.php/search/courses', + [ + 'start_item_id' => $option['sem_tree_id'], + 'path_id' => $option['sem_tree_id'], + 'cmd' => 'show_sem_range', + 'item_id' => $option['sem_tree_id'] . '_withkids', + 'level' => 'vv', + 'option' => $option_name + ], true)); + } + if (!empty($option['range_tree_id'])) { + $item_name = DBManager::get()->fetchColumn(' + SELECT `name` + FROM `range_tree` + WHERE item_id = ?', + [$option['range_tree_id']]); + return new Navigation($item_name, + URLHelper::getURL('dispatch.php/search/courses', + [ + 'start_item_id' => $option['range_tree_id'], + 'path_id' => $option['range_tree_id'], + 'cmd' => 'show_sem_range_tree', + 'item_id' => $option['range_tree_id'] . '_withkids', + 'level' => 'ev', + 'option' => $option_name + ], true)); + } + + return null; + } + + /** + * The class SemBrowse uses a vast number of variables stored in the + * session. This function sets the default values or transfers some + * of them to url parameters if a filter in the sidebar has been changed. + * + * @see SemBrowse::setClassesSelector() + * @see SemBrowse::setSemesterSelector() + */ + public static function transferSessionData() + { + if (empty($_SESSION['sem_browse_data']) || Request::option('reset_all')) { + $_SESSION['sem_browse_data'] = []; + } + + $_SESSION['sem_browse_data']['qs_choose'] = Request::get('search_sem_qs_choose', + $_SESSION['sem_browse_data']['qs_choose'] ?? null); + + // simulate button clicked if semester was changed + $old_default_sem = $_SESSION['sem_browse_data']['default_sem'] ?? null; + if (Request::option('search_sem_sem', $old_default_sem) != $old_default_sem) { + $_SESSION['sem_browse_data']['default_sem'] = Request::option('search_sem_sem'); + if ($_SESSION['sem_browse_data']['sset']) { + Request::set('search_sem_quick_search_parameter', $_SESSION['sem_browse_data']['sset']); + Request::set('search_sem_quick_search', $_SESSION['sem_browse_data']['sset']); + Request::set('search_sem_qs_choose', $_SESSION['sem_browse_data']['qs_choose']); + Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); + Request::set('search_sem_do_search', '1'); + Request::set('search_sem_' . md5('is_sended'), '1'); + } else { + Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); + Request::set('search_sem_sem_change', '1'); + Request::set('search_sem_sem_select', '1'); + } + } + + // simulate button clicked if class was changed + $old_show_class = $_SESSION['sem_browse_data']['show_class'] ?? null; + if (Request::option('show_class', $old_show_class) != $old_show_class) { + $_SESSION['sem_browse_data']['show_class'] = Request::option('show_class'); + + if ($_SESSION['sem_browse_data']['show_class'] + && $_SESSION['sem_browse_data']['show_class'] != 'all') { + $class = $GLOBALS['SEM_CLASS'][$_SESSION['sem_browse_data']['show_class']]; + $_SESSION['sem_browse_data']['sem_status'] = array_keys($class->getSemTypes()); + } else { + $_SESSION['sem_browse_data']['sem_status'] = false; + } + + if ($_SESSION['sem_browse_data']['sset']) { + Request::set('search_sem_quick_search_parameter', $_SESSION['sem_browse_data']['sset']); + Request::set('search_sem_quick_search', $_SESSION['sem_browse_data']['sset']); + Request::set('search_sem_qs_choose', $_SESSION['sem_browse_data']['qs_choose']); + Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); + Request::set('search_sem_do_search', '1'); + Request::set('search_sem_' . md5('is_sended'), '1'); + } else { + Request::set('search_sem_category', $_SESSION['sem_browse_data']['show_class']); + Request::set('search_sem_sem_change', '1'); + Request::set('search_sem_sem_select', '1'); + } + } + + // set default values + if (empty($_SESSION['sem_browse_data']['default_sem'])) { + $_SESSION['sem_browse_data']['default_sem'] = + Semester::getIndexById(self::getDefaultSemester(), true, true) + ?: 'all'; + } + $_SESSION['sem_browse_data']['show_class'] = + $_SESSION['sem_browse_data']['show_class'] ?? 'all'; + $_SESSION['sem_browse_data']['group_by'] = + $_SESSION['sem_browse_data']['group_by'] ?? '0'; + } + + /** + * Retrieves the default semester from session or calculate it considering + * the value from SEMESTER_TIME_SWITCH. + * + * @return Semester The semester object of the default semester. + */ + public static function getDefaultSemester() + { + $default_sem = $_SESSION['_default_sem']; + if (!$default_sem) { + $current_sem = Semester::findDefault(); + $default_sem = $current_sem->id; + } + + return $default_sem; + } + + /** + * Adds a widget to the sidebar to select a course class. The result set is + * filtered by this class. + * + * @param string $submit_url The submit url. + */ + public static function setClassesSelector($submit_url) + { + $classes_filter = new SelectWidget(_('Veranstaltungsklassen'), + $submit_url, 'show_class'); + $classes_filter->addElement(new SelectElement('all', _('Alle'), + ($_SESSION['sem_browse_data']['show_class'] ?: 'all') === 'all')); + foreach ($GLOBALS['SEM_CLASS'] as $key => $val) { + $classes_filter->addElement(new SelectElement($key, $val['name'], + ($_SESSION['sem_browse_data']['show_class'] == $key))); + } + Sidebar::Get()->addWidget($classes_filter); + } + + /** + * Adds a widget to the sidebar to select a semester. The result set is + * filtered by this semester. + * + * @param string $submit_url The submit url. + */ + public static function setSemesterSelector($submit_url) + { + $semesters = Semester::findAllVisible(); + $sidebar = Sidebar::Get(); + $list = new SelectWidget(_('Semester'), + $submit_url, 'search_sem_sem'); + $list->addElement(new SelectElement('all', _('Alle'), + ($_SESSION['sem_browse_data']['default_sem']) === 'all')); + foreach(array_reverse($semesters, true) as $i => $semester_data) { + $list->addElement(new SelectElement( + $i, + $semester_data['name'], + ($_SESSION['sem_browse_data']['default_sem'] !== 'all' + && intval($_SESSION['sem_browse_data']['default_sem']) === $i) + )); + } + $sidebar->addWidget($list, 'filter_semester'); + } + + /** + * Returns the admission status for a course. + * + * @param string $seminar_id Id of the course + * @param bool $prelim State of preliminary setting + * @return int + */ + public static function getStatusCourseAdmission($seminar_id, $prelim) + { + $sql = "SELECT COUNT(`type`) AS `types`, + SUM(IF(`type` = 'LockedAdmission', 1, 0)) AS `type_locked` + FROM `seminar_courseset` + INNER JOIN `courseset_rule` USING (`set_id`) + WHERE `seminar_id` = ? + GROUP BY `set_id`"; + + $stmt = DBManager::get()->prepare($sql); + $stmt->execute([$seminar_id]); + $result = $stmt->fetch(); + + if (!empty($result['types'])) { + if ($result['type_locked']) { + return 2; + } + return 1; + } + + if ($prelim) { + return 1; + } + return 0; + } + + /** + * Returns a Quick Search form to put inside a WidgetElement. + * + * @param string $level The Level of search , expected ('f', 'vv', 'ev') + * @return string $search_form_content Contains a form element with a quick search input, predefined (hidden) inputs and search button + */ + public function getQuickSearchForm() + { + if ($this->sem_browse_data['level'] === 'vv') { + $this->search_obj->sem_tree =& $this->sem_tree->tree; + if ($this->sem_tree->start_item_id !== 'root') { + $this->search_obj->search_scopes[] = $this->sem_tree->start_item_id; + } + } elseif ($this->sem_browse_data['level'] === 'ev') { + $this->search_obj->range_tree =& $this->range_tree->tree; + if ($this->range_tree->start_item_id !== 'root'){ + $this->search_obj->search_ranges[] = $this->range_tree->start_item_id; + } + } + + $search_form_content = $this->search_obj->getFormStart(URLHelper::getLink(), ['class' => '']); + $quicksearch = $this->getQuicksearch(); + $quicksearch->setInputStyle('height:22px;width: 100%;'); + $quicksearch->withButton(['search_button_name'=> 'course_search_button']); + $quicksearch->disableAutocomplete(); + $search_form_content .= $quicksearch->render(); + $search_form_content .= $this->search_obj->getHiddenField('qs_choose','title_lecturer_number'); + + if($this->sem_browse_data['level'] == 'vv') + $search_form_content .= $this->search_obj->getHiddenField('scope_choose', $this->sem_tree->start_item_id); + + if($this->sem_browse_data['level'] == 'ev') + $search_form_content .= $this->search_obj->getHiddenField('range_choose', $this->range_tree->start_item_id); + + $search_form_content .= $this->search_obj->getHiddenField('level', $this->sem_browse_data['level']); + $search_form_content .= $this->search_obj->getHiddenField('sem', htmlReady($_SESSION['sem_browse_data']['default_sem'])); + + $search_form_content .= $this->search_obj->getFormEnd(); + return $search_form_content; + } +} diff --git a/lib/classes/SemClass.class.php b/lib/classes/SemClass.class.php deleted file mode 100644 index 2a038e2..0000000 --- a/lib/classes/SemClass.class.php +++ /dev/null @@ -1,644 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -/** - * Class to define and manage attributes of seminar classes (or seminar categories). - * Usually all sem-classes are stored in a global variable $SEM_CLASS which is - * an array of SemClass objects. - * - * SemClass::getClasses() gets you all seminar classes in an array. - * - * You can access the attributes of a sem-class like an associative - * array with $sem_class['default_read_level']. The uinderlying data is stored - * in the database in the table sem_classes. - * - * If you want to have a name of a sem-class like "Lehre", please use - * $sem_class['name'] and you will get a fully localized name and not the pure - * database entry. - * - * This class manages also which modules are contained in which course-slots, - * like "what module is used as a forum in my seminars". In the database stored - * is the name of the module like "CoreForum" or a classname of a plugin or null - * if the forum is completely disabled by root for this sem-class. Core-modules - * can only be used within a standard slot. Plugins may also be used as optional - * modules not contained in a slot. - * - * In the field 'modules' in the database is for each modules stored in a json-string - * if the module is activatable by the teacher or not and if it is activated as - * a default. Please use the methods SemClass::isSlotModule, SemClass::getSlotModule, - * SemClass::isModuleAllowed, SemClass::isModuleMandatory, SemClass::isSlotMandatory - * or even more simple SemClass::getNavigationForSlot (see documentation there). - */ -class SemClass implements ArrayAccess -{ - protected $data = []; - - static protected $studygroup_forbidden_modules = [ - 'CoreAdmin', - 'CoreParticipants', - ]; - - static protected $sem_classes = null; - - static public function getDefaultSemClass() { - $data = [ - 'name' => "Fehlerhafte Seminarklasse!", - 'modules' => '{"CoreOverview":{"activated":1,"sticky":1},"CoreAdmin":{"activated":1,"sticky":1}}', - 'visible' => 1, - 'is_group' => false - ]; - return new SemClass($data); - } - - /** - * Generates a dummy SemClass for institutes of this type (as defined in config.inc.php). - * @param integer $type institute type - * @return SemClass - */ - static public function getDefaultInstituteClass($type) - { - global $INST_MODULES; - - // fall back to 'default' if modules are not defined - $type = isset($INST_MODULES[$type]) ? $type : 'default'; - - $data = [ - 'name' => _('Generierte Standardinstitutsklasse'), - 'visible' => 1, - 'admin' => 'CoreAdmin', // always available - 'overview' => 'CoreOverview' // always available - ]; - $slots = [ - 'forum' => 'CoreForum', - 'documents' => 'CoreDocuments', - 'scm' => 'CoreScm', - 'wiki' => 'CoreWiki', - 'calendar' => 'CoreCalendar', - 'elearning_interface' => 'CoreElearningInterface', - 'personal' => 'CorePersonal' - ]; - $modules = [ - 'CoreAdmin' => ['activated' => 1, 'sticky' => 1], - 'CoreOverview' => ['activated' => 1, 'sticky' => 1], - ]; - - foreach ($slots as $slot => $module) { - $data[$slot] = $module; - $modules[$module] = ['activated' => (int) ($INST_MODULES[$type][$slot] ?? 0), 'sticky' => 0]; - } - $data['modules'] = json_encode($modules); - - return new SemClass($data); - } - - /** - * Constructor can be set with integer of sem_class_id or an array of - * the old $SEM_CLASS style. - * @param integer | array $data - */ - public function __construct($data) - { - $db = DBManager::get(); - if (is_int($data)) { - $statement = $db->prepare("SELECT * FROM sem_classes WHERE id = :id "); - $statement->execute(['id' => $data]); - $this->data = $statement->fetch(PDO::FETCH_ASSOC); - } else { - $this->data = $data; - } - if (!empty($this->data['modules'])) { - $this->data['modules'] = self::object2array(json_decode($this->data['modules'])); - - } else { - $this->data['modules'] = []; - } - if (!empty($this->data['studygroup_mode'])) { - if (!isset($this->data['modules']['CoreStudygroupAdmin'])) { - $this->data['modules']['CoreStudygroupAdmin'] = ['activated' => 1, 'sticky' => 1]; - } - } else { - if (!isset($this->data['modules']['CoreAdmin'])) { - $this->data['modules']['CoreAdmin'] = ['activated' => 1, 'sticky' => 1]; - } - } - foreach (array_keys($this->data['modules']) as $modulename) { - if ($this->isModuleForbidden($modulename)) { - unset($this->data['modules'][$modulename]); - } - } - } - - - /** - * @param string $module - * @return false|int - */ - public function activateModuleInCourses($module) - { - $plugin = PluginManager::getInstance()->getPlugin($module); - if ($plugin) { - return Course::findEachBySQL(function ($course) use ($plugin) { - return PluginManager::getInstance()->setPluginActivated($plugin->getPluginId(), $course->id, true); - }, - "seminare.status IN (?)", - [array_keys($this->getSemTypes())]); - } else { - return false; - } - } - - /** - * @param string $module - * @return false|int - */ - public function deActivateModuleInCourses($module) - { - $plugin = PluginManager::getInstance()->getPlugin($module); - if ($plugin) { - return Course::findEachBySQL(function ($course) use ($plugin) { - return PluginManager::getInstance()->setPluginActivated($plugin->getPluginId(), $course->id, false); - }, - "seminare.status IN (?)", - [array_keys($this->getSemTypes())]); - } else { - return false; - } - - } - - /** - * Returns the number of seminars of this sem_class in Stud.IP - * @return integer - */ - public function countSeminars() - { - $db = DBManager::get(); - $sum = 0; - foreach ($GLOBALS['SEM_TYPE'] as $sem_type) { - if ($sem_type['class'] == $this->data['id']) { - $sum += $sem_type->countSeminars(); - } - } - return $sum; - } - - - /** - * @param string $modulename - * @return bool - */ - public function isModuleForbidden($modulename) - { - if (!empty($this->data['studygroup_mode'])) { - return in_array($modulename, self::$studygroup_forbidden_modules); - } else { - return strpos($modulename, 'Studygroup') !== false; - } - } - - /** - * Returns the metadata of a module regarding this sem_class object. - * @param string $modulename - * @return array('sticky' => (bool), 'activated' => (bool)) - */ - public function getModuleMetadata($modulename) - { - return $this->data['modules'][$modulename]; - } - - /** - * Sets the metadata for each module at once. - * @param array $module_array: array($module_name => array('sticky' => (bool), 'activated' => (bool)), ...) - */ - public function setModules($module_array) - { - $this->data['modules'] = $module_array; - } - - /** - * Returns all metadata of the modules at once. - * @return array: array($module_name => array('sticky' => (bool), 'activated' => (bool)), ...) - */ - public function getModules() - { - return $this->data['modules']; - } - - /** - * @return StudipModule[] - */ - public function getModuleObjects() - { - $result = []; - foreach (array_keys($this->getModules()) as $module) { - $plugin = PluginManager::getInstance()->getPlugin($module); - if ($plugin) { - $result[$plugin->getPluginId()] = $plugin; - } - } - return $result; - } - - /** - * @return string[] - */ - public function getActivatedModules() - { - return array_keys(array_filter($this->data['modules'], function ($meta) { - return $meta['activated']; - })); - } - - /** - * @return StudipModule[] - */ - public function getActivatedModuleObjects() - { - $result = []; - foreach ($this->getActivatedModules() as $module) { - $plugin = PluginManager::getInstance()->getPlugin($module); - if ($plugin) { - $result[$plugin->getPluginId()] = $plugin; - } - } - return $result; - } - - /** - * @return mixed|object - */ - public function getAdminModuleObject() - { - if ($this->data['studygroup_mode']) { - $module = 'CoreStudygroupAdmin'; - } else { - $module = 'CoreAdmin'; - } - return PluginManager::getInstance()->getPlugin($module); - } - - /** - * Returns true if a module is activated on default for this sem_class. - * @param string $modulename - * @return boolean - */ - public function isModuleActivated($modulename) - { - return isset($this->data['modules'][$modulename]) - && $this->data['modules'][$modulename]['activated']; - } - - /** - * Returns if a module is allowed to be displayed for this sem_class. - * @param string $modulename - * @return boolean - */ - public function isModuleAllowed($modulename) - { - return !$this->isModuleForbidden($modulename) - && ( - empty($this->data['modules'][$modulename]) - || empty($this->data['modules'][$modulename]['sticky']) - || !empty($this->data['modules'][$modulename]['activated']) - ); - } - - /** - * Returns if a module is mandatory for this sem_class. - * @param string $module - * @return boolean - */ - public function isModuleMandatory($module) - { - return isset($this->data['modules'][$module]) - && !empty($this->data['modules'][$module]['sticky']) - && !empty($this->data['modules'][$module]['activated']); - } - - public function getSemTypes() - { - $types = []; - foreach (SemType::getTypes() as $id => $type) { - if ($type['class'] == $this->data['id']) { - $types[$id] = $type; - } - } - return $types; - } - - /** - * Checks if the current sem class is usable for course grouping. - */ - public function isGroup() - { - return $this->data['is_group']; - } - - /** - * Checks if any SemClasses exist that provide grouping functionality. - * @return SimpleCollection - */ - public static function getGroupClasses() - { - return SimpleCollection::createFromArray(self::getClasses())->findBy('is_group', true); - } - - /** - * stores all data in the database - * @return boolean success - */ - public function store() - { - $db = DBManager::get(); - $statement = $db->prepare( - "UPDATE sem_classes " . - "SET name = :name, " . - "description = :description, " . - "create_description = :create_description, " . - "studygroup_mode = :studygroup_mode, " . - "only_inst_user = :only_inst_user, " . - "default_read_level = :default_read_level, " . - "default_write_level = :default_write_level, " . - "bereiche = :bereiche, " . - "module = :module, " . - "show_browse = :show_browse, " . - "write_access_nobody = :write_access_nobody, " . - "topic_create_autor = :topic_create_autor, " . - "visible = :visible, " . - "course_creation_forbidden = :course_creation_forbidden, " . - "modules = :modules, " . - "title_dozent = :title_dozent, " . - "title_dozent_plural = :title_dozent_plural, " . - "title_tutor = :title_tutor, " . - "title_tutor_plural = :title_tutor_plural, " . - "title_autor = :title_autor, " . - "title_autor_plural = :title_autor_plural, " . - "admission_prelim_default = :admission_prelim_default, " . - "admission_type_default = :admission_type_default, " . - "show_raumzeit = :show_raumzeit, " . - "is_group = :is_group, " . - "unlimited_forbidden = :unlimited_forbidden, " . - "chdate = UNIX_TIMESTAMP() " . - "WHERE id = :id ". - ""); - \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); - return $statement->execute([ - 'id' => $this->data['id'], - 'name' => $this->data['name'], - 'description' => $this->data['description'], - 'create_description' => $this->data['create_description'], - 'studygroup_mode' => (int) $this->data['studygroup_mode'], - 'only_inst_user' => (int) $this->data['only_inst_user'], - 'default_read_level' => (int) $this->data['default_read_level'], - 'default_write_level' => (int) $this->data['default_write_level'], - 'bereiche' => (int) $this->data['bereiche'], - 'module' => (int) $this->data['module'], - 'show_browse' => (int) $this->data['show_browse'], - 'write_access_nobody' => (int) $this->data['write_access_nobody'], - 'topic_create_autor' => (int) $this->data['topic_create_autor'], - 'visible' => (int) $this->data['visible'], - 'course_creation_forbidden' => (int) $this->data['course_creation_forbidden'], - 'modules' => json_encode((object) $this->data['modules']), - 'title_dozent' => $this->data['title_dozent'] - ? $this->data['title_dozent'] - : null, - 'title_dozent_plural' => $this->data['title_dozent_plural'] - ? $this->data['title_dozent_plural'] - : null, - 'title_tutor' => $this->data['title_tutor'] - ? $this->data['title_tutor'] - : null, - 'title_tutor_plural' => $this->data['title_tutor_plural'] - ? $this->data['title_tutor_plural'] - : null, - 'title_autor' => $this->data['title_autor'] - ? $this->data['title_autor'] - : null, - 'title_autor_plural' => $this->data['title_autor_plural'] - ? $this->data['title_autor_plural'] - : null, - 'admission_prelim_default' => (int)$this->data['admission_prelim_default'], - 'admission_type_default' => (int)$this->data['admission_type_default'], - 'show_raumzeit' => (int) $this->data['show_raumzeit'], - 'is_group' => (int) $this->data['is_group'], - 'unlimited_forbidden' => (int) $this->data['unlimited_forbidden'], - ]); - } - - /** - * Deletes the sem_class-object and all its sem_types. Will only delete, - * if there are no seminars in this sem_class. - * Remember to refresh the global $SEM_CLASS and $SEM_TYPE array. - * @return boolean : success of deletion - */ - public function delete() - { - if ($this->countSeminars() === 0) { - foreach ($GLOBALS['SEM_TYPE'] as $sem_type) { - if ($sem_type['class'] == $this->data['id']) { - $sem_type->delete(); - } - } - $GLOBALS['SEM_TYPE'] = SemType::getTypes(); - $db = DBManager::get(); - $statement = $db->prepare(" - DELETE FROM sem_classes - WHERE id = :id - "); - \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); - return $statement->execute([ - 'id' => $this->data['id'] - ]); - } else { - return false; - } - } - - /** - * Sets an attribute of sem_class->data - * @param string $offset - * @param mixed $value - */ - public function set($offset, $value) - { - $this->data[$offset] = $value; - } - - /*************************************************************************** - * ArrayAccess methods * - ***************************************************************************/ - - /** - * deprecated, does nothing, should not be used - * @param string $offset - * @param mixed $value - */ - public function offsetSet($offset, $value): void - { - } - - /** - * Compatibility function with old $SEM_CLASS variable for plugins. Maps the - * new array-structure to the old boolean values. - * @param integer $offset: name of attribute - */ - public function offsetGet($offset): mixed - { - switch ($offset) { - case "name": - return gettext($this->data['name']); - case "only_inst_user": - return (bool) $this->data['only_inst_user']; - case "bereiche": - return (bool) $this->data['bereiche']; - case "show_browse": - return (bool) $this->data['show_browse']; - case "write_access_nobody": - return (bool) $this->data['write_access_nobody']; - case "topic_create_autor": - return (bool) $this->data['topic_create_autor']; - case "visible": - return (bool) $this->data['visible']; - case "studygroup_mode": - return (bool) $this->data['studygroup_mode']; - case "admission_prelim_default": - return (int) $this->data['admission_prelim_default']; - case "admission_type_default": - return (int) $this->data['admission_type_default']; - case "is_group": - return (bool) $this->data['is_group']; - } - //ansonsten - return $this->data[$offset] ?? null; - } - - /** - * ArrayAccess method to check if an attribute exists. - * @param int $offset - */ - public function offsetExists($offset): bool - { - return isset($this->data[$offset]); - } - - /** - * deprecated, does nothing, should not be used - * @param string $offset - */ - public function offsetUnset($offset): void - { - } - - /*************************************************************************** - * static methods * - ***************************************************************************/ - - /** - * Returns an array of all SemClasses in Stud.IP. Equivalent to global - * $SEM_CLASS variable. This variable is statically stored in this class. - * @return SemClass[] of SemClass - */ - static public function getClasses() - { - if (!is_array(self::$sem_classes)) { - $db = DBManager::get(); - self::$sem_classes = []; - - $cache = \Studip\Cache\Factory::getCache(); - $class_array = unserialize($cache->read('DB_SEM_CLASSES_ARRAY')); - if (!$class_array) { - - try { - $statement = $db->prepare( - "SELECT * FROM sem_classes ORDER BY id ASC " - ); - $statement->execute(); - $class_array = $statement->fetchAll(PDO::FETCH_ASSOC); - - if ($class_array) { - $cache = \Studip\Cache\Factory::getCache(); - $cache->write('DB_SEM_CLASSES_ARRAY', serialize($class_array)); - } - } catch (PDOException $e) { - //for use without or before migration 92 - $class_array = $GLOBALS['SEM_CLASS_OLD_VAR']; - if (is_array($class_array)) { - ksort($class_array); - foreach ($class_array as $id => $class) { - self::$sem_classes[$id] = new SemClass($class); - } - } else { - self::$sem_classes[1] = self::getDefaultSemClass(); - } - } - } - foreach ($class_array as $sem_class) { - self::$sem_classes[$sem_class['id']] = new SemClass($sem_class); - } - } - return self::$sem_classes; - } - - /** - * Refreshes the internal $sem_classes cache-variable. - * @return array of SemClass - */ - static public function refreshClasses() - { - \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); - self::$sem_classes = null; - return self::getClasses(); - } - - /** - * Static method to recursively transform an object into an associative array. - * @param mixed $obj: should be of class StdClass - * @return array - */ - static public function object2array($obj) - { - $arr_raw = is_object($obj) ? get_object_vars($obj) : $obj; - foreach ($arr_raw as $key => $val) { - $val = (is_array($val) || is_object($val)) ? self::object2array($val) : $val; - $arr[$key] = $val; - } - return $arr; - } - - - /** - * Static method only to keep the translationstrings of the values. It is - * never used within the system. - */ - static private function localization() - { - _("Lehre"); - _("Forschung"); - _("Organisation"); - _("Community"); - _("Arbeitsgruppen"); - _("importierte Kurse"); - _("Hauptveranstaltungen"); - - _("Hier finden Sie alle in Stud.IP registrierten Lehrveranstaltungen"); - _("Verwenden Sie diese Kategorie, um normale Lehrveranstaltungen anzulegen"); - _("Hier finden Sie virtuelle Veranstaltungen zum Thema Forschung an der Universität"); - _("In dieser Kategorie können Sie virtuelle Veranstaltungen für Forschungsprojekte anlegen."); - _("Hier finden Sie virtuelle Veranstaltungen zu verschiedenen Gremien an der Universität"); - _("Um virtuelle Veranstaltungen für Uni-Gremien anzulegen, verwenden Sie diese Kategorie"); - _("Hier finden Sie virtuelle Veranstaltungen zu unterschiedlichen Themen"); - _("Wenn Sie Veranstaltungen als Diskussiongruppen zu unterschiedlichen Themen anlegen möchten, verwenden Sie diese Kategorie."); - _("Hier finden Sie verschiedene Arbeitsgruppen an der %s"); - _("Verwenden Sie diese Kategorie, um unterschiedliche Arbeitsgruppen anzulegen."); - _("Veranstaltungen dieser Kategorie dienen als Gruppierungselement, um die Zusammengehörigkeit von Veranstaltungen anderer Kategorien abzubilden."); - } - -} diff --git a/lib/classes/SemClass.php b/lib/classes/SemClass.php new file mode 100644 index 0000000..2a038e2 --- /dev/null +++ b/lib/classes/SemClass.php @@ -0,0 +1,644 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +/** + * Class to define and manage attributes of seminar classes (or seminar categories). + * Usually all sem-classes are stored in a global variable $SEM_CLASS which is + * an array of SemClass objects. + * + * SemClass::getClasses() gets you all seminar classes in an array. + * + * You can access the attributes of a sem-class like an associative + * array with $sem_class['default_read_level']. The uinderlying data is stored + * in the database in the table sem_classes. + * + * If you want to have a name of a sem-class like "Lehre", please use + * $sem_class['name'] and you will get a fully localized name and not the pure + * database entry. + * + * This class manages also which modules are contained in which course-slots, + * like "what module is used as a forum in my seminars". In the database stored + * is the name of the module like "CoreForum" or a classname of a plugin or null + * if the forum is completely disabled by root for this sem-class. Core-modules + * can only be used within a standard slot. Plugins may also be used as optional + * modules not contained in a slot. + * + * In the field 'modules' in the database is for each modules stored in a json-string + * if the module is activatable by the teacher or not and if it is activated as + * a default. Please use the methods SemClass::isSlotModule, SemClass::getSlotModule, + * SemClass::isModuleAllowed, SemClass::isModuleMandatory, SemClass::isSlotMandatory + * or even more simple SemClass::getNavigationForSlot (see documentation there). + */ +class SemClass implements ArrayAccess +{ + protected $data = []; + + static protected $studygroup_forbidden_modules = [ + 'CoreAdmin', + 'CoreParticipants', + ]; + + static protected $sem_classes = null; + + static public function getDefaultSemClass() { + $data = [ + 'name' => "Fehlerhafte Seminarklasse!", + 'modules' => '{"CoreOverview":{"activated":1,"sticky":1},"CoreAdmin":{"activated":1,"sticky":1}}', + 'visible' => 1, + 'is_group' => false + ]; + return new SemClass($data); + } + + /** + * Generates a dummy SemClass for institutes of this type (as defined in config.inc.php). + * @param integer $type institute type + * @return SemClass + */ + static public function getDefaultInstituteClass($type) + { + global $INST_MODULES; + + // fall back to 'default' if modules are not defined + $type = isset($INST_MODULES[$type]) ? $type : 'default'; + + $data = [ + 'name' => _('Generierte Standardinstitutsklasse'), + 'visible' => 1, + 'admin' => 'CoreAdmin', // always available + 'overview' => 'CoreOverview' // always available + ]; + $slots = [ + 'forum' => 'CoreForum', + 'documents' => 'CoreDocuments', + 'scm' => 'CoreScm', + 'wiki' => 'CoreWiki', + 'calendar' => 'CoreCalendar', + 'elearning_interface' => 'CoreElearningInterface', + 'personal' => 'CorePersonal' + ]; + $modules = [ + 'CoreAdmin' => ['activated' => 1, 'sticky' => 1], + 'CoreOverview' => ['activated' => 1, 'sticky' => 1], + ]; + + foreach ($slots as $slot => $module) { + $data[$slot] = $module; + $modules[$module] = ['activated' => (int) ($INST_MODULES[$type][$slot] ?? 0), 'sticky' => 0]; + } + $data['modules'] = json_encode($modules); + + return new SemClass($data); + } + + /** + * Constructor can be set with integer of sem_class_id or an array of + * the old $SEM_CLASS style. + * @param integer | array $data + */ + public function __construct($data) + { + $db = DBManager::get(); + if (is_int($data)) { + $statement = $db->prepare("SELECT * FROM sem_classes WHERE id = :id "); + $statement->execute(['id' => $data]); + $this->data = $statement->fetch(PDO::FETCH_ASSOC); + } else { + $this->data = $data; + } + if (!empty($this->data['modules'])) { + $this->data['modules'] = self::object2array(json_decode($this->data['modules'])); + + } else { + $this->data['modules'] = []; + } + if (!empty($this->data['studygroup_mode'])) { + if (!isset($this->data['modules']['CoreStudygroupAdmin'])) { + $this->data['modules']['CoreStudygroupAdmin'] = ['activated' => 1, 'sticky' => 1]; + } + } else { + if (!isset($this->data['modules']['CoreAdmin'])) { + $this->data['modules']['CoreAdmin'] = ['activated' => 1, 'sticky' => 1]; + } + } + foreach (array_keys($this->data['modules']) as $modulename) { + if ($this->isModuleForbidden($modulename)) { + unset($this->data['modules'][$modulename]); + } + } + } + + + /** + * @param string $module + * @return false|int + */ + public function activateModuleInCourses($module) + { + $plugin = PluginManager::getInstance()->getPlugin($module); + if ($plugin) { + return Course::findEachBySQL(function ($course) use ($plugin) { + return PluginManager::getInstance()->setPluginActivated($plugin->getPluginId(), $course->id, true); + }, + "seminare.status IN (?)", + [array_keys($this->getSemTypes())]); + } else { + return false; + } + } + + /** + * @param string $module + * @return false|int + */ + public function deActivateModuleInCourses($module) + { + $plugin = PluginManager::getInstance()->getPlugin($module); + if ($plugin) { + return Course::findEachBySQL(function ($course) use ($plugin) { + return PluginManager::getInstance()->setPluginActivated($plugin->getPluginId(), $course->id, false); + }, + "seminare.status IN (?)", + [array_keys($this->getSemTypes())]); + } else { + return false; + } + + } + + /** + * Returns the number of seminars of this sem_class in Stud.IP + * @return integer + */ + public function countSeminars() + { + $db = DBManager::get(); + $sum = 0; + foreach ($GLOBALS['SEM_TYPE'] as $sem_type) { + if ($sem_type['class'] == $this->data['id']) { + $sum += $sem_type->countSeminars(); + } + } + return $sum; + } + + + /** + * @param string $modulename + * @return bool + */ + public function isModuleForbidden($modulename) + { + if (!empty($this->data['studygroup_mode'])) { + return in_array($modulename, self::$studygroup_forbidden_modules); + } else { + return strpos($modulename, 'Studygroup') !== false; + } + } + + /** + * Returns the metadata of a module regarding this sem_class object. + * @param string $modulename + * @return array('sticky' => (bool), 'activated' => (bool)) + */ + public function getModuleMetadata($modulename) + { + return $this->data['modules'][$modulename]; + } + + /** + * Sets the metadata for each module at once. + * @param array $module_array: array($module_name => array('sticky' => (bool), 'activated' => (bool)), ...) + */ + public function setModules($module_array) + { + $this->data['modules'] = $module_array; + } + + /** + * Returns all metadata of the modules at once. + * @return array: array($module_name => array('sticky' => (bool), 'activated' => (bool)), ...) + */ + public function getModules() + { + return $this->data['modules']; + } + + /** + * @return StudipModule[] + */ + public function getModuleObjects() + { + $result = []; + foreach (array_keys($this->getModules()) as $module) { + $plugin = PluginManager::getInstance()->getPlugin($module); + if ($plugin) { + $result[$plugin->getPluginId()] = $plugin; + } + } + return $result; + } + + /** + * @return string[] + */ + public function getActivatedModules() + { + return array_keys(array_filter($this->data['modules'], function ($meta) { + return $meta['activated']; + })); + } + + /** + * @return StudipModule[] + */ + public function getActivatedModuleObjects() + { + $result = []; + foreach ($this->getActivatedModules() as $module) { + $plugin = PluginManager::getInstance()->getPlugin($module); + if ($plugin) { + $result[$plugin->getPluginId()] = $plugin; + } + } + return $result; + } + + /** + * @return mixed|object + */ + public function getAdminModuleObject() + { + if ($this->data['studygroup_mode']) { + $module = 'CoreStudygroupAdmin'; + } else { + $module = 'CoreAdmin'; + } + return PluginManager::getInstance()->getPlugin($module); + } + + /** + * Returns true if a module is activated on default for this sem_class. + * @param string $modulename + * @return boolean + */ + public function isModuleActivated($modulename) + { + return isset($this->data['modules'][$modulename]) + && $this->data['modules'][$modulename]['activated']; + } + + /** + * Returns if a module is allowed to be displayed for this sem_class. + * @param string $modulename + * @return boolean + */ + public function isModuleAllowed($modulename) + { + return !$this->isModuleForbidden($modulename) + && ( + empty($this->data['modules'][$modulename]) + || empty($this->data['modules'][$modulename]['sticky']) + || !empty($this->data['modules'][$modulename]['activated']) + ); + } + + /** + * Returns if a module is mandatory for this sem_class. + * @param string $module + * @return boolean + */ + public function isModuleMandatory($module) + { + return isset($this->data['modules'][$module]) + && !empty($this->data['modules'][$module]['sticky']) + && !empty($this->data['modules'][$module]['activated']); + } + + public function getSemTypes() + { + $types = []; + foreach (SemType::getTypes() as $id => $type) { + if ($type['class'] == $this->data['id']) { + $types[$id] = $type; + } + } + return $types; + } + + /** + * Checks if the current sem class is usable for course grouping. + */ + public function isGroup() + { + return $this->data['is_group']; + } + + /** + * Checks if any SemClasses exist that provide grouping functionality. + * @return SimpleCollection + */ + public static function getGroupClasses() + { + return SimpleCollection::createFromArray(self::getClasses())->findBy('is_group', true); + } + + /** + * stores all data in the database + * @return boolean success + */ + public function store() + { + $db = DBManager::get(); + $statement = $db->prepare( + "UPDATE sem_classes " . + "SET name = :name, " . + "description = :description, " . + "create_description = :create_description, " . + "studygroup_mode = :studygroup_mode, " . + "only_inst_user = :only_inst_user, " . + "default_read_level = :default_read_level, " . + "default_write_level = :default_write_level, " . + "bereiche = :bereiche, " . + "module = :module, " . + "show_browse = :show_browse, " . + "write_access_nobody = :write_access_nobody, " . + "topic_create_autor = :topic_create_autor, " . + "visible = :visible, " . + "course_creation_forbidden = :course_creation_forbidden, " . + "modules = :modules, " . + "title_dozent = :title_dozent, " . + "title_dozent_plural = :title_dozent_plural, " . + "title_tutor = :title_tutor, " . + "title_tutor_plural = :title_tutor_plural, " . + "title_autor = :title_autor, " . + "title_autor_plural = :title_autor_plural, " . + "admission_prelim_default = :admission_prelim_default, " . + "admission_type_default = :admission_type_default, " . + "show_raumzeit = :show_raumzeit, " . + "is_group = :is_group, " . + "unlimited_forbidden = :unlimited_forbidden, " . + "chdate = UNIX_TIMESTAMP() " . + "WHERE id = :id ". + ""); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + return $statement->execute([ + 'id' => $this->data['id'], + 'name' => $this->data['name'], + 'description' => $this->data['description'], + 'create_description' => $this->data['create_description'], + 'studygroup_mode' => (int) $this->data['studygroup_mode'], + 'only_inst_user' => (int) $this->data['only_inst_user'], + 'default_read_level' => (int) $this->data['default_read_level'], + 'default_write_level' => (int) $this->data['default_write_level'], + 'bereiche' => (int) $this->data['bereiche'], + 'module' => (int) $this->data['module'], + 'show_browse' => (int) $this->data['show_browse'], + 'write_access_nobody' => (int) $this->data['write_access_nobody'], + 'topic_create_autor' => (int) $this->data['topic_create_autor'], + 'visible' => (int) $this->data['visible'], + 'course_creation_forbidden' => (int) $this->data['course_creation_forbidden'], + 'modules' => json_encode((object) $this->data['modules']), + 'title_dozent' => $this->data['title_dozent'] + ? $this->data['title_dozent'] + : null, + 'title_dozent_plural' => $this->data['title_dozent_plural'] + ? $this->data['title_dozent_plural'] + : null, + 'title_tutor' => $this->data['title_tutor'] + ? $this->data['title_tutor'] + : null, + 'title_tutor_plural' => $this->data['title_tutor_plural'] + ? $this->data['title_tutor_plural'] + : null, + 'title_autor' => $this->data['title_autor'] + ? $this->data['title_autor'] + : null, + 'title_autor_plural' => $this->data['title_autor_plural'] + ? $this->data['title_autor_plural'] + : null, + 'admission_prelim_default' => (int)$this->data['admission_prelim_default'], + 'admission_type_default' => (int)$this->data['admission_type_default'], + 'show_raumzeit' => (int) $this->data['show_raumzeit'], + 'is_group' => (int) $this->data['is_group'], + 'unlimited_forbidden' => (int) $this->data['unlimited_forbidden'], + ]); + } + + /** + * Deletes the sem_class-object and all its sem_types. Will only delete, + * if there are no seminars in this sem_class. + * Remember to refresh the global $SEM_CLASS and $SEM_TYPE array. + * @return boolean : success of deletion + */ + public function delete() + { + if ($this->countSeminars() === 0) { + foreach ($GLOBALS['SEM_TYPE'] as $sem_type) { + if ($sem_type['class'] == $this->data['id']) { + $sem_type->delete(); + } + } + $GLOBALS['SEM_TYPE'] = SemType::getTypes(); + $db = DBManager::get(); + $statement = $db->prepare(" + DELETE FROM sem_classes + WHERE id = :id + "); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + return $statement->execute([ + 'id' => $this->data['id'] + ]); + } else { + return false; + } + } + + /** + * Sets an attribute of sem_class->data + * @param string $offset + * @param mixed $value + */ + public function set($offset, $value) + { + $this->data[$offset] = $value; + } + + /*************************************************************************** + * ArrayAccess methods * + ***************************************************************************/ + + /** + * deprecated, does nothing, should not be used + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value): void + { + } + + /** + * Compatibility function with old $SEM_CLASS variable for plugins. Maps the + * new array-structure to the old boolean values. + * @param integer $offset: name of attribute + */ + public function offsetGet($offset): mixed + { + switch ($offset) { + case "name": + return gettext($this->data['name']); + case "only_inst_user": + return (bool) $this->data['only_inst_user']; + case "bereiche": + return (bool) $this->data['bereiche']; + case "show_browse": + return (bool) $this->data['show_browse']; + case "write_access_nobody": + return (bool) $this->data['write_access_nobody']; + case "topic_create_autor": + return (bool) $this->data['topic_create_autor']; + case "visible": + return (bool) $this->data['visible']; + case "studygroup_mode": + return (bool) $this->data['studygroup_mode']; + case "admission_prelim_default": + return (int) $this->data['admission_prelim_default']; + case "admission_type_default": + return (int) $this->data['admission_type_default']; + case "is_group": + return (bool) $this->data['is_group']; + } + //ansonsten + return $this->data[$offset] ?? null; + } + + /** + * ArrayAccess method to check if an attribute exists. + * @param int $offset + */ + public function offsetExists($offset): bool + { + return isset($this->data[$offset]); + } + + /** + * deprecated, does nothing, should not be used + * @param string $offset + */ + public function offsetUnset($offset): void + { + } + + /*************************************************************************** + * static methods * + ***************************************************************************/ + + /** + * Returns an array of all SemClasses in Stud.IP. Equivalent to global + * $SEM_CLASS variable. This variable is statically stored in this class. + * @return SemClass[] of SemClass + */ + static public function getClasses() + { + if (!is_array(self::$sem_classes)) { + $db = DBManager::get(); + self::$sem_classes = []; + + $cache = \Studip\Cache\Factory::getCache(); + $class_array = unserialize($cache->read('DB_SEM_CLASSES_ARRAY')); + if (!$class_array) { + + try { + $statement = $db->prepare( + "SELECT * FROM sem_classes ORDER BY id ASC " + ); + $statement->execute(); + $class_array = $statement->fetchAll(PDO::FETCH_ASSOC); + + if ($class_array) { + $cache = \Studip\Cache\Factory::getCache(); + $cache->write('DB_SEM_CLASSES_ARRAY', serialize($class_array)); + } + } catch (PDOException $e) { + //for use without or before migration 92 + $class_array = $GLOBALS['SEM_CLASS_OLD_VAR']; + if (is_array($class_array)) { + ksort($class_array); + foreach ($class_array as $id => $class) { + self::$sem_classes[$id] = new SemClass($class); + } + } else { + self::$sem_classes[1] = self::getDefaultSemClass(); + } + } + } + foreach ($class_array as $sem_class) { + self::$sem_classes[$sem_class['id']] = new SemClass($sem_class); + } + } + return self::$sem_classes; + } + + /** + * Refreshes the internal $sem_classes cache-variable. + * @return array of SemClass + */ + static public function refreshClasses() + { + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + self::$sem_classes = null; + return self::getClasses(); + } + + /** + * Static method to recursively transform an object into an associative array. + * @param mixed $obj: should be of class StdClass + * @return array + */ + static public function object2array($obj) + { + $arr_raw = is_object($obj) ? get_object_vars($obj) : $obj; + foreach ($arr_raw as $key => $val) { + $val = (is_array($val) || is_object($val)) ? self::object2array($val) : $val; + $arr[$key] = $val; + } + return $arr; + } + + + /** + * Static method only to keep the translationstrings of the values. It is + * never used within the system. + */ + static private function localization() + { + _("Lehre"); + _("Forschung"); + _("Organisation"); + _("Community"); + _("Arbeitsgruppen"); + _("importierte Kurse"); + _("Hauptveranstaltungen"); + + _("Hier finden Sie alle in Stud.IP registrierten Lehrveranstaltungen"); + _("Verwenden Sie diese Kategorie, um normale Lehrveranstaltungen anzulegen"); + _("Hier finden Sie virtuelle Veranstaltungen zum Thema Forschung an der Universität"); + _("In dieser Kategorie können Sie virtuelle Veranstaltungen für Forschungsprojekte anlegen."); + _("Hier finden Sie virtuelle Veranstaltungen zu verschiedenen Gremien an der Universität"); + _("Um virtuelle Veranstaltungen für Uni-Gremien anzulegen, verwenden Sie diese Kategorie"); + _("Hier finden Sie virtuelle Veranstaltungen zu unterschiedlichen Themen"); + _("Wenn Sie Veranstaltungen als Diskussiongruppen zu unterschiedlichen Themen anlegen möchten, verwenden Sie diese Kategorie."); + _("Hier finden Sie verschiedene Arbeitsgruppen an der %s"); + _("Verwenden Sie diese Kategorie, um unterschiedliche Arbeitsgruppen anzulegen."); + _("Veranstaltungen dieser Kategorie dienen als Gruppierungselement, um die Zusammengehörigkeit von Veranstaltungen anderer Kategorien abzubilden."); + } + +} diff --git a/lib/classes/SemType.class.php b/lib/classes/SemType.class.php deleted file mode 100644 index 5be1f19..0000000 --- a/lib/classes/SemType.class.php +++ /dev/null @@ -1,257 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -if (isset($GLOBALS['SEM_TYPE'])) { - $GLOBALS['SEM_TYPE_OLD_VAR'] = $GLOBALS['SEM_TYPE']; -} - -/** - * Class to define and manage attributes of seminar types. - * Usually all sem-types are stored in a global variable $SEM_TYPE which is - * an array of SemType objects. - * - * SemType::getTypes() gets you all seminar types in an array. - * - * This class only represents the name of the type and gives a relation to a - * sem_class. - */ -class SemType implements ArrayAccess -{ - protected $data = []; - static protected $sem_types = null; - - /** - * Constructor can be set with integer of sem_class_id or an array of - * the old $SEM_CLASS style. - * @param integer | array $data - */ - public function __construct($data) { - $db = DBManager::get(); - if (is_int($data)) { - $statement = $db->prepare("SELECT * FROM sem_types WHERE id = :id "); - $statement->execute(['id' => $data]); - $this->data = $statement->fetch(PDO::FETCH_ASSOC); - } else { - $this->data = $data; - } - } - - /** - * Returns the number of seminars of this sem_type in Stud.IP - * @return integer - */ - public function countSeminars() { - $db = DBManager::get(); - $statement = $db->prepare("SELECT COUNT(*) FROM seminare WHERE status = :sem_type "); - $statement->execute(['sem_type' => $this->data['id']]); - return (int) $statement->fetch(PDO::FETCH_COLUMN, 0); - } - - /** - * stores all data in the database - * @return boolean success - */ - public function store() { - $db = DBManager::get(); - $statement = $db->prepare( - "UPDATE sem_types " . - "SET name = :name, " . - "class = :class, " . - "chdate = UNIX_TIMESTAMP() " . - "WHERE id = :id ". - ""); - \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); - return $statement->execute([ - 'id' => $this->data['id'], - 'name' => $this->data['name'], - 'class' => $this->data['class'] - ]); - } - - /** - * Deletes the sem_type-object. Will only delete, - * if there are no seminars in this sem_type. - * Remember to refresh the global $SEM_TYPE array. - * @return boolean : success of deletion - */ - public function delete() { - if ($this->countSeminars() === 0) { - $db = DBManager::get(); - $statement = $db->prepare(" - DELETE FROM sem_types - WHERE id = :id - "); - \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); - return $statement->execute([ - 'id' => $this->data['id'] - ]); - } else { - return false; - } - } - - /** - * Sets an attribute of sem_type->data - * @param string $offset - * @param mixed $value - */ - public function set($offset, $value) { - $this->data[$offset] = $value; - } - - public function getClass() { - return $GLOBALS['SEM_CLASS'][$this->data['class']] ?? SemClass::getDefaultSemClass(); - } - - /*************************************************************************** - * ArrayAccess methods * - ***************************************************************************/ - - /** - * deprecated, does nothing, should not be used - * @param string $offset - * @param mixed $value - */ - - public function offsetSet($offset, $value): void - { - } - - /** - * Compatibility function with old $SEM_TYPE variable for plugins. Maps the - * new array-structure to the old boolean values. - * @param integer $offset: name of attribute - */ - public function offsetGet($offset): mixed - { - switch ($offset) { - case "name": - return gettext($this->data['name']); - case in_array($offset, ["title_dozent", "title_tutor", "title_autor"]): - $sem_class = $this->getClass(); - $title = [$sem_class[$offset], $sem_class[$offset.'_plural']]; - return $title[0] || $title[1] ? $title : $this->data[$offset]; - } - //ansonsten - return $this->data[$offset]; - } - - /** - * ArrayAccess method to check if an attribute exists. - * @param mixed $offset - */ - public function offsetExists($offset): bool - { - return isset($this->data[$offset]); - } - - /** - * deprecated, does nothing, should not be used - * @param string $offset - */ - public function offsetUnset($offset): void - { - } - - /*************************************************************************** - * static methods * - ***************************************************************************/ - - /** - * Returns an array of all SemTypes in Stud.IP. Equivalent to global - * $SEM_TYPE variable. This variable is statically stored in this class. - * @return array of SemType - */ - static public function getTypes() { - if (!is_array(self::$sem_types)) { - $db = DBManager::get(); - self::$sem_types = []; - - $cache = \Studip\Cache\Factory::getCache(); - $types_array = unserialize($cache->read('DB_SEM_TYPES_ARRAY')); - if (!$types_array) { - try { - $statement = $db->prepare( - "SELECT * FROM sem_types ORDER BY id ASC " - ); - $statement->execute(); - $types_array = $statement->fetchAll(PDO::FETCH_ASSOC); - if ($types_array) { - $cache = \Studip\Cache\Factory::getCache(); - $cache->write('DB_SEM_TYPES_ARRAY', serialize($types_array)); - } - } catch (PDOException $e) { - //for use without or before migration 92 - $type_array = $GLOBALS['SEM_TYPE_OLD_VAR']; - if (is_array($type_array)) { - ksort($type_array); - foreach ($type_array as $id => $type) { - self::$sem_types[$id] = new SemType($type); - } - } else { - self::$sem_types[1] = new SemType(['name' => 'default', 'class' => 1, 'id' => 1]); - } - } - } - foreach ($types_array as $sem_type) { - self::$sem_types[$sem_type['id']] = new SemType($sem_type); - } - } - return self::$sem_types; - } - - static public function refreshTypes() { - self::$sem_types = null; - \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); - return self::getTypes(); - } - - /** - * Gets all SemTypes that are allowed as group parents. - * @return array - */ - public static function getGroupingSemTypes() - { - return SimpleCollection::createFromArray(array_flatten(SemClass::getGroupClasses()->getSemTypes()))->pluck('id'); - } - - /** - * Gets all SemTypes that are allowed as group parents. - * @return array - */ - public static function getNonGroupingSemTypes() - { - $non_grouping = SimpleCollection::createFromArray(SemClass::getClasses())->findBy('is_group', false)->findBy('studygroup_mode', false); - return SimpleCollection::createFromArray(array_flatten($non_grouping->getSemTypes()))->pluck('id'); - } - - /** - * Static method only to keep the translationstrings of the values. It is - * never used within the system. - */ - static private function localization() { - _("Vorlesung"); - _("Seminar"); - _("Übung"); - _("Praktikum"); - _("Colloquium"); - _("Kolloquium"); - _("Forschungsgruppe"); - _("sonstige"); - _("Gremium"); - _("Projektgruppe"); - _("Kulturforum"); - _("Veranstaltungsboard"); - _("Studiengruppe"); - - } - -} diff --git a/lib/classes/SemType.php b/lib/classes/SemType.php new file mode 100644 index 0000000..5be1f19 --- /dev/null +++ b/lib/classes/SemType.php @@ -0,0 +1,257 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +if (isset($GLOBALS['SEM_TYPE'])) { + $GLOBALS['SEM_TYPE_OLD_VAR'] = $GLOBALS['SEM_TYPE']; +} + +/** + * Class to define and manage attributes of seminar types. + * Usually all sem-types are stored in a global variable $SEM_TYPE which is + * an array of SemType objects. + * + * SemType::getTypes() gets you all seminar types in an array. + * + * This class only represents the name of the type and gives a relation to a + * sem_class. + */ +class SemType implements ArrayAccess +{ + protected $data = []; + static protected $sem_types = null; + + /** + * Constructor can be set with integer of sem_class_id or an array of + * the old $SEM_CLASS style. + * @param integer | array $data + */ + public function __construct($data) { + $db = DBManager::get(); + if (is_int($data)) { + $statement = $db->prepare("SELECT * FROM sem_types WHERE id = :id "); + $statement->execute(['id' => $data]); + $this->data = $statement->fetch(PDO::FETCH_ASSOC); + } else { + $this->data = $data; + } + } + + /** + * Returns the number of seminars of this sem_type in Stud.IP + * @return integer + */ + public function countSeminars() { + $db = DBManager::get(); + $statement = $db->prepare("SELECT COUNT(*) FROM seminare WHERE status = :sem_type "); + $statement->execute(['sem_type' => $this->data['id']]); + return (int) $statement->fetch(PDO::FETCH_COLUMN, 0); + } + + /** + * stores all data in the database + * @return boolean success + */ + public function store() { + $db = DBManager::get(); + $statement = $db->prepare( + "UPDATE sem_types " . + "SET name = :name, " . + "class = :class, " . + "chdate = UNIX_TIMESTAMP() " . + "WHERE id = :id ". + ""); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + return $statement->execute([ + 'id' => $this->data['id'], + 'name' => $this->data['name'], + 'class' => $this->data['class'] + ]); + } + + /** + * Deletes the sem_type-object. Will only delete, + * if there are no seminars in this sem_type. + * Remember to refresh the global $SEM_TYPE array. + * @return boolean : success of deletion + */ + public function delete() { + if ($this->countSeminars() === 0) { + $db = DBManager::get(); + $statement = $db->prepare(" + DELETE FROM sem_types + WHERE id = :id + "); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + return $statement->execute([ + 'id' => $this->data['id'] + ]); + } else { + return false; + } + } + + /** + * Sets an attribute of sem_type->data + * @param string $offset + * @param mixed $value + */ + public function set($offset, $value) { + $this->data[$offset] = $value; + } + + public function getClass() { + return $GLOBALS['SEM_CLASS'][$this->data['class']] ?? SemClass::getDefaultSemClass(); + } + + /*************************************************************************** + * ArrayAccess methods * + ***************************************************************************/ + + /** + * deprecated, does nothing, should not be used + * @param string $offset + * @param mixed $value + */ + + public function offsetSet($offset, $value): void + { + } + + /** + * Compatibility function with old $SEM_TYPE variable for plugins. Maps the + * new array-structure to the old boolean values. + * @param integer $offset: name of attribute + */ + public function offsetGet($offset): mixed + { + switch ($offset) { + case "name": + return gettext($this->data['name']); + case in_array($offset, ["title_dozent", "title_tutor", "title_autor"]): + $sem_class = $this->getClass(); + $title = [$sem_class[$offset], $sem_class[$offset.'_plural']]; + return $title[0] || $title[1] ? $title : $this->data[$offset]; + } + //ansonsten + return $this->data[$offset]; + } + + /** + * ArrayAccess method to check if an attribute exists. + * @param mixed $offset + */ + public function offsetExists($offset): bool + { + return isset($this->data[$offset]); + } + + /** + * deprecated, does nothing, should not be used + * @param string $offset + */ + public function offsetUnset($offset): void + { + } + + /*************************************************************************** + * static methods * + ***************************************************************************/ + + /** + * Returns an array of all SemTypes in Stud.IP. Equivalent to global + * $SEM_TYPE variable. This variable is statically stored in this class. + * @return array of SemType + */ + static public function getTypes() { + if (!is_array(self::$sem_types)) { + $db = DBManager::get(); + self::$sem_types = []; + + $cache = \Studip\Cache\Factory::getCache(); + $types_array = unserialize($cache->read('DB_SEM_TYPES_ARRAY')); + if (!$types_array) { + try { + $statement = $db->prepare( + "SELECT * FROM sem_types ORDER BY id ASC " + ); + $statement->execute(); + $types_array = $statement->fetchAll(PDO::FETCH_ASSOC); + if ($types_array) { + $cache = \Studip\Cache\Factory::getCache(); + $cache->write('DB_SEM_TYPES_ARRAY', serialize($types_array)); + } + } catch (PDOException $e) { + //for use without or before migration 92 + $type_array = $GLOBALS['SEM_TYPE_OLD_VAR']; + if (is_array($type_array)) { + ksort($type_array); + foreach ($type_array as $id => $type) { + self::$sem_types[$id] = new SemType($type); + } + } else { + self::$sem_types[1] = new SemType(['name' => 'default', 'class' => 1, 'id' => 1]); + } + } + } + foreach ($types_array as $sem_type) { + self::$sem_types[$sem_type['id']] = new SemType($sem_type); + } + } + return self::$sem_types; + } + + static public function refreshTypes() { + self::$sem_types = null; + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + return self::getTypes(); + } + + /** + * Gets all SemTypes that are allowed as group parents. + * @return array + */ + public static function getGroupingSemTypes() + { + return SimpleCollection::createFromArray(array_flatten(SemClass::getGroupClasses()->getSemTypes()))->pluck('id'); + } + + /** + * Gets all SemTypes that are allowed as group parents. + * @return array + */ + public static function getNonGroupingSemTypes() + { + $non_grouping = SimpleCollection::createFromArray(SemClass::getClasses())->findBy('is_group', false)->findBy('studygroup_mode', false); + return SimpleCollection::createFromArray(array_flatten($non_grouping->getSemTypes()))->pluck('id'); + } + + /** + * Static method only to keep the translationstrings of the values. It is + * never used within the system. + */ + static private function localization() { + _("Vorlesung"); + _("Seminar"); + _("Übung"); + _("Praktikum"); + _("Colloquium"); + _("Kolloquium"); + _("Forschungsgruppe"); + _("sonstige"); + _("Gremium"); + _("Projektgruppe"); + _("Kulturforum"); + _("Veranstaltungsboard"); + _("Studiengruppe"); + + } + +} diff --git a/lib/classes/Seminar.class.php b/lib/classes/Seminar.class.php deleted file mode 100644 index a2d69ea..0000000 --- a/lib/classes/Seminar.class.php +++ /dev/null @@ -1,2439 +0,0 @@ - - * @author Stefan Suchi - * @author Suchi & Berg GmbH - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once 'lib/dates.inc.php'; - -class Seminar -{ - var $issues = null; // Array of Issue - var $irregularSingleDates = null; // Array of SingleDates - var $messages = []; // occured errors, infos, and warnings - var $semester = null; - var $filterStart = 0; - var $filterEnd = 0; - var $hasDatesOutOfDuration = -1; - var $message_stack = []; - - var $user_number = 0;//? - var $commands; //? - var $BookedRoomsStatTemp; //??? - - var $request_id;//TODO - var $requestData; - var $room_request; - - private $_metadate = null; // MetaDate - - private $alias = [ - 'seminar_number' => 'VeranstaltungsNummer', - 'subtitle' => 'Untertitel', - 'description' => 'Beschreibung', - 'location' => 'Ort', - 'misc' => 'Sonstiges', - 'read_level' => 'Lesezugriff', - 'write_level' => 'Schreibzugriff', - 'semester_start_time' => 'start_time', - 'semester_duration_time' => 'duration_time', - 'form' => 'art', - 'participants' => 'teilnehmer', - 'requirements' => 'vorrausetzungen', - 'orga' => 'lernorga', - ]; - - private $course = null; - - private $course_set = null; - - private static $seminar_object_pool; - - public static function GetInstance($id = false, $refresh_cache = false) - { - if ($id) { - if ($refresh_cache) { - self::$seminar_object_pool[$id] = null; - } - if (!empty(self::$seminar_object_pool[$id]) && is_object(self::$seminar_object_pool[$id]) && self::$seminar_object_pool[$id]->getId() == $id) { - return self::$seminar_object_pool[$id]; - } else { - self::$seminar_object_pool[$id] = new Seminar($id); - return self::$seminar_object_pool[$id]; - } - } else { - return new Seminar(false); - } - } - - public static function setInstance(Seminar $seminar) - { - return self::$seminar_object_pool[$seminar->id] = $seminar; - } - - /** - * Constructor - * - * Pass nothing to create a seminar, or the seminar_id from an existing seminar to change or delete - * @access public - * @param string $seminar_id the seminar to be retrieved - */ - public function __construct($course_or_id = FALSE) - { - $course = Course::toObject($course_or_id); - if ($course) { - $this->course = $course; - } elseif ($course_or_id === false) { - $this->course = new Course(); - $this->course->setId($this->course->getNewId()); - } else { //hmhmhm - throw new Exception(sprintf(_('Fehler: Konnte das Seminar mit der ID %s nicht finden!'), $course_or_id)); - } - } - - public function __get($field) - { - if ($field == 'is_new') { - return $this->course->isNew(); - } - if ($field == 'metadate') { - if ($this->_metadate === null) { - $this->_metadate = new MetaDate($this->id); - $this->_metadate->setSeminarStartTime($this->start_time); - $this->_metadate->setSeminarDurationTime($this->duration_time); - } - return $this->_metadate; - } - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - return $this->course->$field; - } - - public function __set($field, $value) - { - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - if ($field == 'metadate') { - return $this->_metadate = $value; - } - return $this->course->$field = $value; - } - - public function __isset($field) - { - if ($field == 'metadate') { - return is_object($this->_metadate); - } - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - return isset($this->course->$field); - } - - public function __call($method, $params) - { - return call_user_func_array([$this->course, $method], $params); - } - - public static function GetSemIdByDateId($date_id) - { - $stmt = DBManager::get()->prepare("SELECT range_id FROM termine WHERE termin_id = ? LIMIT 1"); - $stmt->execute([$date_id]); - return $stmt->fetchColumn(); - } - - /** - * - * creates an new id for this object - * @access private - * @return string the unique id - */ - public function createId() - { - return $this->course->getNewId(); - } - - public function getMembers($status = 'dozent') - { - $ret = []; - foreach($this->course->getMembersWithStatus($status) as $m) { - $ret[$m->user_id]['user_id'] = $m->user_id; - $ret[$m->user_id]['username'] = $m->username; - $ret[$m->user_id]['Vorname'] = $m->vorname; - $ret[$m->user_id]['Nachname'] = $m->nachname; - $ret[$m->user_id]['Email'] = $m->email; - $ret[$m->user_id]['position'] = $m->position; - $ret[$m->user_id]['label'] = $m->label; - $ret[$m->user_id]['status'] = $m->status; - $ret[$m->user_id]['mkdate'] = $m->mkdate; - $ret[$m->user_id]['fullname'] = $m->getUserFullname(); - } - return $ret; - } - - public function getAdmissionMembers($status = 'awaiting') - { - $ret = []; - foreach($this->course->admission_applicants->findBy('status', $status)->orderBy('position nachname') as $m) { - $ret[$m->user_id]['user_id'] = $m->user_id; - $ret[$m->user_id]['username'] = $m->username; - $ret[$m->user_id]['Vorname'] = $m->vorname; - $ret[$m->user_id]['Nachname'] = $m->nachname; - $ret[$m->user_id]['Email'] = $m->email; - $ret[$m->user_id]['position'] = $m->position; - $ret[$m->user_id]['status'] = $m->status; - $ret[$m->user_id]['mkdate'] = $m->mkdate; - $ret[$m->user_id]['fullname'] = $m->getUserFullname(); - } - return $ret; - } - - public function getId() - { - return $this->id; - } - - public function getName() - { - return $this->name; - } - - /** - * return the field VeranstaltungsNummer for the seminar - * - * @return string the seminar-number for the current seminar - */ - public function getNumber() - { - return $this->seminar_number; - } - - public function isVisible() - { - return $this->visible; - } - - public function getInstitutId() - { - return $this->institut_id; - } - - public function getSemesterStartTime() - { - return $this->semester_start_time; - } - - public function getSemesterDurationTime() - { - return $this->semester_duration_time; - } - - public function getNextDate($return_mode = 'string') - { - $next_date = ''; - if ($return_mode == 'int') { - echo __class__.'::'.__function__.', line '.__line__.', return_mode "int" ist not supported by this function!';die; - } - - if (!$termine = SeminarDB::getNextDate($this->id)) - return false; - - foreach ($termine['termin'] as $singledate_id) { - $next_date .= DateFormatter::formatDateAndRoom($singledate_id, $return_mode) . '
'; - } - - if (!empty($termine['ex_termin'])) { - foreach ($termine['ex_termin'] as $ex_termin_id) { - $ex_termin = new SingleDate($ex_termin_id); - $template = $GLOBALS['template_factory']->open('dates/missing_date.php'); - $template->formatted_date = DateFormatter::formatDateAndRoom($ex_termin_id, $return_mode); - $template->ex_termin = $ex_termin; - $missing_date = $template->render(); - - if (!empty($termine['termin'])) { - $termin = new SingleDate($termine['termin'][0]); - if ($ex_termin->getStartTime() <= $termin->getStartTime()) { - return $next_date . $missing_date; - } else { - return $next_date; - } - } else { - return $missing_date; - } - } - } else { - return $next_date; - } - - return false; - } - - public function getFirstDate($return_mode = 'string') { - if (!$dates = SeminarDB::getFirstDate($this->id)) { - return false; - } - - return DateFormatter::formatDateWithAllRooms(['termin' => $dates], $return_mode); - } - - /** - * This function returns an associative array of the dates owned by this seminar - * - * @returns mixed a multidimensional array of seminar-dates - */ - public function getUndecoratedData($filter = false) - { - - // Caching - $cache = \Studip\Cache\Factory::getCache(); - $cache_key = 'course/undecorated_data/'. $this->id; - - if ($filter) { - $sub_key = ($_SESSION['_language'] ?? 'none') .'/'. $this->filterStart .'-'. $this->filterEnd; - } else { - $sub_key = ($_SESSION['_language'] ?? 'none') .'/unfiltered'; - } - - $data = unserialize($cache->read($cache_key)); - - // build cache from scratch - if (empty($data) || empty($data[$sub_key])) { - $cycles = $this->metadate->getCycleData(); - $dates = $this->getSingleDates($filter, $filter); - $rooms = []; - - foreach (array_keys($cycles) as $id) { - if ($this->filterStart && $this->filterEnd - && !$this->metadate->hasDates($id, $this->filterStart, $this->filterEnd)) - { - unset($cycles[$id]); - continue; - } - - $cycles[$id]['first_date'] = CycleDataDB::getFirstDate($id); - $cycles[$id]['last_date'] = CycleDataDB::getLastDate($id); - if (!empty($cycles[$id]['assigned_rooms'])) { - foreach ($cycles[$id]['assigned_rooms'] as $room_id => $count) { - if (!isset($rooms[$room_id])) { - $rooms[$room_id] = 0; - } - $rooms[$room_id] += $count; - } - } - } - - // besser wieder mit direktem Query statt Objekten - if (is_array($cycles) && count($cycles) === 0) { - $cycles = false; - } - - $ret['regular']['turnus_data'] = $cycles; - - // the irregular single-dates - foreach ($dates as $val) { - $zw = [ - 'metadate_id' => $val->getMetaDateID(), - 'termin_id' => $val->getTerminID(), - 'date_typ' => $val->getDateType(), - 'start_time' => $val->getStartTime(), - 'end_time' => $val->getEndTime(), - 'mkdate' => $val->getMkDate(), - 'chdate' => $val->getMkDate(), - 'ex_termin' => $val->isExTermin(), - 'orig_ex' => $val->isExTermin(), - 'range_id' => $val->getRangeID(), - 'author_id' => $val->getAuthorID(), - 'resource_id' => $val->getResourceID(), - 'raum' => $val->getFreeRoomText(), - 'typ' => $val->getDateType(), - 'tostring' => $val->toString() - ]; - - if ($val->getResourceID()) { - if (!isset($rooms[$val->getResourceID()])) { - $rooms[$val->getResourceID()] = 0; - } - $rooms[$val->getResourceID()]++; - } - - $ret['irregular'][$val->getTerminID()] = $zw; - } - - $ret['rooms'] = $rooms; - $ret['ort'] = $this->location; - - $data[$sub_key] = $ret; - - // write data to cache - $cache->write($cache_key, serialize($data), 600); - } - - return $data[$sub_key]; - } - - public function getFormattedTurnus($short = FALSE) - { - // activate this with StEP 00077 - /* $cache = Cache::instance(); - * $cache_key = "formatted_turnus".$this->id; - * if (! $return_string = $cache->read($cache_key)) - * { - */ - return $this->getDatesExport(['short' => $short, 'shrink' => true]); - - // activate this with StEP 00077 - // $cache->write($cache_key, $return_string, 60*60); - // } - } - - public function getFormattedTurnusDates($short = FALSE) - { - if ($cycles = $this->metadate->getCycles()) { - $return_string = []; - foreach ($cycles as $id => $c) { - $return_string[$id] = $c->toString($short); - //hmm tja... - if ($c->description){ - $return_string[$id] .= ' ('. htmlReady($c->description) .')'; - } - } - return $return_string; - } else - return FALSE; - } - - public function getMetaDateCount() - { - return sizeof($this->metadate->cycles); - } - - public function getMetaDateValue($key, $value_name) - { - return $this->metadate->cycles[$key]->$value_name; - } - - public function setMetaDateValue($key, $value_name, $value) - { - $this->metadate->cycles[$key]->$value_name = $value; - } - - /** - * restore the data - * - * the complete data of the object will be loaded from the db - * @access public - * @throws Exception if there is no such course - * @return boolean always true - */ - public function restore() - { - if ($this->course->id) { - $this->course->restore(); - } - $this->irregularSingleDates = null; - $this->issues = null; - $this->_metadate = null; - $this->course_set = null; - - return TRUE; - } - - /** - * returns an array of variables from the seminar-object, excluding variables - * containing objects or arrays - * - * @return array - */ - public function getSettings() { - $settings = $this->course->toRawArray(); - unset($settings['config']); - return $settings; - } - - public function store($trigger_chdate = true) - { - // activate this with StEP 00077 - // $cache = Cache::instance(); - // $cache->expire("formatted_turnus".$this->id); - - //check for security consistency - if ($this->write_level < $this->read_level) // hier wusste ein Lehrender nicht, was er tat - $this->write_level = $this->read_level; - - if ($this->irregularSingleDates) { - foreach ($this->irregularSingleDates as $val) { - $val->store(); - } - } - - if ($this->issues) { - foreach ($this->issues as $val) { - $val->store(); - } - } - - $metadate_changed = isset($this->metadate) ? $this->metadate->store() : 0; - $course_changed = $this->course->store(); - if ($metadate_changed && $trigger_chdate) { - return $this->course->triggerChdate(); - } else { - return $course_changed ?: false; - } - } - - public function setStartSemester($start) - { - global $perm; - - if ($perm->have_perm('tutor') && $start != $this->semester_start_time) { - // logging >>>>>> - StudipLog::log("SEM_SET_STARTSEMESTER", $this->getId(), $start); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - $this->semester_start_time = $start; - $this->metadate->setSeminarStartTime($start); - $this->createMessage(_("Das Startsemester wurde geändert.")); - $this->createInfo(_("Beachten Sie, dass Termine, die nicht mit den Einstellungen der regelmäßigen Zeit übereinstimmen (z.B. auf Grund einer Verschiebung der regelmäßigen Zeit), teilweise gelöscht sein könnten!")); - return TRUE; - } - return FALSE; - } - - public function removeAndUpdateSingleDates() - { - SeminarCycleDate::removeOutRangedSingleDates( - $this->semester_start_time, - $this->getEndSemesterVorlesEnde(), - $this->id - ); - - foreach ($this->metadate->cycles as $key => $val) { - $this->metadate->cycles[$key]->readSingleDates(); - $this->metadate->createSingleDates($key); - $this->metadate->cycles[$key]->termine = NULL; - } - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - } - - public function getStartSemester() - { - return $this->semester_start_time; - } - - /* - * setEndSemester - * @param end integer 0 (one Semester), -1 (eternal), or timestamp of last happening semester - * @returns TRUE on success, FALSE on failure - */ - public function setEndSemester($end) - { - global $perm; - - $previousEndSemester = $this->getEndSemester(); // save the end-semester before it is changed, so we can choose lateron in which semesters we need to be rebuilt the SingleDates - - if ($end != $this->getEndSemester()) { // only change Duration if it differs from the current one - - if ($end == 0) { // the seminar takes place just in the selected start-semester - $this->semester_duration_time = 0; - $this->metadate->setSeminarDurationTime(0); - // logging >>>>>> - StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end, 'Laufzeit: 1 Semester'); - // logging <<<<<< - } else if ($end == -1) { // the seminar takes place in every semester above and including the start-semester - // logging >>>>>> - StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end, 'Laufzeit: unbegrenzt'); - // logging <<<<<< - $this->semester_duration_time = -1; - $this->metadate->setSeminarDurationTime(-1); - SeminarCycleDate::removeOutRangedSingleDates( - $this->semester_start_time, - $this->getEndSemesterVorlesEnde(), - $this->id - ); - } else { // the seminar takes place between the selected start~ and end-semester - // logging >>>>>> - StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end); - // logging <<<<<< - $this->semester_duration_time = $end - $this->semester_start_time; // the duration is stored, not the real end-point - $this->metadate->setSeminarDurationTime($this->semester_duration_time); - } - - $this->createMessage(_("Die Dauer wurde geändert.")); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - - /* - * If the duration has been changed, we have to create new SingleDates - * if the new duration is longer than the previous one - */ - if ( ($previousEndSemester != -1) && ( ($previousEndSemester < $this->getEndSemester()) || (($previousEndSemester == 0) && ($this->getEndSemester() == -1) ) )) { - // if the previous duration was unlimited, the only option choosable is - // a shorter duration then 'ever', so there cannot be any new SingleDates - - // special case: if the previous selection was 'one semester' and the new one is 'eternal', - // than we have to find out the end of the only semester, the start-semester - if ($previousEndSemester == 0) { - $startAfterTimeStamp = $this->course->start_semester->ende; - } else { - $startAfterTimeStamp = $previousEndSemester; - } - - foreach ($this->metadate->cycles as $key => $val) { - $this->metadate->createSingleDates(['metadate_id' => $key, 'startAfterTimeStamp' => $startAfterTimeStamp]); - $this->metadate->cycles[$key]->termine = NULL; // emtpy the SingleDates for each cycle, so that SingleDates, which were not in the current view, are not loaded and therefore should not be visible - } - } - } - - return TRUE; - } - - /* - * getEndSemester - * @returns 0 (one Semester), -1 (eternal), or TimeStamp of last Semester for this Seminar - */ - public function getEndSemester() - { - if ($this->semester_duration_time == 0) return 0; // seminar takes place only in the start-semester - if ($this->semester_duration_time == -1) return -1; // seminar takes place eternally - return $this->semester_start_time + $this->semester_duration_time; // seminar takes place between start~ and end-semester - } - - public function getEndSemesterVorlesEnde() - { - if ($this->semester_duration_time == -1) { - $semesters = Semester::getAll(); - $very_last_semester = array_pop($semesters); - return $very_last_semester->vorles_ende; - } - return $this->course->end_semester->vorles_ende; - } - - /** - * return the name of the seminars start-semester - * - * @return string the name of the start-semester or false if there is no start-semester - */ - public function getStartSemesterName() - { - return $this->course->start_semester->name; - } - - /** - * return an array of singledate-objects for the submitted cycle identified by metadate_id - * - * @param string $metadate_id the id identifying the cycle - * - * @return mixed an array of singledate-objects - */ - public function readSingleDatesForCycle($metadate_id) - { - return $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - } - - public function readSingleDates($force = FALSE, $filter = FALSE) - { - if (!$force) { - if (is_array($this->irregularSingleDates)) { - return TRUE; - } - } - $this->irregularSingleDates = []; - - if ($filter) { - $data = SeminarDB::getSingleDates($this->id, $this->filterStart, $this->filterEnd); - } else { - $data = SeminarDB::getSingleDates($this->id); - } - - foreach ($data as $val) { - unset($termin); - $termin = new SingleDate(); - $termin->fillValuesFromArray($val); - $this->irregularSingleDates[$val['termin_id']] =& $termin; - } - } - - public function &getSingleDate($singleDateID, $cycle_id = '') - { - if ($cycle_id == '') { - $this->readSingleDates(); - return $this->irregularSingleDates[$singleDateID]; - } else { - $dates = $this->metadate->getSingleDates($cycle_id, $this->filterStart, $this->filterEnd); - $data =& $dates; - return $data[$singleDateID]; - } - } - - public function &getSingleDates($filter = false, $force = false, $include_deleted_dates = false) - { - $this->readSingleDates($force, $filter); - if (!$include_deleted_dates) { - return $this->irregularSingleDates; - } else { - $deleted_dates = []; - foreach (SeminarDB::getDeletedSingleDates($this->getId(), $this->filterStart, $this->filterEnd) as $val) { - $termin = new SingleDate(); - $termin->fillValuesFromArray($val); - $deleted_dates[$val['termin_id']] = $termin; - } - $dates = array_merge($this->irregularSingleDates, $deleted_dates); - uasort($dates, function($a,$b) { - if ($a->getStartTime() == $b->getStartTime()) return 0; - return $a->getStartTime() < $b->getStartTime() ? -1 : 1;} - ); - return $dates; - } - } - - public function getCycles() - { - return $this->metadate->getCycles(); - } - - public function &getSingleDatesForCycle($metadate_id) - { - if (!$this->metadate->cycles[$metadate_id]->termine) { - $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - if (!$this->metadate->cycles[$metadate_id]->termine) { - $this->readSingleDates(); - $this->metadate->createSingleDates($metadate_id, $this->irregularSingleDates); - $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - } - //$this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - } - $dates = $this->metadate->getSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - return $dates; - } - - public function readIssues($force = false) - { - if (!is_array($this->issues) || $force) { - $this->issues = []; - $data = SeminarDB::getIssues($this->id); - - foreach ($data as $val) { - unset($issue); - $issue = new Issue(); - $issue->fillValuesFromArray($val); - $this->issues[$val['issue_id']] =& $issue; - } - } - } - - public function addSingleDate(&$singledate) - { - // logging >>>>>> - StudipLog::log("SEM_ADD_SINGLEDATE", $this->getId(), $singledate->toString(), 'SingleDateID: '.$singledate->getTerminID()); - // logging <<<<<< - - $cache = \Studip\Cache\Factory::getCache(); - $cache->expire('course/undecorated_data/'. $this->getId()); - - $this->readSingleDates(); - $this->irregularSingleDates[$singledate->getSingleDateID()] =& $singledate; - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } - - public function addIssue(&$issue) - { - $this->readIssues(); - if ($issue instanceof Issue) { - $max = -1; - if (is_array($this->issues)) foreach ($this->issues as $val) { - if ($val->getPriority() > $max) { - $max = $val->getPriority(); - } - } - $max++; - $issue->setPriority($max); - $this->issues[$issue->getIssueID()] =& $issue; - return TRUE; - } else { - return FALSE; - } - } - - public function deleteSingleDate($date_id, $cycle_id = '') - { - $this->readSingleDates(); - // logging >>>>>> - StudipLog::log("SEM_DELETE_SINGLEDATE",$date_id, $this->getId(), 'Cycle_id: '.$cycle_id); - // logging <<<<<< - if ($cycle_id == '') { - $this->irregularSingleDates[$date_id]->delete(true); - unset ($this->irregularSingleDates[$date_id]); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } else { - $this->metadate->deleteSingleDate($cycle_id, $date_id, $this->filterStart, $this->filterEnd); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } - } - - public function cancelSingleDate($date_id, $cycle_id = '') - { - if ($cycle_id) { - return $this->deleteSingleDate($date_id, $cycle_id); - } - $this->readSingleDates(); - // logging >>>>>> - StudipLog::log("SEM_DELETE_SINGLEDATE",$date_id, $this->getId(), 'appointment cancelled'); - // logging <<<<<< - $this->irregularSingleDates[$date_id]->setExTermin(true); - $this->irregularSingleDates[$date_id]->store(); - unset ($this->irregularSingleDates[$date_id]); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } - - public function unDeleteSingleDate($date_id, $cycle_id = '') - { - // logging >>>>>> - StudipLog::log("SEM_UNDELETE_SINGLEDATE",$date_id, $this->getId(), 'Cycle_id: '.$cycle_id); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - if ($cycle_id == '') { - $termin = new SingleDate($date_id); - if (!$termin->isExTermin()) { - return false; - } - $termin->setExTermin(false); - $termin->store(); - return true; - } else { - return $this->metadate->unDeleteSingleDate($cycle_id, $date_id, $this->filterStart, $this->filterEnd); - } - } - - /** - * return all stacked messages as a multidimensional array - * - * The array has the following structure: - * array( 'type' => ..., 'message' ... ) - * where type is one of error, info and success - * - * @return mixed the array of stacked messages - */ - public function getStackedMessages() - { - if ( is_array( $this->message_stack ) ) { - $ret = []; - - // cycle through message types and set title and details appropriate - foreach ($this->message_stack as $type => $messages ) { - switch ( $type ) { - case 'error': - $ret['error'] = [ - 'title' => _("Es sind Fehler/Probleme aufgetreten!"), - 'details' => $this->message_stack['error'] - ]; - break; - - case 'info': - $ret['info'] = [ - 'title' => implode('
', $this->message_stack['info']), - 'details' => [] - ]; - break; - - case 'success': - $ret['success'] = [ - 'title' => _("Ihre Änderungen wurden gespeichert!"), - 'details' => $this->message_stack['success'] - ]; - break; - } - } - - return $ret; - } - - return false; - } - - /** - * return the next stacked messag-string - * - * @return string a message-string - */ - public function getNextMessage() - { - if ($this->messages[0]) { - $ret = $this->messages[0]; - unset ($this->messages[0]); - sort($this->messages); - return $ret; - } - return FALSE; - } - - /** - * stack an error-message - * - * @param string $text the message to stack - */ - public function createError($text) - { - $this->messages[] = 'error§'.$text.'§'; - $this->message_stack['error'][] = $text; - } - - /** - * stack an info-message - * - * @param string $text the message to stack - */ - public function createInfo($text) - { - $this->messages[] = 'info§'.$text.'§'; - $this->message_stack['info'][] = $text; - } - - /** - * stack a success-message - * - * @param string $text the message to stack - */ - public function createMessage($text) - { - $this->messages[] = 'msg§'.$text.'§'; - $this->message_stack['success'][] = $text; - } - - /** - * add an array of messages to the message-stack - * - * @param mixed $messages array of pre-marked message-strings - * @param bool returns true on success - */ - public function appendMessages( $messages ) - { - if (!is_array($messages)) return false; - - foreach ( $messages as $type => $msgs ) { - foreach ($msgs as $msg) { - $this->message_stack[$type][] = $msg; - } - } - return true; - } - - public function addCycle($data = []) - { - $new_id = $this->metadate->addCycle($data); - if($new_id){ - $this->setStartWeek($data['startWeek'], $new_id); - $this->setTurnus($data['turnus'], $new_id); - } - // logging >>>>>> - if($new_id){ - $cycle_info = $this->metadate->cycles[$new_id]->toString(); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - StudipLog::log("SEM_ADD_CYCLE", $this->getId(), NULL, $cycle_info, '
'.print_r($data,true).'
'); - } - // logging <<<<<< - return $new_id; - } - - /** - * Change a regular timeslot of the seminar. The data is passed as an array - * conatining the following fields: - * start_stunde, start_minute, end_stunde, end_minute - * description, turnus, startWeek, day, sws - * - * @param array $data the cycle-data - * - * @return void - */ - public function editCycle($data = []) - { - $cycle = $this->metadate->cycles[$data['cycle_id']]; - $new_start = mktime($data['start_stunde'], $data['start_minute']); - $new_end = mktime($data['end_stunde'], $data['end_minute']); - $old_start = mktime($cycle->getStartStunde(),$cycle->getStartMinute()); - $old_end = mktime($cycle->getEndStunde(), $cycle->getEndMinute()); - $do_changes = false; - - // check, if the new timeslot exceeds the old one - if (($new_start < $old_start) || ($new_end > $old_end) || ($data['day'] != $cycle->day) ) { - $has_bookings = false; - - // check, if there are any booked rooms - foreach($cycle->getSingleDates() as $singleDate) { - if ($singleDate->getStarttime() > (time() - 3600) && $singleDate->hasRoom()) { - $has_bookings = true; - break; - } - } - - // if the timeslot exceeds the previous one and has some booked rooms - // they would be lost, so ask the user for permission to do so. - if (!$data['really_change'] && $has_bookings) { - $link_params = [ - 'editCycle_x' => '1', - 'editCycle_y' => '1', - 'cycle_id' => $data['cycle_id'], - 'start_stunde' => $data['start_stunde'], - 'start_minute' => $data['start_minute'], - 'end_stunde' => $data['end_stunde'], - 'end_minute' => $data['end_minute'], - 'day' => $data['day'], - 'really_change' => 'true' - ]; - $question = _("Wenn Sie die regelmäßige Zeit auf %s ändern, verlieren Sie die Raumbuchungen für alle in der Zukunft liegenden Termine!") - ."\n". _("Sind Sie sicher, dass Sie die regelmäßige Zeit ändern möchten?"); - $question_time = '**'. strftime('%A', $data['day']) .', '. $data['start_stunde'] .':'. $data['start_minute'] - .' - '. $data['end_stunde'] .':'. $data['end_minute'] .'**'; - - echo (string)QuestionBox::create( - sprintf($question, $question_time), - URLHelper::getURL('', $link_params) - ); - - } else { - $do_changes = true; - } - } else { - $do_changes = true; - } - - $messages = false; - $same_time = false; - - // only apply changes, if the user approved the change or - // the change does not need any approval - if ($do_changes) { - if ($data['description'] != $cycle->getDescription()) { - $this->createMessage(_("Die Beschreibung des regelmäßigen Eintrags wurde geändert.")); - $message = true; - $do_changes = true; - } - - if ($old_start == $new_start && $old_end == $new_end) { - $same_time = true; - } - if ($data['startWeek'] != $cycle->week_offset) { - $this->setStartWeek($data['startWeek'], $cycle->metadate_id); - $message = true; - $do_changes = true; - } - if ($data['turnus'] != $cycle->cycle) { - $this->setTurnus($data['turnus'], $cycle->metadate_id); - $message = true; - $do_changes = true; - } - if ($data['day'] != $cycle->day) { - $message = true; - $same_time = false; - $do_changes = true; - } - if (round(str_replace(',','.', $data['sws']),1) != $cycle->sws) { - $cycle->sws = $data['sws']; - $this->createMessage(_("Die Semesterwochenstunden für Lehrende des regelmäßigen Eintrags wurden geändert.")); - $message = true; - $do_changes = true; - } - - $change_from = $cycle->toString(); - if ($this->metadate->editCycle($data)) { - if (!$same_time) { - // logging >>>>>> - StudipLog::log("SEM_CHANGE_CYCLE", $this->getId(), NULL, $change_from .' -> '. $cycle->toString()); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - $this->createMessage(sprintf(_("Die regelmäßige Veranstaltungszeit wurde auf \"%s\" für alle in der Zukunft liegenden Termine geändert!"), - ''.getWeekday($data['day']) . ', ' . $data['start_stunde'] . ':' . $data['start_minute'].' - '. - $data['end_stunde'] . ':' . $data['end_minute'] . '')); - $message = true; - } - } else { - if (!$same_time) { - $this->createInfo(sprintf(_("Die regelmäßige Veranstaltungszeit wurde auf \"%s\" geändert, jedoch gab es keine Termine die davon betroffen waren."), - ''.getWeekday($data['day']) . ', ' . $data['start_stunde'] . ':' . $data['start_minute'].' - '. - $data['end_stunde'] . ':' . $data['end_minute'] . '')); - $message = true; - } - } - $this->metadate->sortCycleData(); - - if (!$message) { - $this->createInfo("Sie haben keine Änderungen vorgenommen!"); - } - } - } - - public function deleteCycle($cycle_id) - { - // logging >>>>>> - $cycle_info = $this->metadate->cycles[$cycle_id]->toString(); - StudipLog::log("SEM_DELETE_CYCLE", $this->getId(), NULL, $cycle_info); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - return $this->metadate->deleteCycle($cycle_id); - } - - public function setTurnus($turnus, $metadate_id = false) - { - if ($this->metadate->getTurnus($metadate_id) != $turnus) { - $this->metadate->setTurnus($turnus, $metadate_id); - $key = $metadate_id ? $metadate_id : $this->metadate->getFirstMetadate()->metadate_id; - $this->createMessage(sprintf(_("Der Turnus für den Termin %s wurde geändert."), $this->metadate->cycles[$key]->toString())); - $this->metadate->createSingleDates($key); - $this->metadate->cycles[$key]->termine = null; - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - } - return TRUE; - } - - public function getTurnus($metadate_id = false) - { - return $this->metadate->getTurnus($metadate_id); - } - - - /** - * get StatOfNotBookedRooms returns an array: - * open: number of rooms with no booking - * all: number of singleDates, which can have a booking - * open_rooms: array of singleDates which have no booking - * - * @param String $cycle_id Id of cycle - * @return array as described above - */ - public function getStatOfNotBookedRooms($cycle_id) - { - if (!isset($this->BookedRoomsStatTemp[$cycle_id])) { - $this->BookedRoomsStatTemp[$cycle_id] = SeminarDB::getStatOfNotBookedRooms($cycle_id, $this->id, $this->filterStart, $this->filterEnd); - } - return $this->BookedRoomsStatTemp[$cycle_id]; - } - - public function getStatus() - { - return $this->status; - } - - public function getBookedRoomsTooltip($cycle_id) - { - $stat = $this->getStatOfNotBookedRooms($cycle_id); - $pattern = '%s , %s, %s-%s
'; - $return = ''; - if ($stat['open'] > 0 && $stat['open'] !== $stat['all']) { - $return = _('Folgende Termine haben keine Raumbuchung:') . '
'; - - foreach ($stat['open_rooms'] as $aSingleDate) { - $return .= sprintf($pattern,strftime('%a', $aSingleDate['date']), - strftime('%d.%m.%Y', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['end_time'])); - } - } - - // are there any dates with declined room-requests? - if ($stat['declined'] > 0) { - $return .= _('Folgende Termine haben eine abgelehnte Raumanfrage') . '
'; - foreach ($stat['declined_dates'] as $aSingleDate) { - $return .= sprintf($pattern,strftime('%a', $aSingleDate['date']), - strftime('%d.%m.%Y', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['end_time'])); - } - } - - return $return; - } - - /** - * @param $cycle_id - * @return string - */ - public function getCycleColorClass($cycle_id) - { - if (Config::get()->RESOURCES_ENABLE && Config::get()->RESOURCES_ENABLE_BOOKINGSTATUS_COLORING) { - if (!$this->metadate->hasDates($cycle_id, $this->filterStart, $this->filterEnd)) { - return 'red'; - } - - $stat = $this->getStatOfNotBookedRooms($cycle_id); - - if ($stat['open'] > 0 && $stat['open'] == $stat['all']) { - return 'red'; - } - if ($stat['open'] > 0) { - return 'yellow '; - } - return 'green '; - } - - return ''; - } - - public function &getIssues($force = false) - { - $this->readIssues($force); - $this->renumberIssuePrioritys(); - if (is_array($this->issues)) { - uasort($this->issues, function ($a, $b) { - return $a->getPriority() - $b->getPriority(); - }); - } - return $this->issues; - } - - public function deleteIssue($issue_id) - { - $this->issues[$issue_id]->delete(); - unset($this->issues[$issue_id]); - return TRUE; - } - - public function &getIssue($issue_id) - { - $this->readIssues(); - return $this->issues[$issue_id]; - } - - /* - * changeIssuePriority - * - * changes an issue with an given id to a new priority - * - * @param - * issue_id the issue_id of the issue to be changed - * new_priority the new priority - */ - public function changeIssuePriority($issue_id, $new_priority) - { - /* REMARK: - * This function only works, when an issue is moved ONE slote higher or lower - * It does NOT work with ARBITRARY movements! - */ - $this->readIssues(); - $old_priority = $this->issues[$issue_id]->getPriority(); // get old priority, so we can just exchange prioritys of two issues - foreach ($this->issues as $id => $issue) { // search for the concuring issue - if ($issue->getPriority() == $new_priority) { - $this->issues[$id]->setPriority($old_priority); // the concuring issue gets the old id of the changed issue - $this->issues[$id]->store(); // ###store_problem### - } - } - - $this->issues[$issue_id]->setPriority($new_priority); // changed issue gets the new priority - $this->issues[$issue_id]->store(); // ###store_problem### - - } - - public function renumberIssuePrioritys() - { - if (is_array($this->issues)) { - - $sorter = []; - foreach ($this->issues as $id => $issue) { - $sorter[$id] = $issue->getPriority(); - } - asort($sorter); - $i = 0; - foreach ($sorter as $id => $old_priority) { - $this->issues[$id]->setPriority($i); - $i++; - } - } - } - - public function autoAssignIssues($themen, $cycle_id) - { - $this->metadate->cycles[$cycle_id]->autoAssignIssues($themen, $this->filterStart, $this->filterEnd); - } - - - public function applyTimeFilter($start, $end) - { - $this->filterStart = $start; - $this->filterEnd = $end; - } - - public function setFilter($timestamp) - { - if ($timestamp == 'all') { - $_SESSION['raumzeitFilter'] = 'all'; - $this->applyTimeFilter(0, 0); - } else { - $filterSemester = Semester::findByTimestamp($timestamp); - $_SESSION['raumzeitFilter'] = $filterSemester->beginn; - $this->applyTimeFilter($filterSemester->beginn, $filterSemester->ende); - } - } - - public function registerCommand($command, $function) - { - $this->commands[$command] = $function; - } - - public function processCommands() - { - global $cmd; - - // workaround for multiple submit-buttons with new Button-API - foreach ($this->commands as $r_cmd => $func) { - if (Request::submitted($r_cmd)) { - $cmd = $r_cmd; - } - } - - if (!isset($cmd) && Request::option('cmd')) $cmd = Request::option('cmd'); - if (!isset($cmd)) return FALSE; - - if (isset($this->commands[$cmd])) { - call_user_func($this->commands[$cmd], $this); - } - } - - public function getFreeTextPredominantRoom($cycle_id) - { - if (!($room = $this->metadate->cycles[$cycle_id]->getFreeTextPredominantRoom($this->filterStart, $this->filterEnd))) { - return FALSE; - } - return $room; - } - - public function getPredominantRoom($cycle_id, $list = FALSE) - { - if (!($rooms = $this->metadate->cycles[$cycle_id]->getPredominantRoom($this->filterStart, $this->filterEnd))) { - return FALSE; - } - if ($list) { - return $rooms; - } else { - return $rooms[0]; - } - } - - - public function hasDatesOutOfDuration($force = false) - { - if ($this->hasDatesOutOfDuration == -1 || $force) { - $this->hasDatesOutOfDuration = SeminarDB::hasDatesOutOfDuration($this->getStartSemester(), $this->getEndSemesterVorlesEnde(), $this->id); - } - return $this->hasDatesOutOfDuration; - } - - public function getStartWeek($metadate_id = false) - { - return $this->metadate->getStartWoche($metadate_id); - } - - public function setStartWeek($week, $metadate_id = false) - { - if ($this->metadate->getStartWoche($metadate_id) == $week) { - return FALSE; - } else { - $this->metadate->setStartWoche($week, $metadate_id); - $key = $metadate_id ? $metadate_id : $this->metadate->getFirstMetadate()->metadate_id; - $this->createMessage(sprintf(_("Die Startwoche für den Termin %s wurde geändert."), $this->metadate->cycles[$key]->toString())); - $this->metadate->createSingleDates($key); - $this->metadate->cycles[$key]->termine = null; - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - } - } - - - /** - * instance method - * - * returns number of participants for each usergroup in seminar, - * total, lecturers, tutors, authors, users - * - * @param string (optional) return count only for given usergroup - * - * @return array - */ - - public function getNumberOfParticipants() - { - $args = func_get_args(); - array_unshift($args, $this->id); - return call_user_func_array(["Seminar", "getNumberOfParticipantsBySeminarId"], $args); - } - - /** - * class method - * - * returns number of participants for each usergroup in given seminar, - * total, lecturers, tutors, authors, users - * - * @param string seminar_id - * - * @param string (optional) return count only for given usergroup - * - * @return array - */ - - public function getNumberOfParticipantsBySeminarId($sem_id) - { - $db = DBManager::get(); - $stmt1 = $db->prepare("SELECT - COUNT(Seminar_id) AS anzahl, - COUNT(IF(status='dozent',Seminar_id,NULL)) AS anz_dozent, - COUNT(IF(status='tutor',Seminar_id,NULL)) AS anz_tutor, - COUNT(IF(status='autor',Seminar_id,NULL)) AS anz_autor, - COUNT(IF(status='user',Seminar_id,NULL)) AS anz_user - FROM seminar_user - WHERE Seminar_id = ? - GROUP BY Seminar_id"); - $stmt1->execute([$sem_id]); - $numbers = $stmt1->fetch(PDO::FETCH_ASSOC); - - $stmt2 = $db->prepare("SELECT COUNT(*) as anzahl - FROM admission_seminar_user - WHERE seminar_id = ? - AND status = 'accepted'"); - $stmt2->execute([$sem_id]); - $acceptedUsers = $stmt2->fetch(PDO::FETCH_ASSOC); - - - $count = 0; - if ($numbers["anzahl"]) { - $count += $numbers["anzahl"]; - } - if ($acceptedUsers["anzahl"]) { - $count += $acceptedUsers["anzahl"]; - } - - $participant_count = []; - $participant_count['total'] = $count; - $participant_count['lecturers'] = $numbers['anz_dozent'] ? (int) $numbers['anz_dozent'] : 0; - $participant_count['tutors'] = $numbers['anz_tutor'] ? (int) $numbers['anz_tutor'] : 0; - $participant_count['authors'] = $numbers['anz_autor'] ? (int) $numbers['anz_autor'] : 0; - $participant_count['users'] = $numbers['anz_user'] ? (int) $numbers['anz_user'] : 0; - - // return specific parameter if - $params = func_get_args(); - if (sizeof($params) > 1) { - if (in_array($params[1], array_keys($participant_count))) { - return $participant_count[$params[1]]; - } else { - trigger_error(get_class($this)."::__getParticipantInfos - unknown parameter requested"); - } - } - - return $participant_count; - } - - - /** - * Returns the IDs of this course's study areas. - * - * @return array an array of IDs - */ - public function getStudyAreas() - { - $stmt = DBManager::get()->prepare("SELECT DISTINCT sem_tree_id ". - "FROM seminar_sem_tree ". - "WHERE seminar_id=?"); - - $stmt->execute([$this->id]); - return $stmt->fetchAll(PDO::FETCH_COLUMN, 0); - } - - /** - * Sets the study areas of this course. - * - * @param array an array of IDs - * - * @return void - */ - public function setStudyAreas($selected) - { - $old = $this->getStudyAreas(); - $sem_tree = TreeAbstract::GetInstance("StudipSemTree"); - $removed = array_diff($old, $selected); - $added = array_diff($selected, $old); - $count_removed = 0; - $count_added = 0; - foreach($removed as $one){ - $count_removed += $sem_tree->DeleteSemEntries($one, $this->getId()); - } - foreach($added as $one){ - $count_added += $sem_tree->InsertSemEntry($one, $this->getId()); - } - if ($count_added || $count_removed) { - NotificationCenter::postNotification("CourseDidChangeStudyArea", $this); - } - return count($old) + $count_added - $count_removed; - } - - /** - * @return boolean returns TRUE if this course is publicly visible, - * FALSE otherwise - */ - public function isPublic() - { - return Config::get()->ENABLE_FREE_ACCESS && $this->read_level == 0; - } - - /** - * @return boolean returns TRUE if this course is a studygroup, - * FALSE otherwise - */ - public function isStudygroup() - { - global $SEM_CLASS, $SEM_TYPE; - return $SEM_CLASS[$SEM_TYPE[$this->status]["class"]]["studygroup_mode"]; - } - - /** - * @return int returns default colour group for new members (shown in meine_seminare.php) - * - **/ - public function getDefaultGroup() - { - if ($this->isStudygroup()) { - return 8; - } else { - return select_group ($this->semester_start_time); - } - } - - - /** - * Deletes the current seminar - * - * @return void returns success-message if seminar could be deleted - * otherwise an error-message - */ - - public function delete() - { - $s_id = $this->id; - - // Delete that Seminar. - - // Alle Benutzer aus dem Seminar rauswerfen. - $db_ar = CourseMember::deleteBySQL('Seminar_id = ?', [$s_id]); - if ($db_ar > 0) { - $this->createMessage(sprintf(_("%s Teilnehmende und Lehrende archiviert."), $db_ar)); - } - - // Alle Benutzer aus Wartelisten rauswerfen - AdmissionApplication::deleteBySQL('seminar_id = ?', [$s_id]); - - // Alle beteiligten Institute rauswerfen - $query = "DELETE FROM seminar_inst WHERE Seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - if (($db_ar = $statement->rowCount()) > 0) { - $this->createMessage(sprintf(_("%s Zuordnungen zu Einrichtungen archiviert."), $db_ar)); - } - - // user aus den Statusgruppen rauswerfen - $count = Statusgruppen::deleteBySQL('range_id = ?', [$s_id]); - if ($count > 0) { - $this->createMessage(sprintf(_('%s Funktionen/Gruppen gelöscht.'), $count)); - } - - // seminar_sem_tree entries are deleted automatically on deletion of the Course object. - - // Alle Termine mit allem was dranhaengt zu diesem Seminar loeschen. - if (($db_ar = SingleDateDB::deleteAllDates($s_id)) > 0) { - $this->createMessage(sprintf(_("%s Veranstaltungstermine archiviert."), $db_ar)); - } - - //Themen - IssueDB::deleteAllIssues($s_id); - - //Cycles - SeminarCycleDate::deleteBySQL('seminar_id = ' . DBManager::get()->quote($s_id)); - - // Alle weiteren Postings zu diesem Seminar in den Forums-Modulen löschen - foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) { - $plugin->deleteContents($s_id); // delete content irrespective of plugin-activation in the seminar - - if ($plugin->isActivated($s_id)) { // only show a message, if the plugin is activated, to not confuse the user - $this->createMessage(sprintf(_('Einträge in %s archiviert.'), $plugin->getPluginName())); - } - } - - // Alle Pluginzuordnungen entfernen - PluginManager::getInstance()->deactivateAllPluginsForRange('sem', $s_id); - - // Alle Dokumente zu diesem Seminar loeschen. - $folder = Folder::findTopFolder($s_id); - if($folder) { - if($folder->delete()) { - $this->createMessage(_("Dokumente und Ordner archiviert.")); - } - } - - - // Freie Seite zu diesem Seminar löschen - $db_ar = StudipScmEntry::deleteBySQL('range_id = ?', [$s_id]); - if ($db_ar > 0) { - $this->createMessage(_("Freie Seite der Veranstaltung archiviert.")); - } - - // Alle News-Verweise auf dieses Seminar löschen - if ( ($db_ar = StudipNews::DeleteNewsRanges($s_id)) ) { - $this->createMessage(sprintf(_("%s Ankündigungen gelöscht."), $db_ar)); - } - //delete entry in news_rss_range - StudipNews::UnsetRssId($s_id); - - //kill the datafields - DataFieldEntry::removeAll($s_id); - - //kill all wiki-pages - $db_wiki = WikiPage::deleteBySQL('range_id = ?', [$s_id]); - if ($db_wiki > 0) { - $this->createMessage(sprintf(_("%s Wiki-Seiten archiviert."), $db_wiki)); - } - - $query = "DELETE FROM wiki_links WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - - // delete course config values - ConfigValue::deleteBySQL('range_id = ?', [$s_id]); - - // kill all the ressources that are assigned to the Veranstaltung (and all the linked or subordinated stuff!) - if (Config::get()->RESOURCES_ENABLE) { - ResourceBooking::deleteBySql( - 'range_id = :course_id', - [ - 'course_id' => $s_id - ] - ); - if ($rr = RoomRequest::existsByCourse($s_id)) { - RoomRequest::find($rr)->delete(); - } - } - - // kill virtual seminar-entries in calendar - $query = "DELETE FROM schedule_seminare WHERE seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - - if(Config::get()->ELEARNING_INTERFACE_ENABLE){ - global $connected_cms; - $del_cms = 0; - $cms_types = ObjectConnections::GetConnectedSystems($s_id); - if(count($cms_types)){ - foreach($cms_types as $system){ - ELearningUtils::loadClass($system); - $del_cms += $connected_cms[$system]->deleteConnectedModules($s_id); - } - $this->createMessage(sprintf(_("%s Verknüpfungen zu externen Systemen gelöscht."), $del_cms )); - } - } - - //kill the object_user_vists for this seminar - object_kill_visits(null, $s_id); - - // Logging... - $query = "SELECT CONCAT(seminare.VeranstaltungsNummer, ' ', seminare.name, '(', semester_data.name, ')') - FROM seminare - LEFT JOIN semester_data ON (seminare.start_time = semester_data.beginn) - WHERE seminare.Seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - $semlogname = $statement->fetchColumn() ?: sprintf('unknown sem_id: %s', $s_id); - - StudipLog::log("SEM_ARCHIVE",$s_id,NULL,$semlogname); - // ...logged - - // delete deputies if necessary - Deputy::deleteByRange_id($s_id); - - UserDomain::removeUserDomainsForSeminar($s_id); - - AutoInsert::deleteSeminar($s_id); - - //Anmeldeset Zordnung entfernen - $cs = $this->getCourseSet(); - if ($cs) { - CourseSet::removeCourseFromSet($cs->getId(), $this->getId()); - $cs->load(); - if (!count($cs->getCourses()) - && $cs->isGlobal() - && $cs->getUserid() != '') { - $cs->delete(); - } - } - AdmissionPriority::unsetAllPrioritiesForCourse($this->getId()); - // und das Seminar loeschen. - $this->course->delete(); - $this->restore(); - return true; - } - - /** - * returns a html representation of the seminar-dates - * - * @param array optional variables which are passed to the template - * @return string the html-representation of the dates - * - * @author Till Glöggler - */ - public function getDatesHTML($params = []) - { - return $this->getDatesTemplate('dates/seminar_html.php', $params); - } - - /** - * returns a representation without html of the seminar-dates - * - * @param array optional variables which are passed to the template - * @return string the representation of the dates without html - * - * @author Till Glöggler - */ - public function getDatesExport($params = []) - { - return $this->getDatesTemplate('dates/seminar_export.php', $params); - } - - /** - * returns a xml-representation of the seminar-dates - * - * @param array optional variables which are passed to the template - * @return string the xml-representation of the dates - * - * @author Till Glöggler - */ - public function getDatesXML($params = []) - { - return $this->getDatesTemplate('dates/seminar_xml.php', $params); - } - - /** - * returns a representation of the seminar-dates with a specifiable template - * - * @param mixed this can be a template-object or a string pointing to a template in path_to_studip/templates - * @param array optional parameters which are passed to the template - * @return string the template output of the dates - * - * @author Till Glöggler - */ - public function getDatesTemplate($template, $params = []) - { - if (!$template instanceof Flexi\Template && is_string($template)) { - $template = $GLOBALS['template_factory']->open($template); - } - - if (!empty($params['semester_id'])) { - $semester = Semester::find($params['semester_id']); - if ($semester) { - // apply filter - $this->applyTimeFilter($semester->beginn, $semester->ende); - } - } - - $template->dates = $this->getUndecoratedData(isset($params['semester_id'])); - $template->seminar_id = $this->getId(); - - $template->set_attributes($params); - return trim($template->render()); - } - - /** - * returns an asscociative array with the attributes of the seminar depending - * on the field-names in the database - * @return array - */ - public function getData() - { - $data = $this->course->toArray(); - foreach($this->alias as $a => $o) { - $data[$a] = $this->course->$o; - } - return $data; - } - - /** - * returns an array with all IDs of Institutes this seminar is related to - * @param sem_id string: optional ID of a seminar, when null, this ID will be used - * @return: array of IDs (not associative) - */ - public function getInstitutes($sem_id = null) - { - if (!$sem_id && $this) { - $sem_id = $this->id; - } - - $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = :sem_id - UNION - SELECT Institut_id FROM seminare WHERE Seminar_id = :sem_id"; - $statement = DBManager::get()->prepare($query); - $statement->execute(compact('sem_id')); - return $statement->fetchAll(PDO::FETCH_COLUMN); - } - - /** - * set the entries for seminar_inst table in database - * seminare.institut_id will always be added - * @param institutes array: array of Institut_id's - * @return bool: if something changed - */ - public function setInstitutes($institutes = []) - { - if (is_array($institutes)) { - $institutes[] = $this->institut_id; - $institutes = array_unique($institutes); - - $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->id]); - $old_inst = $statement->fetchAll(PDO::FETCH_COLUMN); - - $todelete = array_diff($old_inst, $institutes); - - $query = "DELETE FROM seminar_inst WHERE seminar_id = ? AND institut_id = ?"; - $statement = DBManager::get()->prepare($query); - - foreach($todelete as $inst) { - $tmp_instname= get_object_name($inst, 'inst'); - StudipLog::log('CHANGE_INSTITUTE_DATA', $this->id, $inst, 'Die beteiligte Einrichtung "'. $tmp_instname['name'] .'" wurde gelöscht.'); - $statement->execute([$this->id, $inst]); - NotificationCenter::postNotification('SeminarInstitutionDidDelete', $inst, $this->id); - - } - - $toinsert = array_diff($institutes, $old_inst); - - $query = "INSERT INTO seminar_inst (seminar_id, institut_id) VALUES (?, ?)"; - $statement = DBManager::get()->prepare($query); - - foreach($toinsert as $inst) { - $tmp_instname= get_object_name($inst, 'inst'); - StudipLog::log('CHANGE_INSTITUTE_DATA', $this->id, $inst, 'Die beteiligte Einrichtung "'. $tmp_instname['name'] .'" wurde hinzugefügt.'); - $statement->execute([$this->id, $inst]); - NotificationCenter::postNotification('SeminarInstitutionDidCreate', $inst, $this->id); - } - if ($todelete || $toinsert) { - NotificationCenter::postNotification("CourseDidChangeInstitutes", $this); - } - return $todelete || $toinsert; - } else { - $this->createError(_("Ungültige Eingabe der Institute. Es muss " . - "mindestens ein Institut angegeben werden.")); - return false; - } - } - - /** - * adds a user to the seminar with the given status - * @param user_id string: ID of the user - * @param status string: status of the user for the seminar "user", "autor", "tutor", "dozent" - * @param force bool: if false (default) the user will only be upgraded and not degraded in his/her status - */ - public function addMember($user_id, $status = 'autor', $force = false) - { - - if (in_array($GLOBALS['perm']->get_perm($user_id), ["admin", "root"])) { - $this->createError(_("Admin und Root dürfen nicht Mitglied einer Veranstaltung sein.")); - return false; - } - $db = DBManager::get(); - - $rangordnung = array_flip(['user', 'autor', 'tutor', 'dozent']); - if ($rangordnung[$status] > $rangordnung['autor'] && SeminarCategories::getByTypeId($this->status)->only_inst_user) { - //überprüfe, ob im richtigen Institut: - $user_institute_stmt = $db->prepare( - "SELECT Institut_id " . - "FROM user_inst " . - "WHERE user_id = :user_id " . - ""); - $user_institute_stmt->execute(['user_id' => $user_id]); - $user_institute = $user_institute_stmt->fetchAll(PDO::FETCH_COLUMN, 0); - - if (!in_array($this->institut_id, $user_institute) && !count(array_intersect($user_institute, $this->getInstitutes()))) { - $this->createError(_("Einzutragender Nutzer stammt nicht einem beteiligten Institut an.")); - - return false; - } - } - $course_member = CourseMember::findOneBySQL('user_id = ? AND Seminar_id = ?', [$user_id, $this->id]); - $new_position = (int) DBManager::get()->fetchColumn( - "SELECT MAX(position) + 1 FROM seminar_user WHERE status = ? AND Seminar_id = ?", - [$status, $this->id] - ); - $numberOfTeachers = CourseMember::countBySql("Seminar_id = ? AND status = 'dozent'", [$this->id]); - - if (!$course_member && !$force) { - CourseMember::create([ - 'Seminar_id' => $this->id, - 'user_id' => $user_id, - 'status' => $status, - 'position' => $new_position?:0, - 'gruppe' => (int) select_group($this->getSemesterStartTime()), - 'visible' => in_array($status, ['tutor', 'dozent']) ? 'yes' : 'unknown', - ]); - // delete the entries, user is now in the seminar - if (AdmissionApplication::deleteBySQL('user_id = ? AND seminar_id = ?', [$user_id, $this->getId()])) { - //renumber the waiting/accepted/lot list, a user was deleted from it - AdmissionApplication::renumberAdmission($this->getId()); - } - $cs = $this->getCourseSet(); - if ($cs) { - AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId()); - } - - CalendarScheduleModel::deleteSeminarEntries($user_id, $this->getId()); - NotificationCenter::postNotification('CourseDidGetMember', $this, $user_id); - NotificationCenter::postNotification('UserDidEnterCourse', $this->id, $user_id); - StudipLog::log('SEM_USER_ADD', $this->id, $user_id, $status, 'Wurde in die Veranstaltung eingetragen'); - $this->course->resetRelation('members'); - $this->course->resetRelation('admission_applicants'); - - // Check if we need to add user to parent course as well. - if ($this->parent_course) { - $parent = new Seminar($this->parent); - $parent->addMember($user_id, $status, $force); - } - - return $this; - } elseif ( - ($force || $rangordnung[$course_member->status] < $rangordnung[$status]) - && ($course_member->status !== 'dozent' || $numberOfTeachers > 1) - ) { - $visibility = $course_member->visible; - if (in_array($status, ['tutor', 'dozent'])) { - $visibility = 'yes'; - } - $course_member->status = $status; - $course_member->visible = $visibility; - $course_member->position = $new_position; - $course_member->store(); - - if ($course_member->status === 'dozent') { - $termine = DBManager::get()->fetchFirst( - "SELECT termin_id FROM termine WHERE range_id = ?", - [$this->id] - ); - - DBManager::get()->execute( - "DELETE FROM termin_related_persons WHERE range_id IN (?) AND user_id = ?", - [$termine, $user_id] - ); - } - NotificationCenter::postNotification('CourseDidChangeMember', $this, $user_id); - $this->course->resetRelation('members'); - $this->course->resetRelation('admission_applicants'); - return $this; - } else { - if ($course_member->status === 'dozent' && $numberOfTeachers <= 1) { - $this->createError(sprintf(_('Die Person kann nicht herabgestuft werden, ' . -'da mindestens ein/eine Veranstaltungsleiter/-in (%s) in die Veranstaltung eingetragen sein muss!'), - get_title_for_status('dozent', 1, $this->status)) . - ' ' . sprintf(_('Tragen Sie zunächst eine weitere Person als Veranstaltungsleiter/-in (%s) ein.'), -get_title_for_status('dozent', 1, $this->status))); - } - - return false; - } - } - - /** - * Cancels a subscription to an admission. - * - * @param array $users - * @param string $status - * @return array - * @throws NotificationVetoException - */ - public function cancelAdmissionSubscription(array $users, string $status): array - { - $msgs = []; - $messaging = new messaging; - $course_set = $this->getCourseSet(); - $users = User::findMany($users); - foreach ($users as $user) { - $prio_delete = false; - if ($course_set) { - $prio_delete = AdmissionPriority::unsetPriority($course_set->getId(), $user->id, $this->getId()); - } - $result = AdmissionApplication::deleteBySQL( - 'seminar_id = ? AND user_id = ? AND status = ?', - [$this->getId(), $user->id, $status] - ); - if ($result || $prio_delete) { - setTempLanguage($user->id); - if ($status !== 'accepted') { - $message = sprintf( - _('Sie wurden von der Warteliste der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'), - $this->getFullName() - ); - } else { - $message = sprintf( - _('Sie wurden aus der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'), - $this->getFullName() - ); - } - restoreLanguage(); - $messaging->insert_message( - $message, - $user->username, - '____%system%____', - false, - false, - '1', - false, - sprintf('%s %s', _('Systemnachricht:'), _('nicht zugelassen in Veranstaltung')), - true - ); - StudipLog::log('SEM_USER_DEL', $this->getId(), $user->id, 'Wurde aus der Veranstaltung entfernt'); - NotificationCenter::postNotification('UserDidLeaveCourse', $this->getId(), $user->id); - - $msgs[] = $user->getFullName(); - } - } - return $msgs; - } - - /** - * Cancels a subscription to a course - * @param array $users - * @return array - * @throws Exception - */ - public function cancelSubscription(array $users): array - { - $msgs = []; - $messaging = new messaging; - $users = User::findMany($users); - foreach ($users as $user) { - // delete member from seminar - if ($this->deleteMember($user->id)) { - setTempLanguage($user->id); - $message = sprintf( - _('Ihre Anmeldung zur Veranstaltung **%s** wurde aufgehoben.'), - $this->getFullName() - ); - restoreLanguage(); - $messaging->insert_message( - $message, - $user->username, - '____%system%____', - false, - false, - '1', - false, - sprintf('%s %s', _('Systemnachricht:'), _("Anmeldung aufgehoben")), - true - ); - $msgs[] = $user->getFullName(); - } - } - - return $msgs; - } - - /** - * deletes a user from the seminar by respecting the rule that at least one - * user with status "dozent" must stay there - * @param string $user_id user_id of the user to delete - * @return boolean - */ - public function deleteMember($user_id): bool - { - $dozenten = $this->getMembers(); - if (count($dozenten) >= 2 || empty($dozenten[$user_id])) { - $result = CourseMember::deleteBySQL('Seminar_id = ? AND user_id = ?', [$this->id, $user_id]); - if ($result === 0) { - return true; - } - // If this course is a child of another course... - if ($this->parent_course) { - // ... check if user is member in another sibling ... - $other = CourseMember::countBySQL( - "`user_id` = :user AND `Seminar_id` IN (:courses) AND `Seminar_id` != :this", - ['user' => $user_id, 'courses' => $this->parent->children->pluck('seminar_id'), 'this' => $this->id] - ); - - // ... and delete from parent course if this was the only - // course membership in this family. - if ($other === 0) { - $s = new Seminar($this->parent); - $s->deleteMember($user_id); - } - } - - if ($this->children != null) { - foreach ($this->children as $child) { - $s = new Seminar($child); - $s->deleteMember($user_id); - } - } - - if (!empty($dozenten[$user_id])) { - $query = "SELECT termin_id FROM termine WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->id]); - $termine = $statement->fetchAll(PDO::FETCH_COLUMN); - - $query = "DELETE FROM termin_related_persons WHERE range_id = ? AND user_id = ?"; - $statement = DBManager::get()->prepare($query); - - foreach ($termine as $termin_id) { - $statement->execute([$termin_id, $user_id]); - } - if (Deputy::isActivated()) { - $other_dozenten = array_diff(array_keys($dozenten), [$user_id]); - foreach (Deputy::findByRange_id($user_id) as $default_deputy) { - if ($default_deputy->user_id != $GLOBALS['user']->id && - !Deputy::countBySql("range_id IN (?)", [$other_dozenten])) { - Deputy::deleteBySQL("range_id = ? AND user_id = ?", [$this->id, $default_deputy->user_id]); - } - } - } - } - - // Delete course related datafield entries - DatafieldEntryModel::deleteBySQL('range_id = ? AND sec_range_id = ?', [$user_id, $this->id]); - - // Remove from associated status groups - foreach (Statusgruppen::findBySeminar_id($this->id) as $group) { - $group->removeUser($user_id, true); - } - - $this->createMessage(sprintf( - _('Nutzer %s wurde aus der Veranstaltung entfernt.'), - '' . htmlReady(get_fullname($user_id)) . '' - )); - NotificationCenter::postNotification('CourseDidChangeMember', $this, $user_id); - NotificationCenter::postNotification('UserDidLeaveCourse', $this->id, $user_id); - StudipLog::log('SEM_USER_DEL', $this->id, $user_id, 'Wurde aus der Veranstaltung entfernt'); - $this->course->resetRelation('members'); - return true; - } else { - $this->createError( - sprintf( - _('Die Veranstaltung muss wenigstens einen/eine VeranstaltungsleiterIn (%s) eingetragen haben!'), - get_title_for_status('dozent', 1, $this->status) - ) - . ' ' . _('Tragen Sie zunächst einen anderen ein, um diesen zu löschen.') - ); - return false; - } - } - - /** - * sets the almost never used column position in the table seminar_user - * @param array $members members array: array of user_id's - wrong IDs will be ignored - * @return Seminar - */ - public function setMemberPriority($members): Seminar - { - CourseMember::findEachBySQL( - function (CourseMember $membership) use (&$members) { - $membership->position = array_search($membership->user_id, $members); - $membership->store(); - }, - "Seminar_id = ? AND user_id IN (?)", - [$this->id, $members] - ); - return $this; - } - - /** - * returns array with information about enrolment to this course for given user_id - * ['enrolment_allowed'] : true or false - * ['cause']: keyword to describe the cause - * ['description'] : readable description of the cause - * - * @param string $user_id - * @return array - */ - public function getEnrolmentInfo($user_id) - { - $info = []; - $user = User::find($user_id); - if ($this->getSemClass()->isGroup()) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'grouped'; - $info['description'] = _("Dies ist eine Veranstaltungsgruppe. Sie können sich nur in deren Unterveranstaltungen eintragen."); - return $info; - } - if ($this->read_level == 0 && Config::get()->ENABLE_FREE_ACCESS && !$GLOBALS['perm']->get_studip_perm($this->getId(), $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'free_access'; - $info['description'] = _("Für die Veranstaltung ist keine Anmeldung erforderlich."); - return $info; - } - if (!$user) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'nobody'; - $info['description'] = _("Sie sind nicht in Stud.IP angemeldet."); - return $info; - } - if ($GLOBALS['perm']->have_perm('root', $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'root'; - $info['description'] = _("Sie dürfen ALLES."); - return $info; - } - if ($GLOBALS['perm']->have_studip_perm('admin', $this->getId(), $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'courseadmin'; - $info['description'] = _("Sie sind Administrator_in der Veranstaltung."); - return $info; - } - if ($GLOBALS['perm']->have_perm('admin', $user_id)) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'admin'; - $info['description'] = _("Als Administrator_in können Sie sich nicht für eine Veranstaltung anmelden."); - return $info; - } - //Ist bereits Teilnehmer - if ($GLOBALS['perm']->have_studip_perm('user', $this->getId(), $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'member'; - $info['description'] = _("Sie sind für die Veranstaltung angemeldet."); - return $info; - } - $admission_status = $user->admission_applications->findBy('seminar_id', $this->getId())->val('status'); - if ($admission_status == 'accepted') { - $info['enrolment_allowed'] = false; - $info['cause'] = 'accepted'; - $info['description'] = _("Sie wurden für diese Veranstaltung vorläufig akzeptiert."); - return $info; - } - if ($admission_status == 'awaiting') { - $info['enrolment_allowed'] = false; - $info['cause'] = 'awaiting'; - $info['description'] = _("Sie stehen auf der Warteliste für diese Veranstaltung."); - return $info; - } - if ($GLOBALS['perm']->get_perm($user_id) == 'user') { - $info['enrolment_allowed'] = false; - $info['cause'] = 'user'; - $info['description'] = _("Sie haben nicht die erforderliche Berechtigung sich für eine Veranstaltung anzumelden."); - return $info; - } - //falsche Nutzerdomäne - $same_domain = true; - $user_domains = UserDomain::getUserDomainsForUser($user_id); - if (count($user_domains) > 0) { - $seminar_domains = UserDomain::getUserDomainsForSeminar($this->getId()); - $same_domain = UserDomain::checkUserVisibility($seminar_domains, $user_domains);; - } - if (!$same_domain && !$this->isStudygroup()) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'domain'; - $info['description'] = _("Sie sind nicht in einer zugelassenenen Nutzerdomäne, Sie können sich nicht eintragen!"); - return $info; - } - //Teilnehmerverwaltung mit Sperregel belegt - if (LockRules::Check($this->getId(), 'participants')) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'locked'; - $lockdata = LockRules::getObjectRule($this->getId()); - $info['description'] = _("In diese Veranstaltung können Sie sich nicht eintragen!") . ($lockdata['description'] ? '
' . formatLinks($lockdata['description']) : ''); - return $info; - } - //Veranstaltung unsichtbar für aktuellen Nutzer - if (!$this->visible && !$this->isStudygroup() && !$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM, $user_id)) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'invisible'; - $info['description'] = _("Die Veranstaltung ist gesperrt, Sie können sich nicht eintragen!"); - return $info; - } - if ($courseset = $this->getCourseSet()) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'courseset'; - $info['description'] = _("Die Anmeldung zu dieser Veranstaltung folgt speziellen Regeln. Lesen Sie den Hinweistext."); - $user_prio = AdmissionPriority::getPrioritiesByUser($courseset->getId(), $user_id); - if (isset($user_prio[$this->getId()])) { - if ($courseset->hasAdmissionRule('LimitedAdmission')) { - $info['description'] .= ' ' . sprintf(_("(Sie stehen auf der Anmeldeliste für die automatische Platzverteilung mit der Priorität %s.)"), $user_prio[$this->getId()]); - } else { - $info['description'] .= ' ' . _("(Sie stehen auf der Anmeldeliste für die automatische Platzverteilung.)"); - } - } - return $info; - } - $info['enrolment_allowed'] = true; - $info['cause'] = 'normal'; - $info['description'] = ''; - return $info; - } - - /** - * adds user with given id as preliminary member to course - * - * @param string $user_id - * @return integer 1 if successfull - */ - public function addPreliminaryMember($user_id, $comment = '') - { - $new_admission_member = new AdmissionApplication(); - $new_admission_member->user_id = $user_id; - $new_admission_member->position = 0; - $new_admission_member->status = 'accepted'; - $new_admission_member->comment = $comment; - $this->course->admission_applicants[] = $new_admission_member; - $ok = $new_admission_member->store(); - if ($ok && $this->isStudygroup()) { - StudygroupModel::applicationNotice($this->getId(), $user_id); - } - $cs = $this->getCourseSet(); - if ($cs) { - $prio_delete = AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId()); - } - // LOGGING - StudipLog::log('SEM_USER_ADD', $this->getId(), $user_id, 'accepted', 'Vorläufig akzeptiert'); - return $ok; - } - - /** - * returns courseset object for this course - * - * @return CourseSet courseset object or null - */ - public function getCourseSet() - { - if ($this->course_set === null) { - $this->course_set = CourseSet::getSetForCourse($this->id); - if ($this->course_set === null) { - $this->course_set = false; - } - } - return $this->course_set ?: null; - } - - /** - * returns true if the number of participants of this course is limited - * - * @return boolean - */ - public function isAdmissionEnabled() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->isSeatDistributionEnabled()); - } - - /** - * returns the number of free seats in the course or true if not limited - * - * @return integer - */ - public function getFreeAdmissionSeats() - { - if ($this->isAdmissionEnabled()) { - return $this->course->getFreeSeats(); - } else { - return true; - } - } - - /** - * returns true if the course is locked - * - * @return boolean - */ - public function isAdmissionLocked() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->hasAdmissionRule('LockedAdmission')); - } - - /** - * returns true if the course is password protected - * - * @return boolean - */ - public function isPasswordProtected() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->hasAdmissionRule('PasswordAdmission')); - } - - /** - * returns array with start and endtime of course enrolment timeframe - * return null if there is no timeframe - * - * @return array assoc array with start_time end_time as keys timestamps as values - */ - public function getAdmissionTimeFrame() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->hasAdmissionRule('TimedAdmission')) ? - ['start_time' => $cs->getAdmissionRule('TimedAdmission')->getStartTime(), - 'end_time' => $cs->getAdmissionRule('TimedAdmission')->getEndTime()] : []; - } - - /** - * returns StudipModule object for given slot, null when deactivated or not available - * - * @param string $slot - * @return StudipModule|null - */ - public function getSlotModule($slot): ?StudipModule - { - $module = 'Core' . ucfirst($slot); - if ($this->course->isToolActive($module)) { - return PluginEngine::getPlugin($module); - } - return null; - } - - /** - * adds user with given id on waitinglist - * - * @param string $user_id - * @param string $which_end 'last' or 'first' - * @return integer|bool number on waitlist or false if not successful - */ - public function addToWaitlist($user_id, $which_end = 'last') - { - if (AdmissionApplication::exists([$user_id, $this->id]) || CourseMember::find([$this->id, $user_id])) { - return false; - } - switch ($which_end) { - // Append users to waitlist end. - case 'last': - $maxpos = DBManager::get()->fetchColumn("SELECT MAX(`position`) - FROM `admission_seminar_user` - WHERE `seminar_id`=? - AND `status`='awaiting'", [$this->id]); - $waitpos = $maxpos+1; - break; - // Prepend users to waitlist start. - case 'first': - default: - // Move all others on the waitlist up by the number of people to add. - AdmissionApplication::renumberAdmission($this->id); - $waitpos = 1; - } - $new_admission_member = new AdmissionApplication(); - $new_admission_member->user_id = $user_id; - $new_admission_member->position = $waitpos; - $new_admission_member->status = 'awaiting'; - $new_admission_member->seminar_id = $this->id; - if ($new_admission_member->store()) { - StudipLog::log('SEM_USER_ADD', $this->id, $user_id, 'awaiting', 'Auf Warteliste gesetzt, Position: ' . $waitpos); - $this->course->resetRelation('admission_applicants'); - return $waitpos; - } - return false; - } -} diff --git a/lib/classes/Seminar.php b/lib/classes/Seminar.php new file mode 100644 index 0000000..a2d69ea --- /dev/null +++ b/lib/classes/Seminar.php @@ -0,0 +1,2439 @@ + + * @author Stefan Suchi + * @author Suchi & Berg GmbH + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +require_once 'lib/dates.inc.php'; + +class Seminar +{ + var $issues = null; // Array of Issue + var $irregularSingleDates = null; // Array of SingleDates + var $messages = []; // occured errors, infos, and warnings + var $semester = null; + var $filterStart = 0; + var $filterEnd = 0; + var $hasDatesOutOfDuration = -1; + var $message_stack = []; + + var $user_number = 0;//? + var $commands; //? + var $BookedRoomsStatTemp; //??? + + var $request_id;//TODO + var $requestData; + var $room_request; + + private $_metadate = null; // MetaDate + + private $alias = [ + 'seminar_number' => 'VeranstaltungsNummer', + 'subtitle' => 'Untertitel', + 'description' => 'Beschreibung', + 'location' => 'Ort', + 'misc' => 'Sonstiges', + 'read_level' => 'Lesezugriff', + 'write_level' => 'Schreibzugriff', + 'semester_start_time' => 'start_time', + 'semester_duration_time' => 'duration_time', + 'form' => 'art', + 'participants' => 'teilnehmer', + 'requirements' => 'vorrausetzungen', + 'orga' => 'lernorga', + ]; + + private $course = null; + + private $course_set = null; + + private static $seminar_object_pool; + + public static function GetInstance($id = false, $refresh_cache = false) + { + if ($id) { + if ($refresh_cache) { + self::$seminar_object_pool[$id] = null; + } + if (!empty(self::$seminar_object_pool[$id]) && is_object(self::$seminar_object_pool[$id]) && self::$seminar_object_pool[$id]->getId() == $id) { + return self::$seminar_object_pool[$id]; + } else { + self::$seminar_object_pool[$id] = new Seminar($id); + return self::$seminar_object_pool[$id]; + } + } else { + return new Seminar(false); + } + } + + public static function setInstance(Seminar $seminar) + { + return self::$seminar_object_pool[$seminar->id] = $seminar; + } + + /** + * Constructor + * + * Pass nothing to create a seminar, or the seminar_id from an existing seminar to change or delete + * @access public + * @param string $seminar_id the seminar to be retrieved + */ + public function __construct($course_or_id = FALSE) + { + $course = Course::toObject($course_or_id); + if ($course) { + $this->course = $course; + } elseif ($course_or_id === false) { + $this->course = new Course(); + $this->course->setId($this->course->getNewId()); + } else { //hmhmhm + throw new Exception(sprintf(_('Fehler: Konnte das Seminar mit der ID %s nicht finden!'), $course_or_id)); + } + } + + public function __get($field) + { + if ($field == 'is_new') { + return $this->course->isNew(); + } + if ($field == 'metadate') { + if ($this->_metadate === null) { + $this->_metadate = new MetaDate($this->id); + $this->_metadate->setSeminarStartTime($this->start_time); + $this->_metadate->setSeminarDurationTime($this->duration_time); + } + return $this->_metadate; + } + if(isset($this->alias[$field])) { + $field = $this->alias[$field]; + } + return $this->course->$field; + } + + public function __set($field, $value) + { + if(isset($this->alias[$field])) { + $field = $this->alias[$field]; + } + if ($field == 'metadate') { + return $this->_metadate = $value; + } + return $this->course->$field = $value; + } + + public function __isset($field) + { + if ($field == 'metadate') { + return is_object($this->_metadate); + } + if(isset($this->alias[$field])) { + $field = $this->alias[$field]; + } + return isset($this->course->$field); + } + + public function __call($method, $params) + { + return call_user_func_array([$this->course, $method], $params); + } + + public static function GetSemIdByDateId($date_id) + { + $stmt = DBManager::get()->prepare("SELECT range_id FROM termine WHERE termin_id = ? LIMIT 1"); + $stmt->execute([$date_id]); + return $stmt->fetchColumn(); + } + + /** + * + * creates an new id for this object + * @access private + * @return string the unique id + */ + public function createId() + { + return $this->course->getNewId(); + } + + public function getMembers($status = 'dozent') + { + $ret = []; + foreach($this->course->getMembersWithStatus($status) as $m) { + $ret[$m->user_id]['user_id'] = $m->user_id; + $ret[$m->user_id]['username'] = $m->username; + $ret[$m->user_id]['Vorname'] = $m->vorname; + $ret[$m->user_id]['Nachname'] = $m->nachname; + $ret[$m->user_id]['Email'] = $m->email; + $ret[$m->user_id]['position'] = $m->position; + $ret[$m->user_id]['label'] = $m->label; + $ret[$m->user_id]['status'] = $m->status; + $ret[$m->user_id]['mkdate'] = $m->mkdate; + $ret[$m->user_id]['fullname'] = $m->getUserFullname(); + } + return $ret; + } + + public function getAdmissionMembers($status = 'awaiting') + { + $ret = []; + foreach($this->course->admission_applicants->findBy('status', $status)->orderBy('position nachname') as $m) { + $ret[$m->user_id]['user_id'] = $m->user_id; + $ret[$m->user_id]['username'] = $m->username; + $ret[$m->user_id]['Vorname'] = $m->vorname; + $ret[$m->user_id]['Nachname'] = $m->nachname; + $ret[$m->user_id]['Email'] = $m->email; + $ret[$m->user_id]['position'] = $m->position; + $ret[$m->user_id]['status'] = $m->status; + $ret[$m->user_id]['mkdate'] = $m->mkdate; + $ret[$m->user_id]['fullname'] = $m->getUserFullname(); + } + return $ret; + } + + public function getId() + { + return $this->id; + } + + public function getName() + { + return $this->name; + } + + /** + * return the field VeranstaltungsNummer for the seminar + * + * @return string the seminar-number for the current seminar + */ + public function getNumber() + { + return $this->seminar_number; + } + + public function isVisible() + { + return $this->visible; + } + + public function getInstitutId() + { + return $this->institut_id; + } + + public function getSemesterStartTime() + { + return $this->semester_start_time; + } + + public function getSemesterDurationTime() + { + return $this->semester_duration_time; + } + + public function getNextDate($return_mode = 'string') + { + $next_date = ''; + if ($return_mode == 'int') { + echo __class__.'::'.__function__.', line '.__line__.', return_mode "int" ist not supported by this function!';die; + } + + if (!$termine = SeminarDB::getNextDate($this->id)) + return false; + + foreach ($termine['termin'] as $singledate_id) { + $next_date .= DateFormatter::formatDateAndRoom($singledate_id, $return_mode) . '
'; + } + + if (!empty($termine['ex_termin'])) { + foreach ($termine['ex_termin'] as $ex_termin_id) { + $ex_termin = new SingleDate($ex_termin_id); + $template = $GLOBALS['template_factory']->open('dates/missing_date.php'); + $template->formatted_date = DateFormatter::formatDateAndRoom($ex_termin_id, $return_mode); + $template->ex_termin = $ex_termin; + $missing_date = $template->render(); + + if (!empty($termine['termin'])) { + $termin = new SingleDate($termine['termin'][0]); + if ($ex_termin->getStartTime() <= $termin->getStartTime()) { + return $next_date . $missing_date; + } else { + return $next_date; + } + } else { + return $missing_date; + } + } + } else { + return $next_date; + } + + return false; + } + + public function getFirstDate($return_mode = 'string') { + if (!$dates = SeminarDB::getFirstDate($this->id)) { + return false; + } + + return DateFormatter::formatDateWithAllRooms(['termin' => $dates], $return_mode); + } + + /** + * This function returns an associative array of the dates owned by this seminar + * + * @returns mixed a multidimensional array of seminar-dates + */ + public function getUndecoratedData($filter = false) + { + + // Caching + $cache = \Studip\Cache\Factory::getCache(); + $cache_key = 'course/undecorated_data/'. $this->id; + + if ($filter) { + $sub_key = ($_SESSION['_language'] ?? 'none') .'/'. $this->filterStart .'-'. $this->filterEnd; + } else { + $sub_key = ($_SESSION['_language'] ?? 'none') .'/unfiltered'; + } + + $data = unserialize($cache->read($cache_key)); + + // build cache from scratch + if (empty($data) || empty($data[$sub_key])) { + $cycles = $this->metadate->getCycleData(); + $dates = $this->getSingleDates($filter, $filter); + $rooms = []; + + foreach (array_keys($cycles) as $id) { + if ($this->filterStart && $this->filterEnd + && !$this->metadate->hasDates($id, $this->filterStart, $this->filterEnd)) + { + unset($cycles[$id]); + continue; + } + + $cycles[$id]['first_date'] = CycleDataDB::getFirstDate($id); + $cycles[$id]['last_date'] = CycleDataDB::getLastDate($id); + if (!empty($cycles[$id]['assigned_rooms'])) { + foreach ($cycles[$id]['assigned_rooms'] as $room_id => $count) { + if (!isset($rooms[$room_id])) { + $rooms[$room_id] = 0; + } + $rooms[$room_id] += $count; + } + } + } + + // besser wieder mit direktem Query statt Objekten + if (is_array($cycles) && count($cycles) === 0) { + $cycles = false; + } + + $ret['regular']['turnus_data'] = $cycles; + + // the irregular single-dates + foreach ($dates as $val) { + $zw = [ + 'metadate_id' => $val->getMetaDateID(), + 'termin_id' => $val->getTerminID(), + 'date_typ' => $val->getDateType(), + 'start_time' => $val->getStartTime(), + 'end_time' => $val->getEndTime(), + 'mkdate' => $val->getMkDate(), + 'chdate' => $val->getMkDate(), + 'ex_termin' => $val->isExTermin(), + 'orig_ex' => $val->isExTermin(), + 'range_id' => $val->getRangeID(), + 'author_id' => $val->getAuthorID(), + 'resource_id' => $val->getResourceID(), + 'raum' => $val->getFreeRoomText(), + 'typ' => $val->getDateType(), + 'tostring' => $val->toString() + ]; + + if ($val->getResourceID()) { + if (!isset($rooms[$val->getResourceID()])) { + $rooms[$val->getResourceID()] = 0; + } + $rooms[$val->getResourceID()]++; + } + + $ret['irregular'][$val->getTerminID()] = $zw; + } + + $ret['rooms'] = $rooms; + $ret['ort'] = $this->location; + + $data[$sub_key] = $ret; + + // write data to cache + $cache->write($cache_key, serialize($data), 600); + } + + return $data[$sub_key]; + } + + public function getFormattedTurnus($short = FALSE) + { + // activate this with StEP 00077 + /* $cache = Cache::instance(); + * $cache_key = "formatted_turnus".$this->id; + * if (! $return_string = $cache->read($cache_key)) + * { + */ + return $this->getDatesExport(['short' => $short, 'shrink' => true]); + + // activate this with StEP 00077 + // $cache->write($cache_key, $return_string, 60*60); + // } + } + + public function getFormattedTurnusDates($short = FALSE) + { + if ($cycles = $this->metadate->getCycles()) { + $return_string = []; + foreach ($cycles as $id => $c) { + $return_string[$id] = $c->toString($short); + //hmm tja... + if ($c->description){ + $return_string[$id] .= ' ('. htmlReady($c->description) .')'; + } + } + return $return_string; + } else + return FALSE; + } + + public function getMetaDateCount() + { + return sizeof($this->metadate->cycles); + } + + public function getMetaDateValue($key, $value_name) + { + return $this->metadate->cycles[$key]->$value_name; + } + + public function setMetaDateValue($key, $value_name, $value) + { + $this->metadate->cycles[$key]->$value_name = $value; + } + + /** + * restore the data + * + * the complete data of the object will be loaded from the db + * @access public + * @throws Exception if there is no such course + * @return boolean always true + */ + public function restore() + { + if ($this->course->id) { + $this->course->restore(); + } + $this->irregularSingleDates = null; + $this->issues = null; + $this->_metadate = null; + $this->course_set = null; + + return TRUE; + } + + /** + * returns an array of variables from the seminar-object, excluding variables + * containing objects or arrays + * + * @return array + */ + public function getSettings() { + $settings = $this->course->toRawArray(); + unset($settings['config']); + return $settings; + } + + public function store($trigger_chdate = true) + { + // activate this with StEP 00077 + // $cache = Cache::instance(); + // $cache->expire("formatted_turnus".$this->id); + + //check for security consistency + if ($this->write_level < $this->read_level) // hier wusste ein Lehrender nicht, was er tat + $this->write_level = $this->read_level; + + if ($this->irregularSingleDates) { + foreach ($this->irregularSingleDates as $val) { + $val->store(); + } + } + + if ($this->issues) { + foreach ($this->issues as $val) { + $val->store(); + } + } + + $metadate_changed = isset($this->metadate) ? $this->metadate->store() : 0; + $course_changed = $this->course->store(); + if ($metadate_changed && $trigger_chdate) { + return $this->course->triggerChdate(); + } else { + return $course_changed ?: false; + } + } + + public function setStartSemester($start) + { + global $perm; + + if ($perm->have_perm('tutor') && $start != $this->semester_start_time) { + // logging >>>>>> + StudipLog::log("SEM_SET_STARTSEMESTER", $this->getId(), $start); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + // logging <<<<<< + $this->semester_start_time = $start; + $this->metadate->setSeminarStartTime($start); + $this->createMessage(_("Das Startsemester wurde geändert.")); + $this->createInfo(_("Beachten Sie, dass Termine, die nicht mit den Einstellungen der regelmäßigen Zeit übereinstimmen (z.B. auf Grund einer Verschiebung der regelmäßigen Zeit), teilweise gelöscht sein könnten!")); + return TRUE; + } + return FALSE; + } + + public function removeAndUpdateSingleDates() + { + SeminarCycleDate::removeOutRangedSingleDates( + $this->semester_start_time, + $this->getEndSemesterVorlesEnde(), + $this->id + ); + + foreach ($this->metadate->cycles as $key => $val) { + $this->metadate->cycles[$key]->readSingleDates(); + $this->metadate->createSingleDates($key); + $this->metadate->cycles[$key]->termine = NULL; + } + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + } + + public function getStartSemester() + { + return $this->semester_start_time; + } + + /* + * setEndSemester + * @param end integer 0 (one Semester), -1 (eternal), or timestamp of last happening semester + * @returns TRUE on success, FALSE on failure + */ + public function setEndSemester($end) + { + global $perm; + + $previousEndSemester = $this->getEndSemester(); // save the end-semester before it is changed, so we can choose lateron in which semesters we need to be rebuilt the SingleDates + + if ($end != $this->getEndSemester()) { // only change Duration if it differs from the current one + + if ($end == 0) { // the seminar takes place just in the selected start-semester + $this->semester_duration_time = 0; + $this->metadate->setSeminarDurationTime(0); + // logging >>>>>> + StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end, 'Laufzeit: 1 Semester'); + // logging <<<<<< + } else if ($end == -1) { // the seminar takes place in every semester above and including the start-semester + // logging >>>>>> + StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end, 'Laufzeit: unbegrenzt'); + // logging <<<<<< + $this->semester_duration_time = -1; + $this->metadate->setSeminarDurationTime(-1); + SeminarCycleDate::removeOutRangedSingleDates( + $this->semester_start_time, + $this->getEndSemesterVorlesEnde(), + $this->id + ); + } else { // the seminar takes place between the selected start~ and end-semester + // logging >>>>>> + StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end); + // logging <<<<<< + $this->semester_duration_time = $end - $this->semester_start_time; // the duration is stored, not the real end-point + $this->metadate->setSeminarDurationTime($this->semester_duration_time); + } + + $this->createMessage(_("Die Dauer wurde geändert.")); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + + /* + * If the duration has been changed, we have to create new SingleDates + * if the new duration is longer than the previous one + */ + if ( ($previousEndSemester != -1) && ( ($previousEndSemester < $this->getEndSemester()) || (($previousEndSemester == 0) && ($this->getEndSemester() == -1) ) )) { + // if the previous duration was unlimited, the only option choosable is + // a shorter duration then 'ever', so there cannot be any new SingleDates + + // special case: if the previous selection was 'one semester' and the new one is 'eternal', + // than we have to find out the end of the only semester, the start-semester + if ($previousEndSemester == 0) { + $startAfterTimeStamp = $this->course->start_semester->ende; + } else { + $startAfterTimeStamp = $previousEndSemester; + } + + foreach ($this->metadate->cycles as $key => $val) { + $this->metadate->createSingleDates(['metadate_id' => $key, 'startAfterTimeStamp' => $startAfterTimeStamp]); + $this->metadate->cycles[$key]->termine = NULL; // emtpy the SingleDates for each cycle, so that SingleDates, which were not in the current view, are not loaded and therefore should not be visible + } + } + } + + return TRUE; + } + + /* + * getEndSemester + * @returns 0 (one Semester), -1 (eternal), or TimeStamp of last Semester for this Seminar + */ + public function getEndSemester() + { + if ($this->semester_duration_time == 0) return 0; // seminar takes place only in the start-semester + if ($this->semester_duration_time == -1) return -1; // seminar takes place eternally + return $this->semester_start_time + $this->semester_duration_time; // seminar takes place between start~ and end-semester + } + + public function getEndSemesterVorlesEnde() + { + if ($this->semester_duration_time == -1) { + $semesters = Semester::getAll(); + $very_last_semester = array_pop($semesters); + return $very_last_semester->vorles_ende; + } + return $this->course->end_semester->vorles_ende; + } + + /** + * return the name of the seminars start-semester + * + * @return string the name of the start-semester or false if there is no start-semester + */ + public function getStartSemesterName() + { + return $this->course->start_semester->name; + } + + /** + * return an array of singledate-objects for the submitted cycle identified by metadate_id + * + * @param string $metadate_id the id identifying the cycle + * + * @return mixed an array of singledate-objects + */ + public function readSingleDatesForCycle($metadate_id) + { + return $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); + } + + public function readSingleDates($force = FALSE, $filter = FALSE) + { + if (!$force) { + if (is_array($this->irregularSingleDates)) { + return TRUE; + } + } + $this->irregularSingleDates = []; + + if ($filter) { + $data = SeminarDB::getSingleDates($this->id, $this->filterStart, $this->filterEnd); + } else { + $data = SeminarDB::getSingleDates($this->id); + } + + foreach ($data as $val) { + unset($termin); + $termin = new SingleDate(); + $termin->fillValuesFromArray($val); + $this->irregularSingleDates[$val['termin_id']] =& $termin; + } + } + + public function &getSingleDate($singleDateID, $cycle_id = '') + { + if ($cycle_id == '') { + $this->readSingleDates(); + return $this->irregularSingleDates[$singleDateID]; + } else { + $dates = $this->metadate->getSingleDates($cycle_id, $this->filterStart, $this->filterEnd); + $data =& $dates; + return $data[$singleDateID]; + } + } + + public function &getSingleDates($filter = false, $force = false, $include_deleted_dates = false) + { + $this->readSingleDates($force, $filter); + if (!$include_deleted_dates) { + return $this->irregularSingleDates; + } else { + $deleted_dates = []; + foreach (SeminarDB::getDeletedSingleDates($this->getId(), $this->filterStart, $this->filterEnd) as $val) { + $termin = new SingleDate(); + $termin->fillValuesFromArray($val); + $deleted_dates[$val['termin_id']] = $termin; + } + $dates = array_merge($this->irregularSingleDates, $deleted_dates); + uasort($dates, function($a,$b) { + if ($a->getStartTime() == $b->getStartTime()) return 0; + return $a->getStartTime() < $b->getStartTime() ? -1 : 1;} + ); + return $dates; + } + } + + public function getCycles() + { + return $this->metadate->getCycles(); + } + + public function &getSingleDatesForCycle($metadate_id) + { + if (!$this->metadate->cycles[$metadate_id]->termine) { + $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); + if (!$this->metadate->cycles[$metadate_id]->termine) { + $this->readSingleDates(); + $this->metadate->createSingleDates($metadate_id, $this->irregularSingleDates); + $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); + } + //$this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); + } + $dates = $this->metadate->getSingleDates($metadate_id, $this->filterStart, $this->filterEnd); + return $dates; + } + + public function readIssues($force = false) + { + if (!is_array($this->issues) || $force) { + $this->issues = []; + $data = SeminarDB::getIssues($this->id); + + foreach ($data as $val) { + unset($issue); + $issue = new Issue(); + $issue->fillValuesFromArray($val); + $this->issues[$val['issue_id']] =& $issue; + } + } + } + + public function addSingleDate(&$singledate) + { + // logging >>>>>> + StudipLog::log("SEM_ADD_SINGLEDATE", $this->getId(), $singledate->toString(), 'SingleDateID: '.$singledate->getTerminID()); + // logging <<<<<< + + $cache = \Studip\Cache\Factory::getCache(); + $cache->expire('course/undecorated_data/'. $this->getId()); + + $this->readSingleDates(); + $this->irregularSingleDates[$singledate->getSingleDateID()] =& $singledate; + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + return TRUE; + } + + public function addIssue(&$issue) + { + $this->readIssues(); + if ($issue instanceof Issue) { + $max = -1; + if (is_array($this->issues)) foreach ($this->issues as $val) { + if ($val->getPriority() > $max) { + $max = $val->getPriority(); + } + } + $max++; + $issue->setPriority($max); + $this->issues[$issue->getIssueID()] =& $issue; + return TRUE; + } else { + return FALSE; + } + } + + public function deleteSingleDate($date_id, $cycle_id = '') + { + $this->readSingleDates(); + // logging >>>>>> + StudipLog::log("SEM_DELETE_SINGLEDATE",$date_id, $this->getId(), 'Cycle_id: '.$cycle_id); + // logging <<<<<< + if ($cycle_id == '') { + $this->irregularSingleDates[$date_id]->delete(true); + unset ($this->irregularSingleDates[$date_id]); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + return TRUE; + } else { + $this->metadate->deleteSingleDate($cycle_id, $date_id, $this->filterStart, $this->filterEnd); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + return TRUE; + } + } + + public function cancelSingleDate($date_id, $cycle_id = '') + { + if ($cycle_id) { + return $this->deleteSingleDate($date_id, $cycle_id); + } + $this->readSingleDates(); + // logging >>>>>> + StudipLog::log("SEM_DELETE_SINGLEDATE",$date_id, $this->getId(), 'appointment cancelled'); + // logging <<<<<< + $this->irregularSingleDates[$date_id]->setExTermin(true); + $this->irregularSingleDates[$date_id]->store(); + unset ($this->irregularSingleDates[$date_id]); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + return TRUE; + } + + public function unDeleteSingleDate($date_id, $cycle_id = '') + { + // logging >>>>>> + StudipLog::log("SEM_UNDELETE_SINGLEDATE",$date_id, $this->getId(), 'Cycle_id: '.$cycle_id); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + // logging <<<<<< + if ($cycle_id == '') { + $termin = new SingleDate($date_id); + if (!$termin->isExTermin()) { + return false; + } + $termin->setExTermin(false); + $termin->store(); + return true; + } else { + return $this->metadate->unDeleteSingleDate($cycle_id, $date_id, $this->filterStart, $this->filterEnd); + } + } + + /** + * return all stacked messages as a multidimensional array + * + * The array has the following structure: + * array( 'type' => ..., 'message' ... ) + * where type is one of error, info and success + * + * @return mixed the array of stacked messages + */ + public function getStackedMessages() + { + if ( is_array( $this->message_stack ) ) { + $ret = []; + + // cycle through message types and set title and details appropriate + foreach ($this->message_stack as $type => $messages ) { + switch ( $type ) { + case 'error': + $ret['error'] = [ + 'title' => _("Es sind Fehler/Probleme aufgetreten!"), + 'details' => $this->message_stack['error'] + ]; + break; + + case 'info': + $ret['info'] = [ + 'title' => implode('
', $this->message_stack['info']), + 'details' => [] + ]; + break; + + case 'success': + $ret['success'] = [ + 'title' => _("Ihre Änderungen wurden gespeichert!"), + 'details' => $this->message_stack['success'] + ]; + break; + } + } + + return $ret; + } + + return false; + } + + /** + * return the next stacked messag-string + * + * @return string a message-string + */ + public function getNextMessage() + { + if ($this->messages[0]) { + $ret = $this->messages[0]; + unset ($this->messages[0]); + sort($this->messages); + return $ret; + } + return FALSE; + } + + /** + * stack an error-message + * + * @param string $text the message to stack + */ + public function createError($text) + { + $this->messages[] = 'error§'.$text.'§'; + $this->message_stack['error'][] = $text; + } + + /** + * stack an info-message + * + * @param string $text the message to stack + */ + public function createInfo($text) + { + $this->messages[] = 'info§'.$text.'§'; + $this->message_stack['info'][] = $text; + } + + /** + * stack a success-message + * + * @param string $text the message to stack + */ + public function createMessage($text) + { + $this->messages[] = 'msg§'.$text.'§'; + $this->message_stack['success'][] = $text; + } + + /** + * add an array of messages to the message-stack + * + * @param mixed $messages array of pre-marked message-strings + * @param bool returns true on success + */ + public function appendMessages( $messages ) + { + if (!is_array($messages)) return false; + + foreach ( $messages as $type => $msgs ) { + foreach ($msgs as $msg) { + $this->message_stack[$type][] = $msg; + } + } + return true; + } + + public function addCycle($data = []) + { + $new_id = $this->metadate->addCycle($data); + if($new_id){ + $this->setStartWeek($data['startWeek'], $new_id); + $this->setTurnus($data['turnus'], $new_id); + } + // logging >>>>>> + if($new_id){ + $cycle_info = $this->metadate->cycles[$new_id]->toString(); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + StudipLog::log("SEM_ADD_CYCLE", $this->getId(), NULL, $cycle_info, '
'.print_r($data,true).'
'); + } + // logging <<<<<< + return $new_id; + } + + /** + * Change a regular timeslot of the seminar. The data is passed as an array + * conatining the following fields: + * start_stunde, start_minute, end_stunde, end_minute + * description, turnus, startWeek, day, sws + * + * @param array $data the cycle-data + * + * @return void + */ + public function editCycle($data = []) + { + $cycle = $this->metadate->cycles[$data['cycle_id']]; + $new_start = mktime($data['start_stunde'], $data['start_minute']); + $new_end = mktime($data['end_stunde'], $data['end_minute']); + $old_start = mktime($cycle->getStartStunde(),$cycle->getStartMinute()); + $old_end = mktime($cycle->getEndStunde(), $cycle->getEndMinute()); + $do_changes = false; + + // check, if the new timeslot exceeds the old one + if (($new_start < $old_start) || ($new_end > $old_end) || ($data['day'] != $cycle->day) ) { + $has_bookings = false; + + // check, if there are any booked rooms + foreach($cycle->getSingleDates() as $singleDate) { + if ($singleDate->getStarttime() > (time() - 3600) && $singleDate->hasRoom()) { + $has_bookings = true; + break; + } + } + + // if the timeslot exceeds the previous one and has some booked rooms + // they would be lost, so ask the user for permission to do so. + if (!$data['really_change'] && $has_bookings) { + $link_params = [ + 'editCycle_x' => '1', + 'editCycle_y' => '1', + 'cycle_id' => $data['cycle_id'], + 'start_stunde' => $data['start_stunde'], + 'start_minute' => $data['start_minute'], + 'end_stunde' => $data['end_stunde'], + 'end_minute' => $data['end_minute'], + 'day' => $data['day'], + 'really_change' => 'true' + ]; + $question = _("Wenn Sie die regelmäßige Zeit auf %s ändern, verlieren Sie die Raumbuchungen für alle in der Zukunft liegenden Termine!") + ."\n". _("Sind Sie sicher, dass Sie die regelmäßige Zeit ändern möchten?"); + $question_time = '**'. strftime('%A', $data['day']) .', '. $data['start_stunde'] .':'. $data['start_minute'] + .' - '. $data['end_stunde'] .':'. $data['end_minute'] .'**'; + + echo (string)QuestionBox::create( + sprintf($question, $question_time), + URLHelper::getURL('', $link_params) + ); + + } else { + $do_changes = true; + } + } else { + $do_changes = true; + } + + $messages = false; + $same_time = false; + + // only apply changes, if the user approved the change or + // the change does not need any approval + if ($do_changes) { + if ($data['description'] != $cycle->getDescription()) { + $this->createMessage(_("Die Beschreibung des regelmäßigen Eintrags wurde geändert.")); + $message = true; + $do_changes = true; + } + + if ($old_start == $new_start && $old_end == $new_end) { + $same_time = true; + } + if ($data['startWeek'] != $cycle->week_offset) { + $this->setStartWeek($data['startWeek'], $cycle->metadate_id); + $message = true; + $do_changes = true; + } + if ($data['turnus'] != $cycle->cycle) { + $this->setTurnus($data['turnus'], $cycle->metadate_id); + $message = true; + $do_changes = true; + } + if ($data['day'] != $cycle->day) { + $message = true; + $same_time = false; + $do_changes = true; + } + if (round(str_replace(',','.', $data['sws']),1) != $cycle->sws) { + $cycle->sws = $data['sws']; + $this->createMessage(_("Die Semesterwochenstunden für Lehrende des regelmäßigen Eintrags wurden geändert.")); + $message = true; + $do_changes = true; + } + + $change_from = $cycle->toString(); + if ($this->metadate->editCycle($data)) { + if (!$same_time) { + // logging >>>>>> + StudipLog::log("SEM_CHANGE_CYCLE", $this->getId(), NULL, $change_from .' -> '. $cycle->toString()); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + // logging <<<<<< + $this->createMessage(sprintf(_("Die regelmäßige Veranstaltungszeit wurde auf \"%s\" für alle in der Zukunft liegenden Termine geändert!"), + ''.getWeekday($data['day']) . ', ' . $data['start_stunde'] . ':' . $data['start_minute'].' - '. + $data['end_stunde'] . ':' . $data['end_minute'] . '')); + $message = true; + } + } else { + if (!$same_time) { + $this->createInfo(sprintf(_("Die regelmäßige Veranstaltungszeit wurde auf \"%s\" geändert, jedoch gab es keine Termine die davon betroffen waren."), + ''.getWeekday($data['day']) . ', ' . $data['start_stunde'] . ':' . $data['start_minute'].' - '. + $data['end_stunde'] . ':' . $data['end_minute'] . '')); + $message = true; + } + } + $this->metadate->sortCycleData(); + + if (!$message) { + $this->createInfo("Sie haben keine Änderungen vorgenommen!"); + } + } + } + + public function deleteCycle($cycle_id) + { + // logging >>>>>> + $cycle_info = $this->metadate->cycles[$cycle_id]->toString(); + StudipLog::log("SEM_DELETE_CYCLE", $this->getId(), NULL, $cycle_info); + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + // logging <<<<<< + return $this->metadate->deleteCycle($cycle_id); + } + + public function setTurnus($turnus, $metadate_id = false) + { + if ($this->metadate->getTurnus($metadate_id) != $turnus) { + $this->metadate->setTurnus($turnus, $metadate_id); + $key = $metadate_id ? $metadate_id : $this->metadate->getFirstMetadate()->metadate_id; + $this->createMessage(sprintf(_("Der Turnus für den Termin %s wurde geändert."), $this->metadate->cycles[$key]->toString())); + $this->metadate->createSingleDates($key); + $this->metadate->cycles[$key]->termine = null; + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + } + return TRUE; + } + + public function getTurnus($metadate_id = false) + { + return $this->metadate->getTurnus($metadate_id); + } + + + /** + * get StatOfNotBookedRooms returns an array: + * open: number of rooms with no booking + * all: number of singleDates, which can have a booking + * open_rooms: array of singleDates which have no booking + * + * @param String $cycle_id Id of cycle + * @return array as described above + */ + public function getStatOfNotBookedRooms($cycle_id) + { + if (!isset($this->BookedRoomsStatTemp[$cycle_id])) { + $this->BookedRoomsStatTemp[$cycle_id] = SeminarDB::getStatOfNotBookedRooms($cycle_id, $this->id, $this->filterStart, $this->filterEnd); + } + return $this->BookedRoomsStatTemp[$cycle_id]; + } + + public function getStatus() + { + return $this->status; + } + + public function getBookedRoomsTooltip($cycle_id) + { + $stat = $this->getStatOfNotBookedRooms($cycle_id); + $pattern = '%s , %s, %s-%s
'; + $return = ''; + if ($stat['open'] > 0 && $stat['open'] !== $stat['all']) { + $return = _('Folgende Termine haben keine Raumbuchung:') . '
'; + + foreach ($stat['open_rooms'] as $aSingleDate) { + $return .= sprintf($pattern,strftime('%a', $aSingleDate['date']), + strftime('%d.%m.%Y', $aSingleDate['date']), + strftime('%H:%M', $aSingleDate['date']), + strftime('%H:%M', $aSingleDate['end_time'])); + } + } + + // are there any dates with declined room-requests? + if ($stat['declined'] > 0) { + $return .= _('Folgende Termine haben eine abgelehnte Raumanfrage') . '
'; + foreach ($stat['declined_dates'] as $aSingleDate) { + $return .= sprintf($pattern,strftime('%a', $aSingleDate['date']), + strftime('%d.%m.%Y', $aSingleDate['date']), + strftime('%H:%M', $aSingleDate['date']), + strftime('%H:%M', $aSingleDate['end_time'])); + } + } + + return $return; + } + + /** + * @param $cycle_id + * @return string + */ + public function getCycleColorClass($cycle_id) + { + if (Config::get()->RESOURCES_ENABLE && Config::get()->RESOURCES_ENABLE_BOOKINGSTATUS_COLORING) { + if (!$this->metadate->hasDates($cycle_id, $this->filterStart, $this->filterEnd)) { + return 'red'; + } + + $stat = $this->getStatOfNotBookedRooms($cycle_id); + + if ($stat['open'] > 0 && $stat['open'] == $stat['all']) { + return 'red'; + } + if ($stat['open'] > 0) { + return 'yellow '; + } + return 'green '; + } + + return ''; + } + + public function &getIssues($force = false) + { + $this->readIssues($force); + $this->renumberIssuePrioritys(); + if (is_array($this->issues)) { + uasort($this->issues, function ($a, $b) { + return $a->getPriority() - $b->getPriority(); + }); + } + return $this->issues; + } + + public function deleteIssue($issue_id) + { + $this->issues[$issue_id]->delete(); + unset($this->issues[$issue_id]); + return TRUE; + } + + public function &getIssue($issue_id) + { + $this->readIssues(); + return $this->issues[$issue_id]; + } + + /* + * changeIssuePriority + * + * changes an issue with an given id to a new priority + * + * @param + * issue_id the issue_id of the issue to be changed + * new_priority the new priority + */ + public function changeIssuePriority($issue_id, $new_priority) + { + /* REMARK: + * This function only works, when an issue is moved ONE slote higher or lower + * It does NOT work with ARBITRARY movements! + */ + $this->readIssues(); + $old_priority = $this->issues[$issue_id]->getPriority(); // get old priority, so we can just exchange prioritys of two issues + foreach ($this->issues as $id => $issue) { // search for the concuring issue + if ($issue->getPriority() == $new_priority) { + $this->issues[$id]->setPriority($old_priority); // the concuring issue gets the old id of the changed issue + $this->issues[$id]->store(); // ###store_problem### + } + } + + $this->issues[$issue_id]->setPriority($new_priority); // changed issue gets the new priority + $this->issues[$issue_id]->store(); // ###store_problem### + + } + + public function renumberIssuePrioritys() + { + if (is_array($this->issues)) { + + $sorter = []; + foreach ($this->issues as $id => $issue) { + $sorter[$id] = $issue->getPriority(); + } + asort($sorter); + $i = 0; + foreach ($sorter as $id => $old_priority) { + $this->issues[$id]->setPriority($i); + $i++; + } + } + } + + public function autoAssignIssues($themen, $cycle_id) + { + $this->metadate->cycles[$cycle_id]->autoAssignIssues($themen, $this->filterStart, $this->filterEnd); + } + + + public function applyTimeFilter($start, $end) + { + $this->filterStart = $start; + $this->filterEnd = $end; + } + + public function setFilter($timestamp) + { + if ($timestamp == 'all') { + $_SESSION['raumzeitFilter'] = 'all'; + $this->applyTimeFilter(0, 0); + } else { + $filterSemester = Semester::findByTimestamp($timestamp); + $_SESSION['raumzeitFilter'] = $filterSemester->beginn; + $this->applyTimeFilter($filterSemester->beginn, $filterSemester->ende); + } + } + + public function registerCommand($command, $function) + { + $this->commands[$command] = $function; + } + + public function processCommands() + { + global $cmd; + + // workaround for multiple submit-buttons with new Button-API + foreach ($this->commands as $r_cmd => $func) { + if (Request::submitted($r_cmd)) { + $cmd = $r_cmd; + } + } + + if (!isset($cmd) && Request::option('cmd')) $cmd = Request::option('cmd'); + if (!isset($cmd)) return FALSE; + + if (isset($this->commands[$cmd])) { + call_user_func($this->commands[$cmd], $this); + } + } + + public function getFreeTextPredominantRoom($cycle_id) + { + if (!($room = $this->metadate->cycles[$cycle_id]->getFreeTextPredominantRoom($this->filterStart, $this->filterEnd))) { + return FALSE; + } + return $room; + } + + public function getPredominantRoom($cycle_id, $list = FALSE) + { + if (!($rooms = $this->metadate->cycles[$cycle_id]->getPredominantRoom($this->filterStart, $this->filterEnd))) { + return FALSE; + } + if ($list) { + return $rooms; + } else { + return $rooms[0]; + } + } + + + public function hasDatesOutOfDuration($force = false) + { + if ($this->hasDatesOutOfDuration == -1 || $force) { + $this->hasDatesOutOfDuration = SeminarDB::hasDatesOutOfDuration($this->getStartSemester(), $this->getEndSemesterVorlesEnde(), $this->id); + } + return $this->hasDatesOutOfDuration; + } + + public function getStartWeek($metadate_id = false) + { + return $this->metadate->getStartWoche($metadate_id); + } + + public function setStartWeek($week, $metadate_id = false) + { + if ($this->metadate->getStartWoche($metadate_id) == $week) { + return FALSE; + } else { + $this->metadate->setStartWoche($week, $metadate_id); + $key = $metadate_id ? $metadate_id : $this->metadate->getFirstMetadate()->metadate_id; + $this->createMessage(sprintf(_("Die Startwoche für den Termin %s wurde geändert."), $this->metadate->cycles[$key]->toString())); + $this->metadate->createSingleDates($key); + $this->metadate->cycles[$key]->termine = null; + NotificationCenter::postNotification("CourseDidChangeSchedule", $this); + } + } + + + /** + * instance method + * + * returns number of participants for each usergroup in seminar, + * total, lecturers, tutors, authors, users + * + * @param string (optional) return count only for given usergroup + * + * @return array + */ + + public function getNumberOfParticipants() + { + $args = func_get_args(); + array_unshift($args, $this->id); + return call_user_func_array(["Seminar", "getNumberOfParticipantsBySeminarId"], $args); + } + + /** + * class method + * + * returns number of participants for each usergroup in given seminar, + * total, lecturers, tutors, authors, users + * + * @param string seminar_id + * + * @param string (optional) return count only for given usergroup + * + * @return array + */ + + public function getNumberOfParticipantsBySeminarId($sem_id) + { + $db = DBManager::get(); + $stmt1 = $db->prepare("SELECT + COUNT(Seminar_id) AS anzahl, + COUNT(IF(status='dozent',Seminar_id,NULL)) AS anz_dozent, + COUNT(IF(status='tutor',Seminar_id,NULL)) AS anz_tutor, + COUNT(IF(status='autor',Seminar_id,NULL)) AS anz_autor, + COUNT(IF(status='user',Seminar_id,NULL)) AS anz_user + FROM seminar_user + WHERE Seminar_id = ? + GROUP BY Seminar_id"); + $stmt1->execute([$sem_id]); + $numbers = $stmt1->fetch(PDO::FETCH_ASSOC); + + $stmt2 = $db->prepare("SELECT COUNT(*) as anzahl + FROM admission_seminar_user + WHERE seminar_id = ? + AND status = 'accepted'"); + $stmt2->execute([$sem_id]); + $acceptedUsers = $stmt2->fetch(PDO::FETCH_ASSOC); + + + $count = 0; + if ($numbers["anzahl"]) { + $count += $numbers["anzahl"]; + } + if ($acceptedUsers["anzahl"]) { + $count += $acceptedUsers["anzahl"]; + } + + $participant_count = []; + $participant_count['total'] = $count; + $participant_count['lecturers'] = $numbers['anz_dozent'] ? (int) $numbers['anz_dozent'] : 0; + $participant_count['tutors'] = $numbers['anz_tutor'] ? (int) $numbers['anz_tutor'] : 0; + $participant_count['authors'] = $numbers['anz_autor'] ? (int) $numbers['anz_autor'] : 0; + $participant_count['users'] = $numbers['anz_user'] ? (int) $numbers['anz_user'] : 0; + + // return specific parameter if + $params = func_get_args(); + if (sizeof($params) > 1) { + if (in_array($params[1], array_keys($participant_count))) { + return $participant_count[$params[1]]; + } else { + trigger_error(get_class($this)."::__getParticipantInfos - unknown parameter requested"); + } + } + + return $participant_count; + } + + + /** + * Returns the IDs of this course's study areas. + * + * @return array an array of IDs + */ + public function getStudyAreas() + { + $stmt = DBManager::get()->prepare("SELECT DISTINCT sem_tree_id ". + "FROM seminar_sem_tree ". + "WHERE seminar_id=?"); + + $stmt->execute([$this->id]); + return $stmt->fetchAll(PDO::FETCH_COLUMN, 0); + } + + /** + * Sets the study areas of this course. + * + * @param array an array of IDs + * + * @return void + */ + public function setStudyAreas($selected) + { + $old = $this->getStudyAreas(); + $sem_tree = TreeAbstract::GetInstance("StudipSemTree"); + $removed = array_diff($old, $selected); + $added = array_diff($selected, $old); + $count_removed = 0; + $count_added = 0; + foreach($removed as $one){ + $count_removed += $sem_tree->DeleteSemEntries($one, $this->getId()); + } + foreach($added as $one){ + $count_added += $sem_tree->InsertSemEntry($one, $this->getId()); + } + if ($count_added || $count_removed) { + NotificationCenter::postNotification("CourseDidChangeStudyArea", $this); + } + return count($old) + $count_added - $count_removed; + } + + /** + * @return boolean returns TRUE if this course is publicly visible, + * FALSE otherwise + */ + public function isPublic() + { + return Config::get()->ENABLE_FREE_ACCESS && $this->read_level == 0; + } + + /** + * @return boolean returns TRUE if this course is a studygroup, + * FALSE otherwise + */ + public function isStudygroup() + { + global $SEM_CLASS, $SEM_TYPE; + return $SEM_CLASS[$SEM_TYPE[$this->status]["class"]]["studygroup_mode"]; + } + + /** + * @return int returns default colour group for new members (shown in meine_seminare.php) + * + **/ + public function getDefaultGroup() + { + if ($this->isStudygroup()) { + return 8; + } else { + return select_group ($this->semester_start_time); + } + } + + + /** + * Deletes the current seminar + * + * @return void returns success-message if seminar could be deleted + * otherwise an error-message + */ + + public function delete() + { + $s_id = $this->id; + + // Delete that Seminar. + + // Alle Benutzer aus dem Seminar rauswerfen. + $db_ar = CourseMember::deleteBySQL('Seminar_id = ?', [$s_id]); + if ($db_ar > 0) { + $this->createMessage(sprintf(_("%s Teilnehmende und Lehrende archiviert."), $db_ar)); + } + + // Alle Benutzer aus Wartelisten rauswerfen + AdmissionApplication::deleteBySQL('seminar_id = ?', [$s_id]); + + // Alle beteiligten Institute rauswerfen + $query = "DELETE FROM seminar_inst WHERE Seminar_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$s_id]); + if (($db_ar = $statement->rowCount()) > 0) { + $this->createMessage(sprintf(_("%s Zuordnungen zu Einrichtungen archiviert."), $db_ar)); + } + + // user aus den Statusgruppen rauswerfen + $count = Statusgruppen::deleteBySQL('range_id = ?', [$s_id]); + if ($count > 0) { + $this->createMessage(sprintf(_('%s Funktionen/Gruppen gelöscht.'), $count)); + } + + // seminar_sem_tree entries are deleted automatically on deletion of the Course object. + + // Alle Termine mit allem was dranhaengt zu diesem Seminar loeschen. + if (($db_ar = SingleDateDB::deleteAllDates($s_id)) > 0) { + $this->createMessage(sprintf(_("%s Veranstaltungstermine archiviert."), $db_ar)); + } + + //Themen + IssueDB::deleteAllIssues($s_id); + + //Cycles + SeminarCycleDate::deleteBySQL('seminar_id = ' . DBManager::get()->quote($s_id)); + + // Alle weiteren Postings zu diesem Seminar in den Forums-Modulen löschen + foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) { + $plugin->deleteContents($s_id); // delete content irrespective of plugin-activation in the seminar + + if ($plugin->isActivated($s_id)) { // only show a message, if the plugin is activated, to not confuse the user + $this->createMessage(sprintf(_('Einträge in %s archiviert.'), $plugin->getPluginName())); + } + } + + // Alle Pluginzuordnungen entfernen + PluginManager::getInstance()->deactivateAllPluginsForRange('sem', $s_id); + + // Alle Dokumente zu diesem Seminar loeschen. + $folder = Folder::findTopFolder($s_id); + if($folder) { + if($folder->delete()) { + $this->createMessage(_("Dokumente und Ordner archiviert.")); + } + } + + + // Freie Seite zu diesem Seminar löschen + $db_ar = StudipScmEntry::deleteBySQL('range_id = ?', [$s_id]); + if ($db_ar > 0) { + $this->createMessage(_("Freie Seite der Veranstaltung archiviert.")); + } + + // Alle News-Verweise auf dieses Seminar löschen + if ( ($db_ar = StudipNews::DeleteNewsRanges($s_id)) ) { + $this->createMessage(sprintf(_("%s Ankündigungen gelöscht."), $db_ar)); + } + //delete entry in news_rss_range + StudipNews::UnsetRssId($s_id); + + //kill the datafields + DataFieldEntry::removeAll($s_id); + + //kill all wiki-pages + $db_wiki = WikiPage::deleteBySQL('range_id = ?', [$s_id]); + if ($db_wiki > 0) { + $this->createMessage(sprintf(_("%s Wiki-Seiten archiviert."), $db_wiki)); + } + + $query = "DELETE FROM wiki_links WHERE range_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$s_id]); + + // delete course config values + ConfigValue::deleteBySQL('range_id = ?', [$s_id]); + + // kill all the ressources that are assigned to the Veranstaltung (and all the linked or subordinated stuff!) + if (Config::get()->RESOURCES_ENABLE) { + ResourceBooking::deleteBySql( + 'range_id = :course_id', + [ + 'course_id' => $s_id + ] + ); + if ($rr = RoomRequest::existsByCourse($s_id)) { + RoomRequest::find($rr)->delete(); + } + } + + // kill virtual seminar-entries in calendar + $query = "DELETE FROM schedule_seminare WHERE seminar_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$s_id]); + + if(Config::get()->ELEARNING_INTERFACE_ENABLE){ + global $connected_cms; + $del_cms = 0; + $cms_types = ObjectConnections::GetConnectedSystems($s_id); + if(count($cms_types)){ + foreach($cms_types as $system){ + ELearningUtils::loadClass($system); + $del_cms += $connected_cms[$system]->deleteConnectedModules($s_id); + } + $this->createMessage(sprintf(_("%s Verknüpfungen zu externen Systemen gelöscht."), $del_cms )); + } + } + + //kill the object_user_vists for this seminar + object_kill_visits(null, $s_id); + + // Logging... + $query = "SELECT CONCAT(seminare.VeranstaltungsNummer, ' ', seminare.name, '(', semester_data.name, ')') + FROM seminare + LEFT JOIN semester_data ON (seminare.start_time = semester_data.beginn) + WHERE seminare.Seminar_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$s_id]); + $semlogname = $statement->fetchColumn() ?: sprintf('unknown sem_id: %s', $s_id); + + StudipLog::log("SEM_ARCHIVE",$s_id,NULL,$semlogname); + // ...logged + + // delete deputies if necessary + Deputy::deleteByRange_id($s_id); + + UserDomain::removeUserDomainsForSeminar($s_id); + + AutoInsert::deleteSeminar($s_id); + + //Anmeldeset Zordnung entfernen + $cs = $this->getCourseSet(); + if ($cs) { + CourseSet::removeCourseFromSet($cs->getId(), $this->getId()); + $cs->load(); + if (!count($cs->getCourses()) + && $cs->isGlobal() + && $cs->getUserid() != '') { + $cs->delete(); + } + } + AdmissionPriority::unsetAllPrioritiesForCourse($this->getId()); + // und das Seminar loeschen. + $this->course->delete(); + $this->restore(); + return true; + } + + /** + * returns a html representation of the seminar-dates + * + * @param array optional variables which are passed to the template + * @return string the html-representation of the dates + * + * @author Till Glöggler + */ + public function getDatesHTML($params = []) + { + return $this->getDatesTemplate('dates/seminar_html.php', $params); + } + + /** + * returns a representation without html of the seminar-dates + * + * @param array optional variables which are passed to the template + * @return string the representation of the dates without html + * + * @author Till Glöggler + */ + public function getDatesExport($params = []) + { + return $this->getDatesTemplate('dates/seminar_export.php', $params); + } + + /** + * returns a xml-representation of the seminar-dates + * + * @param array optional variables which are passed to the template + * @return string the xml-representation of the dates + * + * @author Till Glöggler + */ + public function getDatesXML($params = []) + { + return $this->getDatesTemplate('dates/seminar_xml.php', $params); + } + + /** + * returns a representation of the seminar-dates with a specifiable template + * + * @param mixed this can be a template-object or a string pointing to a template in path_to_studip/templates + * @param array optional parameters which are passed to the template + * @return string the template output of the dates + * + * @author Till Glöggler + */ + public function getDatesTemplate($template, $params = []) + { + if (!$template instanceof Flexi\Template && is_string($template)) { + $template = $GLOBALS['template_factory']->open($template); + } + + if (!empty($params['semester_id'])) { + $semester = Semester::find($params['semester_id']); + if ($semester) { + // apply filter + $this->applyTimeFilter($semester->beginn, $semester->ende); + } + } + + $template->dates = $this->getUndecoratedData(isset($params['semester_id'])); + $template->seminar_id = $this->getId(); + + $template->set_attributes($params); + return trim($template->render()); + } + + /** + * returns an asscociative array with the attributes of the seminar depending + * on the field-names in the database + * @return array + */ + public function getData() + { + $data = $this->course->toArray(); + foreach($this->alias as $a => $o) { + $data[$a] = $this->course->$o; + } + return $data; + } + + /** + * returns an array with all IDs of Institutes this seminar is related to + * @param sem_id string: optional ID of a seminar, when null, this ID will be used + * @return: array of IDs (not associative) + */ + public function getInstitutes($sem_id = null) + { + if (!$sem_id && $this) { + $sem_id = $this->id; + } + + $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = :sem_id + UNION + SELECT Institut_id FROM seminare WHERE Seminar_id = :sem_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute(compact('sem_id')); + return $statement->fetchAll(PDO::FETCH_COLUMN); + } + + /** + * set the entries for seminar_inst table in database + * seminare.institut_id will always be added + * @param institutes array: array of Institut_id's + * @return bool: if something changed + */ + public function setInstitutes($institutes = []) + { + if (is_array($institutes)) { + $institutes[] = $this->institut_id; + $institutes = array_unique($institutes); + + $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->id]); + $old_inst = $statement->fetchAll(PDO::FETCH_COLUMN); + + $todelete = array_diff($old_inst, $institutes); + + $query = "DELETE FROM seminar_inst WHERE seminar_id = ? AND institut_id = ?"; + $statement = DBManager::get()->prepare($query); + + foreach($todelete as $inst) { + $tmp_instname= get_object_name($inst, 'inst'); + StudipLog::log('CHANGE_INSTITUTE_DATA', $this->id, $inst, 'Die beteiligte Einrichtung "'. $tmp_instname['name'] .'" wurde gelöscht.'); + $statement->execute([$this->id, $inst]); + NotificationCenter::postNotification('SeminarInstitutionDidDelete', $inst, $this->id); + + } + + $toinsert = array_diff($institutes, $old_inst); + + $query = "INSERT INTO seminar_inst (seminar_id, institut_id) VALUES (?, ?)"; + $statement = DBManager::get()->prepare($query); + + foreach($toinsert as $inst) { + $tmp_instname= get_object_name($inst, 'inst'); + StudipLog::log('CHANGE_INSTITUTE_DATA', $this->id, $inst, 'Die beteiligte Einrichtung "'. $tmp_instname['name'] .'" wurde hinzugefügt.'); + $statement->execute([$this->id, $inst]); + NotificationCenter::postNotification('SeminarInstitutionDidCreate', $inst, $this->id); + } + if ($todelete || $toinsert) { + NotificationCenter::postNotification("CourseDidChangeInstitutes", $this); + } + return $todelete || $toinsert; + } else { + $this->createError(_("Ungültige Eingabe der Institute. Es muss " . + "mindestens ein Institut angegeben werden.")); + return false; + } + } + + /** + * adds a user to the seminar with the given status + * @param user_id string: ID of the user + * @param status string: status of the user for the seminar "user", "autor", "tutor", "dozent" + * @param force bool: if false (default) the user will only be upgraded and not degraded in his/her status + */ + public function addMember($user_id, $status = 'autor', $force = false) + { + + if (in_array($GLOBALS['perm']->get_perm($user_id), ["admin", "root"])) { + $this->createError(_("Admin und Root dürfen nicht Mitglied einer Veranstaltung sein.")); + return false; + } + $db = DBManager::get(); + + $rangordnung = array_flip(['user', 'autor', 'tutor', 'dozent']); + if ($rangordnung[$status] > $rangordnung['autor'] && SeminarCategories::getByTypeId($this->status)->only_inst_user) { + //überprüfe, ob im richtigen Institut: + $user_institute_stmt = $db->prepare( + "SELECT Institut_id " . + "FROM user_inst " . + "WHERE user_id = :user_id " . + ""); + $user_institute_stmt->execute(['user_id' => $user_id]); + $user_institute = $user_institute_stmt->fetchAll(PDO::FETCH_COLUMN, 0); + + if (!in_array($this->institut_id, $user_institute) && !count(array_intersect($user_institute, $this->getInstitutes()))) { + $this->createError(_("Einzutragender Nutzer stammt nicht einem beteiligten Institut an.")); + + return false; + } + } + $course_member = CourseMember::findOneBySQL('user_id = ? AND Seminar_id = ?', [$user_id, $this->id]); + $new_position = (int) DBManager::get()->fetchColumn( + "SELECT MAX(position) + 1 FROM seminar_user WHERE status = ? AND Seminar_id = ?", + [$status, $this->id] + ); + $numberOfTeachers = CourseMember::countBySql("Seminar_id = ? AND status = 'dozent'", [$this->id]); + + if (!$course_member && !$force) { + CourseMember::create([ + 'Seminar_id' => $this->id, + 'user_id' => $user_id, + 'status' => $status, + 'position' => $new_position?:0, + 'gruppe' => (int) select_group($this->getSemesterStartTime()), + 'visible' => in_array($status, ['tutor', 'dozent']) ? 'yes' : 'unknown', + ]); + // delete the entries, user is now in the seminar + if (AdmissionApplication::deleteBySQL('user_id = ? AND seminar_id = ?', [$user_id, $this->getId()])) { + //renumber the waiting/accepted/lot list, a user was deleted from it + AdmissionApplication::renumberAdmission($this->getId()); + } + $cs = $this->getCourseSet(); + if ($cs) { + AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId()); + } + + CalendarScheduleModel::deleteSeminarEntries($user_id, $this->getId()); + NotificationCenter::postNotification('CourseDidGetMember', $this, $user_id); + NotificationCenter::postNotification('UserDidEnterCourse', $this->id, $user_id); + StudipLog::log('SEM_USER_ADD', $this->id, $user_id, $status, 'Wurde in die Veranstaltung eingetragen'); + $this->course->resetRelation('members'); + $this->course->resetRelation('admission_applicants'); + + // Check if we need to add user to parent course as well. + if ($this->parent_course) { + $parent = new Seminar($this->parent); + $parent->addMember($user_id, $status, $force); + } + + return $this; + } elseif ( + ($force || $rangordnung[$course_member->status] < $rangordnung[$status]) + && ($course_member->status !== 'dozent' || $numberOfTeachers > 1) + ) { + $visibility = $course_member->visible; + if (in_array($status, ['tutor', 'dozent'])) { + $visibility = 'yes'; + } + $course_member->status = $status; + $course_member->visible = $visibility; + $course_member->position = $new_position; + $course_member->store(); + + if ($course_member->status === 'dozent') { + $termine = DBManager::get()->fetchFirst( + "SELECT termin_id FROM termine WHERE range_id = ?", + [$this->id] + ); + + DBManager::get()->execute( + "DELETE FROM termin_related_persons WHERE range_id IN (?) AND user_id = ?", + [$termine, $user_id] + ); + } + NotificationCenter::postNotification('CourseDidChangeMember', $this, $user_id); + $this->course->resetRelation('members'); + $this->course->resetRelation('admission_applicants'); + return $this; + } else { + if ($course_member->status === 'dozent' && $numberOfTeachers <= 1) { + $this->createError(sprintf(_('Die Person kann nicht herabgestuft werden, ' . +'da mindestens ein/eine Veranstaltungsleiter/-in (%s) in die Veranstaltung eingetragen sein muss!'), + get_title_for_status('dozent', 1, $this->status)) . + ' ' . sprintf(_('Tragen Sie zunächst eine weitere Person als Veranstaltungsleiter/-in (%s) ein.'), +get_title_for_status('dozent', 1, $this->status))); + } + + return false; + } + } + + /** + * Cancels a subscription to an admission. + * + * @param array $users + * @param string $status + * @return array + * @throws NotificationVetoException + */ + public function cancelAdmissionSubscription(array $users, string $status): array + { + $msgs = []; + $messaging = new messaging; + $course_set = $this->getCourseSet(); + $users = User::findMany($users); + foreach ($users as $user) { + $prio_delete = false; + if ($course_set) { + $prio_delete = AdmissionPriority::unsetPriority($course_set->getId(), $user->id, $this->getId()); + } + $result = AdmissionApplication::deleteBySQL( + 'seminar_id = ? AND user_id = ? AND status = ?', + [$this->getId(), $user->id, $status] + ); + if ($result || $prio_delete) { + setTempLanguage($user->id); + if ($status !== 'accepted') { + $message = sprintf( + _('Sie wurden von der Warteliste der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'), + $this->getFullName() + ); + } else { + $message = sprintf( + _('Sie wurden aus der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'), + $this->getFullName() + ); + } + restoreLanguage(); + $messaging->insert_message( + $message, + $user->username, + '____%system%____', + false, + false, + '1', + false, + sprintf('%s %s', _('Systemnachricht:'), _('nicht zugelassen in Veranstaltung')), + true + ); + StudipLog::log('SEM_USER_DEL', $this->getId(), $user->id, 'Wurde aus der Veranstaltung entfernt'); + NotificationCenter::postNotification('UserDidLeaveCourse', $this->getId(), $user->id); + + $msgs[] = $user->getFullName(); + } + } + return $msgs; + } + + /** + * Cancels a subscription to a course + * @param array $users + * @return array + * @throws Exception + */ + public function cancelSubscription(array $users): array + { + $msgs = []; + $messaging = new messaging; + $users = User::findMany($users); + foreach ($users as $user) { + // delete member from seminar + if ($this->deleteMember($user->id)) { + setTempLanguage($user->id); + $message = sprintf( + _('Ihre Anmeldung zur Veranstaltung **%s** wurde aufgehoben.'), + $this->getFullName() + ); + restoreLanguage(); + $messaging->insert_message( + $message, + $user->username, + '____%system%____', + false, + false, + '1', + false, + sprintf('%s %s', _('Systemnachricht:'), _("Anmeldung aufgehoben")), + true + ); + $msgs[] = $user->getFullName(); + } + } + + return $msgs; + } + + /** + * deletes a user from the seminar by respecting the rule that at least one + * user with status "dozent" must stay there + * @param string $user_id user_id of the user to delete + * @return boolean + */ + public function deleteMember($user_id): bool + { + $dozenten = $this->getMembers(); + if (count($dozenten) >= 2 || empty($dozenten[$user_id])) { + $result = CourseMember::deleteBySQL('Seminar_id = ? AND user_id = ?', [$this->id, $user_id]); + if ($result === 0) { + return true; + } + // If this course is a child of another course... + if ($this->parent_course) { + // ... check if user is member in another sibling ... + $other = CourseMember::countBySQL( + "`user_id` = :user AND `Seminar_id` IN (:courses) AND `Seminar_id` != :this", + ['user' => $user_id, 'courses' => $this->parent->children->pluck('seminar_id'), 'this' => $this->id] + ); + + // ... and delete from parent course if this was the only + // course membership in this family. + if ($other === 0) { + $s = new Seminar($this->parent); + $s->deleteMember($user_id); + } + } + + if ($this->children != null) { + foreach ($this->children as $child) { + $s = new Seminar($child); + $s->deleteMember($user_id); + } + } + + if (!empty($dozenten[$user_id])) { + $query = "SELECT termin_id FROM termine WHERE range_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->id]); + $termine = $statement->fetchAll(PDO::FETCH_COLUMN); + + $query = "DELETE FROM termin_related_persons WHERE range_id = ? AND user_id = ?"; + $statement = DBManager::get()->prepare($query); + + foreach ($termine as $termin_id) { + $statement->execute([$termin_id, $user_id]); + } + if (Deputy::isActivated()) { + $other_dozenten = array_diff(array_keys($dozenten), [$user_id]); + foreach (Deputy::findByRange_id($user_id) as $default_deputy) { + if ($default_deputy->user_id != $GLOBALS['user']->id && + !Deputy::countBySql("range_id IN (?)", [$other_dozenten])) { + Deputy::deleteBySQL("range_id = ? AND user_id = ?", [$this->id, $default_deputy->user_id]); + } + } + } + } + + // Delete course related datafield entries + DatafieldEntryModel::deleteBySQL('range_id = ? AND sec_range_id = ?', [$user_id, $this->id]); + + // Remove from associated status groups + foreach (Statusgruppen::findBySeminar_id($this->id) as $group) { + $group->removeUser($user_id, true); + } + + $this->createMessage(sprintf( + _('Nutzer %s wurde aus der Veranstaltung entfernt.'), + '' . htmlReady(get_fullname($user_id)) . '' + )); + NotificationCenter::postNotification('CourseDidChangeMember', $this, $user_id); + NotificationCenter::postNotification('UserDidLeaveCourse', $this->id, $user_id); + StudipLog::log('SEM_USER_DEL', $this->id, $user_id, 'Wurde aus der Veranstaltung entfernt'); + $this->course->resetRelation('members'); + return true; + } else { + $this->createError( + sprintf( + _('Die Veranstaltung muss wenigstens einen/eine VeranstaltungsleiterIn (%s) eingetragen haben!'), + get_title_for_status('dozent', 1, $this->status) + ) + . ' ' . _('Tragen Sie zunächst einen anderen ein, um diesen zu löschen.') + ); + return false; + } + } + + /** + * sets the almost never used column position in the table seminar_user + * @param array $members members array: array of user_id's - wrong IDs will be ignored + * @return Seminar + */ + public function setMemberPriority($members): Seminar + { + CourseMember::findEachBySQL( + function (CourseMember $membership) use (&$members) { + $membership->position = array_search($membership->user_id, $members); + $membership->store(); + }, + "Seminar_id = ? AND user_id IN (?)", + [$this->id, $members] + ); + return $this; + } + + /** + * returns array with information about enrolment to this course for given user_id + * ['enrolment_allowed'] : true or false + * ['cause']: keyword to describe the cause + * ['description'] : readable description of the cause + * + * @param string $user_id + * @return array + */ + public function getEnrolmentInfo($user_id) + { + $info = []; + $user = User::find($user_id); + if ($this->getSemClass()->isGroup()) { + $info['enrolment_allowed'] = false; + $info['cause'] = 'grouped'; + $info['description'] = _("Dies ist eine Veranstaltungsgruppe. Sie können sich nur in deren Unterveranstaltungen eintragen."); + return $info; + } + if ($this->read_level == 0 && Config::get()->ENABLE_FREE_ACCESS && !$GLOBALS['perm']->get_studip_perm($this->getId(), $user_id)) { + $info['enrolment_allowed'] = true; + $info['cause'] = 'free_access'; + $info['description'] = _("Für die Veranstaltung ist keine Anmeldung erforderlich."); + return $info; + } + if (!$user) { + $info['enrolment_allowed'] = false; + $info['cause'] = 'nobody'; + $info['description'] = _("Sie sind nicht in Stud.IP angemeldet."); + return $info; + } + if ($GLOBALS['perm']->have_perm('root', $user_id)) { + $info['enrolment_allowed'] = true; + $info['cause'] = 'root'; + $info['description'] = _("Sie dürfen ALLES."); + return $info; + } + if ($GLOBALS['perm']->have_studip_perm('admin', $this->getId(), $user_id)) { + $info['enrolment_allowed'] = true; + $info['cause'] = 'courseadmin'; + $info['description'] = _("Sie sind Administrator_in der Veranstaltung."); + return $info; + } + if ($GLOBALS['perm']->have_perm('admin', $user_id)) { + $info['enrolment_allowed'] = false; + $info['cause'] = 'admin'; + $info['description'] = _("Als Administrator_in können Sie sich nicht für eine Veranstaltung anmelden."); + return $info; + } + //Ist bereits Teilnehmer + if ($GLOBALS['perm']->have_studip_perm('user', $this->getId(), $user_id)) { + $info['enrolment_allowed'] = true; + $info['cause'] = 'member'; + $info['description'] = _("Sie sind für die Veranstaltung angemeldet."); + return $info; + } + $admission_status = $user->admission_applications->findBy('seminar_id', $this->getId())->val('status'); + if ($admission_status == 'accepted') { + $info['enrolment_allowed'] = false; + $info['cause'] = 'accepted'; + $info['description'] = _("Sie wurden für diese Veranstaltung vorläufig akzeptiert."); + return $info; + } + if ($admission_status == 'awaiting') { + $info['enrolment_allowed'] = false; + $info['cause'] = 'awaiting'; + $info['description'] = _("Sie stehen auf der Warteliste für diese Veranstaltung."); + return $info; + } + if ($GLOBALS['perm']->get_perm($user_id) == 'user') { + $info['enrolment_allowed'] = false; + $info['cause'] = 'user'; + $info['description'] = _("Sie haben nicht die erforderliche Berechtigung sich für eine Veranstaltung anzumelden."); + return $info; + } + //falsche Nutzerdomäne + $same_domain = true; + $user_domains = UserDomain::getUserDomainsForUser($user_id); + if (count($user_domains) > 0) { + $seminar_domains = UserDomain::getUserDomainsForSeminar($this->getId()); + $same_domain = UserDomain::checkUserVisibility($seminar_domains, $user_domains);; + } + if (!$same_domain && !$this->isStudygroup()) { + $info['enrolment_allowed'] = false; + $info['cause'] = 'domain'; + $info['description'] = _("Sie sind nicht in einer zugelassenenen Nutzerdomäne, Sie können sich nicht eintragen!"); + return $info; + } + //Teilnehmerverwaltung mit Sperregel belegt + if (LockRules::Check($this->getId(), 'participants')) { + $info['enrolment_allowed'] = false; + $info['cause'] = 'locked'; + $lockdata = LockRules::getObjectRule($this->getId()); + $info['description'] = _("In diese Veranstaltung können Sie sich nicht eintragen!") . ($lockdata['description'] ? '
' . formatLinks($lockdata['description']) : ''); + return $info; + } + //Veranstaltung unsichtbar für aktuellen Nutzer + if (!$this->visible && !$this->isStudygroup() && !$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM, $user_id)) { + $info['enrolment_allowed'] = false; + $info['cause'] = 'invisible'; + $info['description'] = _("Die Veranstaltung ist gesperrt, Sie können sich nicht eintragen!"); + return $info; + } + if ($courseset = $this->getCourseSet()) { + $info['enrolment_allowed'] = true; + $info['cause'] = 'courseset'; + $info['description'] = _("Die Anmeldung zu dieser Veranstaltung folgt speziellen Regeln. Lesen Sie den Hinweistext."); + $user_prio = AdmissionPriority::getPrioritiesByUser($courseset->getId(), $user_id); + if (isset($user_prio[$this->getId()])) { + if ($courseset->hasAdmissionRule('LimitedAdmission')) { + $info['description'] .= ' ' . sprintf(_("(Sie stehen auf der Anmeldeliste für die automatische Platzverteilung mit der Priorität %s.)"), $user_prio[$this->getId()]); + } else { + $info['description'] .= ' ' . _("(Sie stehen auf der Anmeldeliste für die automatische Platzverteilung.)"); + } + } + return $info; + } + $info['enrolment_allowed'] = true; + $info['cause'] = 'normal'; + $info['description'] = ''; + return $info; + } + + /** + * adds user with given id as preliminary member to course + * + * @param string $user_id + * @return integer 1 if successfull + */ + public function addPreliminaryMember($user_id, $comment = '') + { + $new_admission_member = new AdmissionApplication(); + $new_admission_member->user_id = $user_id; + $new_admission_member->position = 0; + $new_admission_member->status = 'accepted'; + $new_admission_member->comment = $comment; + $this->course->admission_applicants[] = $new_admission_member; + $ok = $new_admission_member->store(); + if ($ok && $this->isStudygroup()) { + StudygroupModel::applicationNotice($this->getId(), $user_id); + } + $cs = $this->getCourseSet(); + if ($cs) { + $prio_delete = AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId()); + } + // LOGGING + StudipLog::log('SEM_USER_ADD', $this->getId(), $user_id, 'accepted', 'Vorläufig akzeptiert'); + return $ok; + } + + /** + * returns courseset object for this course + * + * @return CourseSet courseset object or null + */ + public function getCourseSet() + { + if ($this->course_set === null) { + $this->course_set = CourseSet::getSetForCourse($this->id); + if ($this->course_set === null) { + $this->course_set = false; + } + } + return $this->course_set ?: null; + } + + /** + * returns true if the number of participants of this course is limited + * + * @return boolean + */ + public function isAdmissionEnabled() + { + $cs = $this->getCourseSet(); + return ($cs && $cs->isSeatDistributionEnabled()); + } + + /** + * returns the number of free seats in the course or true if not limited + * + * @return integer + */ + public function getFreeAdmissionSeats() + { + if ($this->isAdmissionEnabled()) { + return $this->course->getFreeSeats(); + } else { + return true; + } + } + + /** + * returns true if the course is locked + * + * @return boolean + */ + public function isAdmissionLocked() + { + $cs = $this->getCourseSet(); + return ($cs && $cs->hasAdmissionRule('LockedAdmission')); + } + + /** + * returns true if the course is password protected + * + * @return boolean + */ + public function isPasswordProtected() + { + $cs = $this->getCourseSet(); + return ($cs && $cs->hasAdmissionRule('PasswordAdmission')); + } + + /** + * returns array with start and endtime of course enrolment timeframe + * return null if there is no timeframe + * + * @return array assoc array with start_time end_time as keys timestamps as values + */ + public function getAdmissionTimeFrame() + { + $cs = $this->getCourseSet(); + return ($cs && $cs->hasAdmissionRule('TimedAdmission')) ? + ['start_time' => $cs->getAdmissionRule('TimedAdmission')->getStartTime(), + 'end_time' => $cs->getAdmissionRule('TimedAdmission')->getEndTime()] : []; + } + + /** + * returns StudipModule object for given slot, null when deactivated or not available + * + * @param string $slot + * @return StudipModule|null + */ + public function getSlotModule($slot): ?StudipModule + { + $module = 'Core' . ucfirst($slot); + if ($this->course->isToolActive($module)) { + return PluginEngine::getPlugin($module); + } + return null; + } + + /** + * adds user with given id on waitinglist + * + * @param string $user_id + * @param string $which_end 'last' or 'first' + * @return integer|bool number on waitlist or false if not successful + */ + public function addToWaitlist($user_id, $which_end = 'last') + { + if (AdmissionApplication::exists([$user_id, $this->id]) || CourseMember::find([$this->id, $user_id])) { + return false; + } + switch ($which_end) { + // Append users to waitlist end. + case 'last': + $maxpos = DBManager::get()->fetchColumn("SELECT MAX(`position`) + FROM `admission_seminar_user` + WHERE `seminar_id`=? + AND `status`='awaiting'", [$this->id]); + $waitpos = $maxpos+1; + break; + // Prepend users to waitlist start. + case 'first': + default: + // Move all others on the waitlist up by the number of people to add. + AdmissionApplication::renumberAdmission($this->id); + $waitpos = 1; + } + $new_admission_member = new AdmissionApplication(); + $new_admission_member->user_id = $user_id; + $new_admission_member->position = $waitpos; + $new_admission_member->status = 'awaiting'; + $new_admission_member->seminar_id = $this->id; + if ($new_admission_member->store()) { + StudipLog::log('SEM_USER_ADD', $this->id, $user_id, 'awaiting', 'Auf Warteliste gesetzt, Position: ' . $waitpos); + $this->course->resetRelation('admission_applicants'); + return $waitpos; + } + return false; + } +} diff --git a/lib/classes/SeminarCategories.class.php b/lib/classes/SeminarCategories.class.php deleted file mode 100644 index 5302f1e..0000000 --- a/lib/classes/SeminarCategories.class.php +++ /dev/null @@ -1,138 +0,0 @@ -, Suchi & Berg GmbH - * @access public - * @package core - */ -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// SeminarCategories.class.php -// -// Copyright (C) 2008 André Noack , data-quest GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -class SeminarCategories { - - private static $seminar_categories = []; - - private $sem_class_data = []; - private $sem_type_data = []; - - /** - * Enter description here... - * - * @param String $id - * @return Array - */ - public static function Get($id) - { - if (empty(self::$seminar_categories[$id])) { - $cat = new SeminarCategories($id); - if ($cat->id !== false) { - self::$seminar_categories[$id] = $cat; - } else { - self::$seminar_categories[$id] = false; - } - } - return self::$seminar_categories[$id]; - } - - /** - * Enter description here... - * - * @return Array - */ - public static function GetAll(){ - $ret = []; - foreach($GLOBALS['SEM_CLASS'] as $id => $sem_class){ - $ret[] = self::get($id); - } - return $ret; - } - - /** - * Enter description here... - * - * @param String $id - * @return String - */ - public static function GetByTypeId($id){ - return self::Get($GLOBALS['SEM_TYPE'][$id]['class']); - } - - /** - * Enter description here... - * - * @param String $seminar_id - * @return SeminarCategories - */ - public static function GetBySeminarId($seminar_id){ - return self::GetByTypeId(Seminar::GetInstance($seminar_id)->status); - } - - /** - * Enter description here... - * - * @param String $sem_class_id - */ - private function __construct($sem_class_id) { - if(isset($GLOBALS['SEM_CLASS'][$sem_class_id])){ - $this->sem_class_data = $GLOBALS['SEM_CLASS'][$sem_class_id]; - $this->sem_class_data['id'] = $sem_class_id; - foreach($GLOBALS['SEM_TYPE'] as $type_id => $type_data){ - if($type_data['class'] == $sem_class_id) { - $this->sem_type_data[$type_id] = $type_data['name']; - } - } - } - } - - /** - * Enter description here... - * - * @param String $type_id - * @return String - */ - public function getNameOfType($type_id){ - return isset($this->sem_type_data[$type_id]) ? $this->sem_type_data[$type_id] : ''; - } - - /** - * Enter description here... - * - * @return Array - */ - public function getTypes(){ - return $this->sem_type_data; - } - - public function __get($attribute){ - if(isset($this->sem_class_data[$attribute])){ - return $this->sem_class_data[$attribute]; - } else { - return false; - } - } -} -?> diff --git a/lib/classes/SeminarCategories.php b/lib/classes/SeminarCategories.php new file mode 100644 index 0000000..5302f1e --- /dev/null +++ b/lib/classes/SeminarCategories.php @@ -0,0 +1,138 @@ +, Suchi & Berg GmbH + * @access public + * @package core + */ +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// SeminarCategories.class.php +// +// Copyright (C) 2008 André Noack , data-quest GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +class SeminarCategories { + + private static $seminar_categories = []; + + private $sem_class_data = []; + private $sem_type_data = []; + + /** + * Enter description here... + * + * @param String $id + * @return Array + */ + public static function Get($id) + { + if (empty(self::$seminar_categories[$id])) { + $cat = new SeminarCategories($id); + if ($cat->id !== false) { + self::$seminar_categories[$id] = $cat; + } else { + self::$seminar_categories[$id] = false; + } + } + return self::$seminar_categories[$id]; + } + + /** + * Enter description here... + * + * @return Array + */ + public static function GetAll(){ + $ret = []; + foreach($GLOBALS['SEM_CLASS'] as $id => $sem_class){ + $ret[] = self::get($id); + } + return $ret; + } + + /** + * Enter description here... + * + * @param String $id + * @return String + */ + public static function GetByTypeId($id){ + return self::Get($GLOBALS['SEM_TYPE'][$id]['class']); + } + + /** + * Enter description here... + * + * @param String $seminar_id + * @return SeminarCategories + */ + public static function GetBySeminarId($seminar_id){ + return self::GetByTypeId(Seminar::GetInstance($seminar_id)->status); + } + + /** + * Enter description here... + * + * @param String $sem_class_id + */ + private function __construct($sem_class_id) { + if(isset($GLOBALS['SEM_CLASS'][$sem_class_id])){ + $this->sem_class_data = $GLOBALS['SEM_CLASS'][$sem_class_id]; + $this->sem_class_data['id'] = $sem_class_id; + foreach($GLOBALS['SEM_TYPE'] as $type_id => $type_data){ + if($type_data['class'] == $sem_class_id) { + $this->sem_type_data[$type_id] = $type_data['name']; + } + } + } + } + + /** + * Enter description here... + * + * @param String $type_id + * @return String + */ + public function getNameOfType($type_id){ + return isset($this->sem_type_data[$type_id]) ? $this->sem_type_data[$type_id] : ''; + } + + /** + * Enter description here... + * + * @return Array + */ + public function getTypes(){ + return $this->sem_type_data; + } + + public function __get($attribute){ + if(isset($this->sem_class_data[$attribute])){ + return $this->sem_class_data[$attribute]; + } else { + return false; + } + } +} +?> diff --git a/lib/classes/SessionDecoder.class.php b/lib/classes/SessionDecoder.class.php deleted file mode 100644 index d437494..0000000 --- a/lib/classes/SessionDecoder.class.php +++ /dev/null @@ -1,246 +0,0 @@ -, Suchi & Berg GmbH - * @access public - * @package core - */ -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// SessionDecoder.class.php -// -// Copyright (C) 2008 André Noack , data-quest GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -class SessionDecoder implements ArrayAccess, Countable, Iterator { - - private $encoded_session = []; - private $decoded_session = []; - private $var_names = []; - - /** - * Usage: - * Pass the string containing encoded session data to the - * constructor, the identified session variables become public members - * of the object - * - * $session = new SessionDecoder($encoded_session_string); - * print_r($session->my_var); - * or - * print_r($session['my_var']); - * get the names of identified variables - * print_r($session->keys()); - * - * @param string $encoded_session_string - */ - public function __construct($encoded_session_string) { - $this->decode($encoded_session_string); - } - - /** - * pass an encoded session string to fill the object - * - * @param string $encoded_session_string - * @return int number of identified variables - */ - public function decode($encoded_session_string){ - $this->encoded_session = $this->sessionRealDecode($encoded_session_string); - if(is_array($this->encoded_session)) { - $this->var_names = array_keys($this->encoded_session); - } else { - $this->encoded_session = []; - } - $this->decoded_session = []; - - return count($this->encoded_session); - } - - /** - * returns an array containing the names of the identified variables - * - * @return array names of identified variables - */ - public function keys() { - return $this->var_names; - } - - public function rewind(): void - { - reset($this->var_names); - } - - public function current(): mixed - { - $current = current($this->var_names); - return $current !== false ? $this->offsetGet($current) : false; - } - - public function key(): mixed - { - return current($this->var_names); - } - - public function next(): void - { - next($this->var_names); - } - - public function valid(): bool - { - $current = current($this->var_names); - return $current !== false; - } - - public function offsetExists($offset): bool - { - return isset($this->encoded_session[$offset]); - } - - /** - * @param string $offset - */ - public function offsetGet($offset): mixed - { - if($this->offsetExists($offset) && !isset($this->decoded_session[$offset])) { - $this->decoded_session[$offset] = unserialize($this->encoded_session[$offset]); - } - return $this->decoded_session[$offset] ?? null; - } - - public function offsetSet($offset, $value): void - { - } - - public function offsetUnset($offset): void - { - } - - public function count(): int - { - return count($this->var_names); - } - - public function __get($name){ - return $this->offsetGet($name); - } - - public function __isset($name){ - return $this->offsetExists($name); - } - - /** - * a function that returns decoded session data, - * that seems to work in every cases, - * even when strings contain reserved chars - * (c) bmorel at ssi dot fr - * http://www.php.net/manual/en/function.session-decode.php#56106 - * - * @param string $str - * @return array - */ - private function sessionRealDecode($str) { - $ret = []; - $PS_DELIMITER = '|'; - $PS_UNDEF_MARKER = '!'; - $str = (string)$str; - - $endptr = strlen($str); - $p = 0; - - $items = 0; - $level = 0; - - while ($p < $endptr) { - $q = $p; - while ($str[$q] != $PS_DELIMITER) - if (++$q >= $endptr) break 2; - - if ($str[$p] == $PS_UNDEF_MARKER) { - $p++; - $has_value = false; - } else { - $has_value = true; - } - - $name = substr($str, $p, $q - $p); - $q++; - - $serialized = ''; - if ($has_value) { - for (;;) { - $p = $q; - switch ($str[$q]) { - case 'N': /* null */ - case 'b': /* boolean */ - case 'i': /* integer */ - case 'd': /* decimal */ - do $q++; - while ( ($q < $endptr) && ($str[$q] != ';') ); - $q++; - $serialized .= substr($str, $p, $q - $p); - if ($level == 0) break 2; - break; - case 'R': /* reference */ - $q+= 2; - for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q]; - $q++; - //$serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */ - $serialized .= 'N;'; /* unserializing references is not possible*/ - if ($level == 0) break 2; - break; - case 's': /* string */ - $q+=2; - for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q]; - $q+=2; - $q+= (int)$length + 2; - $serialized .= substr($str, $p, $q - $p); - if ($level == 0) break 2; - break; - case 'a': /* array */ - case 'O': /* object */ - do $q++; - while ( ($q < $endptr) && ($str[$q] != '{') ); - $q++; - $level++; - $serialized .= substr($str, $p, $q - $p); - break; - case '}': /* end of array|object */ - $q++; - $serialized .= substr($str, $p, $q - $p); - if (--$level == 0) break 2; - break; - default: - return false; - } - } - } else { - $serialized .= 'N;'; - $q+= 2; - } - $items++; - $p = $q; - $ret[$name] = $serialized; - } - return $ret; - } -} -?> diff --git a/lib/classes/SessionDecoder.php b/lib/classes/SessionDecoder.php new file mode 100644 index 0000000..d437494 --- /dev/null +++ b/lib/classes/SessionDecoder.php @@ -0,0 +1,246 @@ +, Suchi & Berg GmbH + * @access public + * @package core + */ +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// SessionDecoder.class.php +// +// Copyright (C) 2008 André Noack , data-quest GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +class SessionDecoder implements ArrayAccess, Countable, Iterator { + + private $encoded_session = []; + private $decoded_session = []; + private $var_names = []; + + /** + * Usage: + * Pass the string containing encoded session data to the + * constructor, the identified session variables become public members + * of the object + * + * $session = new SessionDecoder($encoded_session_string); + * print_r($session->my_var); + * or + * print_r($session['my_var']); + * get the names of identified variables + * print_r($session->keys()); + * + * @param string $encoded_session_string + */ + public function __construct($encoded_session_string) { + $this->decode($encoded_session_string); + } + + /** + * pass an encoded session string to fill the object + * + * @param string $encoded_session_string + * @return int number of identified variables + */ + public function decode($encoded_session_string){ + $this->encoded_session = $this->sessionRealDecode($encoded_session_string); + if(is_array($this->encoded_session)) { + $this->var_names = array_keys($this->encoded_session); + } else { + $this->encoded_session = []; + } + $this->decoded_session = []; + + return count($this->encoded_session); + } + + /** + * returns an array containing the names of the identified variables + * + * @return array names of identified variables + */ + public function keys() { + return $this->var_names; + } + + public function rewind(): void + { + reset($this->var_names); + } + + public function current(): mixed + { + $current = current($this->var_names); + return $current !== false ? $this->offsetGet($current) : false; + } + + public function key(): mixed + { + return current($this->var_names); + } + + public function next(): void + { + next($this->var_names); + } + + public function valid(): bool + { + $current = current($this->var_names); + return $current !== false; + } + + public function offsetExists($offset): bool + { + return isset($this->encoded_session[$offset]); + } + + /** + * @param string $offset + */ + public function offsetGet($offset): mixed + { + if($this->offsetExists($offset) && !isset($this->decoded_session[$offset])) { + $this->decoded_session[$offset] = unserialize($this->encoded_session[$offset]); + } + return $this->decoded_session[$offset] ?? null; + } + + public function offsetSet($offset, $value): void + { + } + + public function offsetUnset($offset): void + { + } + + public function count(): int + { + return count($this->var_names); + } + + public function __get($name){ + return $this->offsetGet($name); + } + + public function __isset($name){ + return $this->offsetExists($name); + } + + /** + * a function that returns decoded session data, + * that seems to work in every cases, + * even when strings contain reserved chars + * (c) bmorel at ssi dot fr + * http://www.php.net/manual/en/function.session-decode.php#56106 + * + * @param string $str + * @return array + */ + private function sessionRealDecode($str) { + $ret = []; + $PS_DELIMITER = '|'; + $PS_UNDEF_MARKER = '!'; + $str = (string)$str; + + $endptr = strlen($str); + $p = 0; + + $items = 0; + $level = 0; + + while ($p < $endptr) { + $q = $p; + while ($str[$q] != $PS_DELIMITER) + if (++$q >= $endptr) break 2; + + if ($str[$p] == $PS_UNDEF_MARKER) { + $p++; + $has_value = false; + } else { + $has_value = true; + } + + $name = substr($str, $p, $q - $p); + $q++; + + $serialized = ''; + if ($has_value) { + for (;;) { + $p = $q; + switch ($str[$q]) { + case 'N': /* null */ + case 'b': /* boolean */ + case 'i': /* integer */ + case 'd': /* decimal */ + do $q++; + while ( ($q < $endptr) && ($str[$q] != ';') ); + $q++; + $serialized .= substr($str, $p, $q - $p); + if ($level == 0) break 2; + break; + case 'R': /* reference */ + $q+= 2; + for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q]; + $q++; + //$serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */ + $serialized .= 'N;'; /* unserializing references is not possible*/ + if ($level == 0) break 2; + break; + case 's': /* string */ + $q+=2; + for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q]; + $q+=2; + $q+= (int)$length + 2; + $serialized .= substr($str, $p, $q - $p); + if ($level == 0) break 2; + break; + case 'a': /* array */ + case 'O': /* object */ + do $q++; + while ( ($q < $endptr) && ($str[$q] != '{') ); + $q++; + $level++; + $serialized .= substr($str, $p, $q - $p); + break; + case '}': /* end of array|object */ + $q++; + $serialized .= substr($str, $p, $q - $p); + if (--$level == 0) break 2; + break; + default: + return false; + } + } + } else { + $serialized .= 'N;'; + $q+= 2; + } + $items++; + $p = $q; + $ret[$name] = $serialized; + } + return $ret; + } +} +?> diff --git a/lib/classes/SimpleCollection.class.php b/lib/classes/SimpleCollection.class.php deleted file mode 100644 index 6acc10d..0000000 --- a/lib/classes/SimpleCollection.class.php +++ /dev/null @@ -1,782 +0,0 @@ - - * @copyright 2013 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @template T - */ -class SimpleCollection extends StudipArrayObject -{ - /** - * callable to initialize collection - * - * @var ?callable(): array - */ - protected $finder; - - /** - * number of records after last init - * - * @var int - */ - protected $last_count; - - /** - * collection with deleted records - * @var static - */ - protected $deleted; - - /** - * creates a collection from an array of arrays - * all arrays should contain same keys, but is not enforced - * - * @param array $data array containing assoc arrays - * @return SimpleCollection - */ - public static function createFromArray(array $data) - { - return new self($data); - } - - /** - * converts arrays or objects to ArrayObject objects - * if ArrayAccess interface is not available - * - * @param mixed $a - * @return StudipArrayObject|ArrayAccess - */ - public static function arrayToArrayObject($a) - { - if ($a instanceof StudipArrayObject) { - $a->setFlags(StudipArrayObject::ARRAY_AS_PROPS); - return $a; - } - - if ($a instanceof ArrayObject) { - return new StudipArrayObject($a->getArrayCopy(), StudipArrayObject::ARRAY_AS_PROPS); - } - - if ($a instanceof ArrayAccess) { - return $a; - } - - return new StudipArrayObject((array) $a, StudipArrayObject::ARRAY_AS_PROPS); - } - - /** - * returns closure to compare a value against given arguments - * using given operator - * - * @param string|callable(mixed, mixed|array): bool $operator - * @param mixed|array $args - * @throws InvalidArgumentException - * @return callable(mixed): bool comparison function - */ - public static function getCompFunc($operator, $args) - { - if (is_callable($operator)) { - $comp_func = function ($a) use ($args, $operator) { - return $operator($a, $args); - }; - } else { - if (!is_array($args)) { - $args = [$args]; - } - switch ($operator) { - case '==': - $comp_func = function ($a) use ($args) { - return in_array($a, $args); - }; - break; - case '===': - $comp_func = function ($a) use ($args) { - return in_array($a, $args, true); - }; - break; - case '!=': - case '<>': - $comp_func = function ($a) use ($args) { - return !in_array($a, $args); - }; - break; - case '!==': - $comp_func = function ($a) use ($args) { - return !in_array($a, $args, true); - }; - break; - case '<': - case '>': - case '<=': - case '>=': - $op_func = function ($a, $b) use ($operator) { - if ($operator === '<') { - return $a < $b; - } elseif ($operator === '<=') { - return $a <= $b; - } elseif ($operator === '>=') { - return $a >= $b; - } elseif ($operator === '>') { - return $a > $b; - } - }; - $comp_func = function ($a) use ($op_func, $args) { - return $op_func($a, $args[0]); - }; - break; - case '><': - $comp_func = function ($a) use ($args) { - return $a > $args[0] && $a < $args[1]; - }; - break; - case '>=<=': - $comp_func = function ($a) use ($args) { - return $a >= $args[0] && $a <= $args[1]; - }; - break; - case '%=': - $comp_func = function ($a) use ($args) { - $a = mb_strtolower(static::translitLatin1($a)); - $args = array_map([static::class, 'translitLatin1'], $args); - $args = array_map('mb_strtolower', $args); - return in_array($a, $args); - }; - break; - case '*=': - $comp_func = function ($a) use ($args) { - foreach ($args as $arg) { - if (mb_strpos($a, $arg) !== false) { - return true; - } - } - return false; - }; - break; - case '^=': - $comp_func = function ($a) use ($args) { - foreach ($args as $arg) { - if (mb_strpos($a, $arg) === 0) { - return true; - } - } - return false; - }; - break; - case '$=': - $comp_func = function ($a) use ($args) { - foreach ($args as $arg) { - $found = mb_strrpos($a, $arg); - if ($found !== false && ($found + mb_strlen($arg)) === mb_strlen($a)) { - return true; - } - } - return false; - }; - break; - case '~=': - $comp_func = function ($a) use ($args) { - foreach ($args as $arg) { - if (preg_match($arg, $a) === 1) { - return true; - } - } - return false; - }; - break; - default: - throw new InvalidArgumentException('unknown operator: ' . $operator); - } - } - return $comp_func; - } - - /** - * transliterates latin1 string to ascii - * - * @param string $text - * @return string - */ - public static function translitLatin1($text) - { - if (!preg_match('/[\200-\377]/', $text)) { - return $text; - } - $text = str_replace(['ä','Ä','ö','Ö','ü','Ü','ß'], ['a','A','o','O','u','U','s'], $text); - $text = str_replace(['À','Á','Â','Ã','Å','Æ'], 'A' , $text); - $text = str_replace(['à','á','â','ã','å','æ'], 'a' , $text); - $text = str_replace(['È','É','Ê','Ë'], 'E' , $text); - $text = str_replace(['è','é','ê','ë'], 'e' , $text); - $text = str_replace(['Ì','Í','Î','Ï'], 'I' , $text); - $text = str_replace(['ì','í','î','ï'], 'i' , $text); - $text = str_replace(['Ò','Ó','Õ','Ô','Ø'], 'O' , $text); - $text = str_replace(['ò','ó','ô','õ','ø'], 'o' , $text); - $text = str_replace(['Ù','Ú','Û'], 'U' , $text); - $text = str_replace(['ù','ú','û'], 'u' , $text); - $text = str_replace(['Ç','ç','Ð','Ñ','Ý','ñ','ý','ÿ'], ['C','c','D','N','Y','n','y','y'] , $text); - return $text; - } - - /** - * Constructor - * - * @param array|callable(): array $data array or closure to fill collection - */ - public function __construct($data = []) - { - parent::__construct(); - $this->finder = is_callable($data) ? $data : null; - $this->deleted = clone $this; - if (is_callable($data)) { - $this->refresh(); - } else { - $this->exchangeArray($data); - } - } - - /** - * @param array $input - * @return array - */ - public function exchangeArray($input) - { - return parent::exchangeArray(array_map( - [static::class, 'arrayToArrayObject'], - $input - )); - } - - /** - * converts the object and all elements to plain arrays - * - * @return array - */ - public function toArray() - { - $args = func_get_args(); - return $this->map(function ($a) use ($args) { - if (method_exists($a, 'toArray')) { - return call_user_func_array([$a, 'toArray'], $args); - } - if (method_exists($a, 'getArrayCopy')) { - return $a->getArrayCopy(); - } - return (array) $a; - } - ); - } - - /** - * - * @see ArrayObject::append() - */ - public function append($newval) - { - parent::append(static::arrayToArrayObject($newval)); - } - - /** - * Sets the value at the specified index - * ensures the value has ArrayAccess - * - * @param mixed $index - * @param mixed $newval - * - * @see ArrayObject::offsetSet() - */ - - public function offsetSet($index, $newval): void - { - if (is_numeric($index)) { - $index = (int) $index; - } - parent::offsetSet($index, static::arrayToArrayObject($newval)); - } - - /** - * Unsets the value at the specified index - * value is moved to internal deleted collection - * - * @see ArrayObject::offsetUnset() - * @throws InvalidArgumentException - */ - public function offsetUnset($index): void - { - if ($this->offsetExists($index)) { - $this->deleted[] = $this->offsetGet($index); - } - parent::offsetUnset($index); - } - - /** - * sets the finder function - * - * @param callable(): array $finder - * @return void - */ - public function setFinder(callable $finder) - { - $this->finder = $finder; - } - - /** - * get deleted records collection - * @return SimpleCollection - */ - public function getDeleted() - { - return $this->deleted; - } - - /** - * reloads the elements of the collection - * by calling the finder function - * - * @return ?int of records after refresh - */ - public function refresh() - { - if (is_callable($this->finder)) { - $data = call_user_func($this->finder); - $this->exchangeArray($data); - $this->deleted->exchangeArray([]); - return $this->last_count = $this->count(); - } - } - - /** - * returns a new collection containing all elements - * where given columns value matches given value(s) using passed operator - * pass array for multiple values - * - * operators: - * == equal, like php - * === identical, like php - * !=,<> not equal, like php - * !== not identical, like php - * <,>,<=,>= less,greater,less or equal,greater or equal - * >< between without borders, needs two arguments - * >=<= between including borders, needs two arguments - * %= like string, transliterate to ascii,case insensitive - * *= contains string - * ^= begins with string - * $= ends with string - * ~= regex - * - * @param string $key the column name - * @param mixed $values value to search for - * @param string|callable $op operator to find - * @return SimpleCollection with found records - */ - public function findBy($key, $values, $op = '==') - { - $comp_func = self::getCompFunc($op, $values); - return $this->filter(function ($record) use ($comp_func, $key) { - return $comp_func($record[$key]); - }); - } - - /** - * returns the first element - * where given column has given value(s) - * pass array for multiple values - * - * @param string $key the column name - * @param mixed $values value to search for, - * @param string|callable $op operator to find - * @return ?T found record - */ - public function findOneBy($key, $values, $op = '==') - { - $comp_func = self::getCompFunc($op, $values); - return $this->filter(function ($record) use ($comp_func, $key) { - return $comp_func($record[$key]); - }, 1)->first(); - } - - /** - * apply given callback to all elements of - * collection - * - * @param callable(T): int $func the function to call - * @return int|false addition of return values - */ - public function each(callable $func) - { - $result = false; - foreach ($this->storage as $record) { - $result += call_user_func($func, $record); - } - return $result; - } - - /** - * apply given callback to all elements of - * collection and give back array of return values - * - * @param callable(T, mixed): mixed $func the function to call - * @return array - */ - public function map(callable $func) - { - $results = []; - foreach ($this->storage as $key => $value) { - $results[$key] = call_user_func($func, $value, $key); - } - return $results; - } - - /** - * filter elements - * if given callback returns true - * - * @param ?callable(T, mixed): bool $func the function to call - * @param ?integer $limit limit number of found records - * @return SimpleCollection containing filtered elements - */ - public function filter(callable $func = null, $limit = null) - { - $results = []; - $found = 0; - foreach ($this->storage as $key => $value) { - if (call_user_func($func, $value, $key)) { - $results[$key] = $value; - if ($limit && (++$found == $limit)) { - break; - } - } - } - return self::createFromArray($results); - } - - /** - * Returns whether any element of the collection returns true for the - * given callback. - * - * @param callable(T, mixed): bool $func the function to call - * @return bool - */ - public function any(callable $func) - { - foreach ($this->storage as $key => $value) { - if (call_user_func($func, $value, $key)) { - return true; - } - } - return false; - } - - /** - * Returns whether every element of the collection returns true for the - * given callback. - * - * @param callable(T, mixed): bool $func the function to call - * @return bool - */ - public function every(callable $func) - { - foreach ($this->storage as $key => $value) { - if (!call_user_func($func, $value, $key)) { - return false; - } - } - return true; - } - - /** - * extract array of columns values - * pass array or space-delimited string for multiple columns - * - * @param string|array $columns the column(s) to extract - * @return array of extracted values - */ - public function pluck($columns) - { - if (!is_array($columns)) { - $columns = words($columns); - } - $func = function ($r) use ($columns) { - $result = []; - foreach ($columns as $c) { - $result[] = $r[$c]; - } - return $result; - }; - $result = $this->map($func); - return count($columns) === 1 ? array_map('current', $result) : $result; - } - - /** - * returns the collection as grouped array - * first param is the column to group by, it becomes the key in - * the resulting array, default is pk. Limit returned fields with second param - * The grouped entries can optoionally go through the given - * callback. If no callback is provided, only the first grouped - * entry is returned, suitable for grouping by unique column - * - * @param string $group_by the column to group by, pk if ommitted - * @param string|array|null $only_these_fields limit returned fields - * @param ?callable $group_func closure to aggregate grouped entries - * @return array assoc array - */ - public function toGroupedArray($group_by = 'id', $only_these_fields = null, callable $group_func = null) - { - $result = []; - if (is_string($only_these_fields)) { - $only_these_fields = words($only_these_fields); - } - foreach ($this->toArray() as $record) { - $key = $record[$group_by]; - $ret = []; - if (is_array($only_these_fields)) { - $result[$key][] = array_intersect_key($record, array_flip($only_these_fields)); - } else { - $result[$key][] = $record; - } - } - if ($group_func === null) { - $group_func = 'current'; - } - return array_map($group_func, $result); - } - - /** - * get the first element - * - * @return ?T first element or null - */ - public function first() - { - $keys = array_keys($this->storage); - $first_offset = reset($keys); - return $this->offsetGet($first_offset ?: 0); - } - - /** - * get the last element - * - * @return ?T last element or null - */ - public function last() - { - $keys = array_keys($this->storage); - $last_offset = end($keys); - return $this->offsetGet($last_offset ?: 0); - } - - /** - * get the the value from given key from first element - * - * @param string $key - * @return mixed - */ - public function val($key) - { - $first = $this->first(); - return $first[$key] ?? null; - } - - /** - * mark element(s) for deletion - * where given column has given value(s) - * element(s) are moved to - * internal deleted collection - * pass array for multiple values - * - * operators: - * == equal, like php - * === identical, like php - * !=,<> not equal, like php - * !== not identical, like php - * <,>,<=,>= less,greater,less or equal,greater or equal - * >< between without borders, needs two arguments - * >=<= between including borders, needs two arguments - * %= like string, transliterate to ascii,case insensitive - * *= contains string - * ^= begins with string - * $= ends with string - * ~= regex - * - * @param string $key - * @param mixed $values - * @param string|callable(mixed, mixed|array): bool $op operator to find elements - * @return int|false number of unsetted elements - */ - public function unsetBy($key, $values, $op = '==') - { - $ret = false; - $comp_func = self::getCompFunc($op, $values); - foreach ($this->storage as $k => $record) { - if ($comp_func($record[$key])) { - $this->offsetunset($k); - $ret += 1; - } - } - return $ret; - } - - /** - * sorts the collection by columns of contained elements and returns it - * - * works like sql order by: - * first param is a string containing combinations of column names - * and sort direction, separated by comma e.g. - * 'name asc, nummer desc ' - * sorts first by name ascending and then by nummer descending - * second param denotes the sort type (using PHP sort constants): - * SORT_LOCALE_STRING: - * compare items as strings, transliterate latin1 to ascii, case insensitiv, natural order for numbers - * SORT_NUMERIC: - * compare items as integers - * SORT_STRING: - * compare items as strings - * SORT_NATURAL: - * compare items as strings using "natural ordering" - * SORT_FLAG_CASE: - * can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitively - * - * @param string $order columns to order by - * @param integer $sort_flags - * @return $this the sorted collection - */ - public function orderBy($order, $sort_flags = SORT_LOCALE_STRING) - { - //('name asc, nummer desc ') - $sort_locale = false; - switch ($sort_flags) { - case SORT_NATURAL: - $sort_func = 'strnatcmp'; - break; - case SORT_NATURAL | SORT_FLAG_CASE: - $sort_func = 'strnatcasecmp'; - break; - case SORT_STRING | SORT_FLAG_CASE: - $sort_func = 'strcasecmp'; - break; - case SORT_STRING: - $sort_func = 'strcmp'; - break; - case SORT_NUMERIC: - $sort_func = function ($a, $b) { - return (int) $a - (int) $b; - }; - break; - case SORT_LOCALE_STRING: - default: - $sort_func = 'strnatcasecmp'; - $sort_locale = true; - } - - $sorter = []; - foreach (explode(',', $order) as $one) { - $sorter[] = array_values(array_filter(array_map('trim', explode(' ', $one)))); - } - - $func = function ($d1, $d2) use ($sorter, $sort_func, $sort_locale) { - do { - $current_sorter = current($sorter); - $field = $current_sorter[0]; - $dir = $current_sorter[1] ?? ''; - if (!$sort_locale) { - $value1 = $d1[$field]; - $value2 = $d2[$field]; - } else { - $value1 = static::translitLatin1(mb_substr($d1[$field], 0, 100)); - $value2 = static::translitLatin1(mb_substr($d2[$field], 0, 100)); - } - $ret = $sort_func($value1, $value2); - if (strtolower($dir) == 'desc') $ret = $ret * -1; - } while ($ret === 0 && next($sorter)); - - return $ret; - }; - if (count($sorter)) { - $this->uasort($func); - } - return $this; - } - - /** - * returns a new collection contaning a sequence of original collection - * mimics the sql limit constrain: - * used with one parameter, the first x elements are extracted - * used with two parameters, the first parameter denotes the offset, the second the - * number of elements - * - * @param integer $arg1 - * @param ?integer $arg2 - * @return SimpleCollection - */ - public function limit($arg1, $arg2 = null) - { - if (is_null($arg2)) { - if ($arg1 > 0) { - $row_count = $arg1; - $offset = 0; - } else { - $row_count = abs($arg1); - $offset = $arg1; - } - } else { - $offset = $arg1; - $row_count = $arg2; - } - return self::createFromArray(array_slice($this->storage, $offset, $row_count, true)); - } - - /** - * calls the given method on all elements - * of the collection - * @param literal-string $method methodname to call - * @param array $params parameters for methodcall - * @return array of all return values - */ - public function sendMessage($method, $params = []) { - $results = []; - foreach ($this->storage as $record) { - $results[] = call_user_func_array([$record, $method], $params); - } - return $results; - } - - /** - * magic version of sendMessage - * calls undefineds methods on all elements of the collection - * But beware of the dark side... - * - * @param literal-string $method methodname to call - * @param array $params parameters for methodcall - * @return array of all return values - */ - public function __call($method, $params) - { - return $this->sendMessage($method, $params); - } - - /** - * merge in another collection, elements are appended - * - * @param SimpleCollection $a_collection - * @return void - */ - public function merge(SimpleCollection $a_collection) - { - $this->storage = array_merge($this->storage, $a_collection->getArrayCopy()); - } -} diff --git a/lib/classes/SimpleCollection.php b/lib/classes/SimpleCollection.php new file mode 100644 index 0000000..6acc10d --- /dev/null +++ b/lib/classes/SimpleCollection.php @@ -0,0 +1,782 @@ + + * @copyright 2013 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @template T + */ +class SimpleCollection extends StudipArrayObject +{ + /** + * callable to initialize collection + * + * @var ?callable(): array + */ + protected $finder; + + /** + * number of records after last init + * + * @var int + */ + protected $last_count; + + /** + * collection with deleted records + * @var static + */ + protected $deleted; + + /** + * creates a collection from an array of arrays + * all arrays should contain same keys, but is not enforced + * + * @param array $data array containing assoc arrays + * @return SimpleCollection + */ + public static function createFromArray(array $data) + { + return new self($data); + } + + /** + * converts arrays or objects to ArrayObject objects + * if ArrayAccess interface is not available + * + * @param mixed $a + * @return StudipArrayObject|ArrayAccess + */ + public static function arrayToArrayObject($a) + { + if ($a instanceof StudipArrayObject) { + $a->setFlags(StudipArrayObject::ARRAY_AS_PROPS); + return $a; + } + + if ($a instanceof ArrayObject) { + return new StudipArrayObject($a->getArrayCopy(), StudipArrayObject::ARRAY_AS_PROPS); + } + + if ($a instanceof ArrayAccess) { + return $a; + } + + return new StudipArrayObject((array) $a, StudipArrayObject::ARRAY_AS_PROPS); + } + + /** + * returns closure to compare a value against given arguments + * using given operator + * + * @param string|callable(mixed, mixed|array): bool $operator + * @param mixed|array $args + * @throws InvalidArgumentException + * @return callable(mixed): bool comparison function + */ + public static function getCompFunc($operator, $args) + { + if (is_callable($operator)) { + $comp_func = function ($a) use ($args, $operator) { + return $operator($a, $args); + }; + } else { + if (!is_array($args)) { + $args = [$args]; + } + switch ($operator) { + case '==': + $comp_func = function ($a) use ($args) { + return in_array($a, $args); + }; + break; + case '===': + $comp_func = function ($a) use ($args) { + return in_array($a, $args, true); + }; + break; + case '!=': + case '<>': + $comp_func = function ($a) use ($args) { + return !in_array($a, $args); + }; + break; + case '!==': + $comp_func = function ($a) use ($args) { + return !in_array($a, $args, true); + }; + break; + case '<': + case '>': + case '<=': + case '>=': + $op_func = function ($a, $b) use ($operator) { + if ($operator === '<') { + return $a < $b; + } elseif ($operator === '<=') { + return $a <= $b; + } elseif ($operator === '>=') { + return $a >= $b; + } elseif ($operator === '>') { + return $a > $b; + } + }; + $comp_func = function ($a) use ($op_func, $args) { + return $op_func($a, $args[0]); + }; + break; + case '><': + $comp_func = function ($a) use ($args) { + return $a > $args[0] && $a < $args[1]; + }; + break; + case '>=<=': + $comp_func = function ($a) use ($args) { + return $a >= $args[0] && $a <= $args[1]; + }; + break; + case '%=': + $comp_func = function ($a) use ($args) { + $a = mb_strtolower(static::translitLatin1($a)); + $args = array_map([static::class, 'translitLatin1'], $args); + $args = array_map('mb_strtolower', $args); + return in_array($a, $args); + }; + break; + case '*=': + $comp_func = function ($a) use ($args) { + foreach ($args as $arg) { + if (mb_strpos($a, $arg) !== false) { + return true; + } + } + return false; + }; + break; + case '^=': + $comp_func = function ($a) use ($args) { + foreach ($args as $arg) { + if (mb_strpos($a, $arg) === 0) { + return true; + } + } + return false; + }; + break; + case '$=': + $comp_func = function ($a) use ($args) { + foreach ($args as $arg) { + $found = mb_strrpos($a, $arg); + if ($found !== false && ($found + mb_strlen($arg)) === mb_strlen($a)) { + return true; + } + } + return false; + }; + break; + case '~=': + $comp_func = function ($a) use ($args) { + foreach ($args as $arg) { + if (preg_match($arg, $a) === 1) { + return true; + } + } + return false; + }; + break; + default: + throw new InvalidArgumentException('unknown operator: ' . $operator); + } + } + return $comp_func; + } + + /** + * transliterates latin1 string to ascii + * + * @param string $text + * @return string + */ + public static function translitLatin1($text) + { + if (!preg_match('/[\200-\377]/', $text)) { + return $text; + } + $text = str_replace(['ä','Ä','ö','Ö','ü','Ü','ß'], ['a','A','o','O','u','U','s'], $text); + $text = str_replace(['À','Á','Â','Ã','Å','Æ'], 'A' , $text); + $text = str_replace(['à','á','â','ã','å','æ'], 'a' , $text); + $text = str_replace(['È','É','Ê','Ë'], 'E' , $text); + $text = str_replace(['è','é','ê','ë'], 'e' , $text); + $text = str_replace(['Ì','Í','Î','Ï'], 'I' , $text); + $text = str_replace(['ì','í','î','ï'], 'i' , $text); + $text = str_replace(['Ò','Ó','Õ','Ô','Ø'], 'O' , $text); + $text = str_replace(['ò','ó','ô','õ','ø'], 'o' , $text); + $text = str_replace(['Ù','Ú','Û'], 'U' , $text); + $text = str_replace(['ù','ú','û'], 'u' , $text); + $text = str_replace(['Ç','ç','Ð','Ñ','Ý','ñ','ý','ÿ'], ['C','c','D','N','Y','n','y','y'] , $text); + return $text; + } + + /** + * Constructor + * + * @param array|callable(): array $data array or closure to fill collection + */ + public function __construct($data = []) + { + parent::__construct(); + $this->finder = is_callable($data) ? $data : null; + $this->deleted = clone $this; + if (is_callable($data)) { + $this->refresh(); + } else { + $this->exchangeArray($data); + } + } + + /** + * @param array $input + * @return array + */ + public function exchangeArray($input) + { + return parent::exchangeArray(array_map( + [static::class, 'arrayToArrayObject'], + $input + )); + } + + /** + * converts the object and all elements to plain arrays + * + * @return array + */ + public function toArray() + { + $args = func_get_args(); + return $this->map(function ($a) use ($args) { + if (method_exists($a, 'toArray')) { + return call_user_func_array([$a, 'toArray'], $args); + } + if (method_exists($a, 'getArrayCopy')) { + return $a->getArrayCopy(); + } + return (array) $a; + } + ); + } + + /** + * + * @see ArrayObject::append() + */ + public function append($newval) + { + parent::append(static::arrayToArrayObject($newval)); + } + + /** + * Sets the value at the specified index + * ensures the value has ArrayAccess + * + * @param mixed $index + * @param mixed $newval + * + * @see ArrayObject::offsetSet() + */ + + public function offsetSet($index, $newval): void + { + if (is_numeric($index)) { + $index = (int) $index; + } + parent::offsetSet($index, static::arrayToArrayObject($newval)); + } + + /** + * Unsets the value at the specified index + * value is moved to internal deleted collection + * + * @see ArrayObject::offsetUnset() + * @throws InvalidArgumentException + */ + public function offsetUnset($index): void + { + if ($this->offsetExists($index)) { + $this->deleted[] = $this->offsetGet($index); + } + parent::offsetUnset($index); + } + + /** + * sets the finder function + * + * @param callable(): array $finder + * @return void + */ + public function setFinder(callable $finder) + { + $this->finder = $finder; + } + + /** + * get deleted records collection + * @return SimpleCollection + */ + public function getDeleted() + { + return $this->deleted; + } + + /** + * reloads the elements of the collection + * by calling the finder function + * + * @return ?int of records after refresh + */ + public function refresh() + { + if (is_callable($this->finder)) { + $data = call_user_func($this->finder); + $this->exchangeArray($data); + $this->deleted->exchangeArray([]); + return $this->last_count = $this->count(); + } + } + + /** + * returns a new collection containing all elements + * where given columns value matches given value(s) using passed operator + * pass array for multiple values + * + * operators: + * == equal, like php + * === identical, like php + * !=,<> not equal, like php + * !== not identical, like php + * <,>,<=,>= less,greater,less or equal,greater or equal + * >< between without borders, needs two arguments + * >=<= between including borders, needs two arguments + * %= like string, transliterate to ascii,case insensitive + * *= contains string + * ^= begins with string + * $= ends with string + * ~= regex + * + * @param string $key the column name + * @param mixed $values value to search for + * @param string|callable $op operator to find + * @return SimpleCollection with found records + */ + public function findBy($key, $values, $op = '==') + { + $comp_func = self::getCompFunc($op, $values); + return $this->filter(function ($record) use ($comp_func, $key) { + return $comp_func($record[$key]); + }); + } + + /** + * returns the first element + * where given column has given value(s) + * pass array for multiple values + * + * @param string $key the column name + * @param mixed $values value to search for, + * @param string|callable $op operator to find + * @return ?T found record + */ + public function findOneBy($key, $values, $op = '==') + { + $comp_func = self::getCompFunc($op, $values); + return $this->filter(function ($record) use ($comp_func, $key) { + return $comp_func($record[$key]); + }, 1)->first(); + } + + /** + * apply given callback to all elements of + * collection + * + * @param callable(T): int $func the function to call + * @return int|false addition of return values + */ + public function each(callable $func) + { + $result = false; + foreach ($this->storage as $record) { + $result += call_user_func($func, $record); + } + return $result; + } + + /** + * apply given callback to all elements of + * collection and give back array of return values + * + * @param callable(T, mixed): mixed $func the function to call + * @return array + */ + public function map(callable $func) + { + $results = []; + foreach ($this->storage as $key => $value) { + $results[$key] = call_user_func($func, $value, $key); + } + return $results; + } + + /** + * filter elements + * if given callback returns true + * + * @param ?callable(T, mixed): bool $func the function to call + * @param ?integer $limit limit number of found records + * @return SimpleCollection containing filtered elements + */ + public function filter(callable $func = null, $limit = null) + { + $results = []; + $found = 0; + foreach ($this->storage as $key => $value) { + if (call_user_func($func, $value, $key)) { + $results[$key] = $value; + if ($limit && (++$found == $limit)) { + break; + } + } + } + return self::createFromArray($results); + } + + /** + * Returns whether any element of the collection returns true for the + * given callback. + * + * @param callable(T, mixed): bool $func the function to call + * @return bool + */ + public function any(callable $func) + { + foreach ($this->storage as $key => $value) { + if (call_user_func($func, $value, $key)) { + return true; + } + } + return false; + } + + /** + * Returns whether every element of the collection returns true for the + * given callback. + * + * @param callable(T, mixed): bool $func the function to call + * @return bool + */ + public function every(callable $func) + { + foreach ($this->storage as $key => $value) { + if (!call_user_func($func, $value, $key)) { + return false; + } + } + return true; + } + + /** + * extract array of columns values + * pass array or space-delimited string for multiple columns + * + * @param string|array $columns the column(s) to extract + * @return array of extracted values + */ + public function pluck($columns) + { + if (!is_array($columns)) { + $columns = words($columns); + } + $func = function ($r) use ($columns) { + $result = []; + foreach ($columns as $c) { + $result[] = $r[$c]; + } + return $result; + }; + $result = $this->map($func); + return count($columns) === 1 ? array_map('current', $result) : $result; + } + + /** + * returns the collection as grouped array + * first param is the column to group by, it becomes the key in + * the resulting array, default is pk. Limit returned fields with second param + * The grouped entries can optoionally go through the given + * callback. If no callback is provided, only the first grouped + * entry is returned, suitable for grouping by unique column + * + * @param string $group_by the column to group by, pk if ommitted + * @param string|array|null $only_these_fields limit returned fields + * @param ?callable $group_func closure to aggregate grouped entries + * @return array assoc array + */ + public function toGroupedArray($group_by = 'id', $only_these_fields = null, callable $group_func = null) + { + $result = []; + if (is_string($only_these_fields)) { + $only_these_fields = words($only_these_fields); + } + foreach ($this->toArray() as $record) { + $key = $record[$group_by]; + $ret = []; + if (is_array($only_these_fields)) { + $result[$key][] = array_intersect_key($record, array_flip($only_these_fields)); + } else { + $result[$key][] = $record; + } + } + if ($group_func === null) { + $group_func = 'current'; + } + return array_map($group_func, $result); + } + + /** + * get the first element + * + * @return ?T first element or null + */ + public function first() + { + $keys = array_keys($this->storage); + $first_offset = reset($keys); + return $this->offsetGet($first_offset ?: 0); + } + + /** + * get the last element + * + * @return ?T last element or null + */ + public function last() + { + $keys = array_keys($this->storage); + $last_offset = end($keys); + return $this->offsetGet($last_offset ?: 0); + } + + /** + * get the the value from given key from first element + * + * @param string $key + * @return mixed + */ + public function val($key) + { + $first = $this->first(); + return $first[$key] ?? null; + } + + /** + * mark element(s) for deletion + * where given column has given value(s) + * element(s) are moved to + * internal deleted collection + * pass array for multiple values + * + * operators: + * == equal, like php + * === identical, like php + * !=,<> not equal, like php + * !== not identical, like php + * <,>,<=,>= less,greater,less or equal,greater or equal + * >< between without borders, needs two arguments + * >=<= between including borders, needs two arguments + * %= like string, transliterate to ascii,case insensitive + * *= contains string + * ^= begins with string + * $= ends with string + * ~= regex + * + * @param string $key + * @param mixed $values + * @param string|callable(mixed, mixed|array): bool $op operator to find elements + * @return int|false number of unsetted elements + */ + public function unsetBy($key, $values, $op = '==') + { + $ret = false; + $comp_func = self::getCompFunc($op, $values); + foreach ($this->storage as $k => $record) { + if ($comp_func($record[$key])) { + $this->offsetunset($k); + $ret += 1; + } + } + return $ret; + } + + /** + * sorts the collection by columns of contained elements and returns it + * + * works like sql order by: + * first param is a string containing combinations of column names + * and sort direction, separated by comma e.g. + * 'name asc, nummer desc ' + * sorts first by name ascending and then by nummer descending + * second param denotes the sort type (using PHP sort constants): + * SORT_LOCALE_STRING: + * compare items as strings, transliterate latin1 to ascii, case insensitiv, natural order for numbers + * SORT_NUMERIC: + * compare items as integers + * SORT_STRING: + * compare items as strings + * SORT_NATURAL: + * compare items as strings using "natural ordering" + * SORT_FLAG_CASE: + * can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitively + * + * @param string $order columns to order by + * @param integer $sort_flags + * @return $this the sorted collection + */ + public function orderBy($order, $sort_flags = SORT_LOCALE_STRING) + { + //('name asc, nummer desc ') + $sort_locale = false; + switch ($sort_flags) { + case SORT_NATURAL: + $sort_func = 'strnatcmp'; + break; + case SORT_NATURAL | SORT_FLAG_CASE: + $sort_func = 'strnatcasecmp'; + break; + case SORT_STRING | SORT_FLAG_CASE: + $sort_func = 'strcasecmp'; + break; + case SORT_STRING: + $sort_func = 'strcmp'; + break; + case SORT_NUMERIC: + $sort_func = function ($a, $b) { + return (int) $a - (int) $b; + }; + break; + case SORT_LOCALE_STRING: + default: + $sort_func = 'strnatcasecmp'; + $sort_locale = true; + } + + $sorter = []; + foreach (explode(',', $order) as $one) { + $sorter[] = array_values(array_filter(array_map('trim', explode(' ', $one)))); + } + + $func = function ($d1, $d2) use ($sorter, $sort_func, $sort_locale) { + do { + $current_sorter = current($sorter); + $field = $current_sorter[0]; + $dir = $current_sorter[1] ?? ''; + if (!$sort_locale) { + $value1 = $d1[$field]; + $value2 = $d2[$field]; + } else { + $value1 = static::translitLatin1(mb_substr($d1[$field], 0, 100)); + $value2 = static::translitLatin1(mb_substr($d2[$field], 0, 100)); + } + $ret = $sort_func($value1, $value2); + if (strtolower($dir) == 'desc') $ret = $ret * -1; + } while ($ret === 0 && next($sorter)); + + return $ret; + }; + if (count($sorter)) { + $this->uasort($func); + } + return $this; + } + + /** + * returns a new collection contaning a sequence of original collection + * mimics the sql limit constrain: + * used with one parameter, the first x elements are extracted + * used with two parameters, the first parameter denotes the offset, the second the + * number of elements + * + * @param integer $arg1 + * @param ?integer $arg2 + * @return SimpleCollection + */ + public function limit($arg1, $arg2 = null) + { + if (is_null($arg2)) { + if ($arg1 > 0) { + $row_count = $arg1; + $offset = 0; + } else { + $row_count = abs($arg1); + $offset = $arg1; + } + } else { + $offset = $arg1; + $row_count = $arg2; + } + return self::createFromArray(array_slice($this->storage, $offset, $row_count, true)); + } + + /** + * calls the given method on all elements + * of the collection + * @param literal-string $method methodname to call + * @param array $params parameters for methodcall + * @return array of all return values + */ + public function sendMessage($method, $params = []) { + $results = []; + foreach ($this->storage as $record) { + $results[] = call_user_func_array([$record, $method], $params); + } + return $results; + } + + /** + * magic version of sendMessage + * calls undefineds methods on all elements of the collection + * But beware of the dark side... + * + * @param literal-string $method methodname to call + * @param array $params parameters for methodcall + * @return array of all return values + */ + public function __call($method, $params) + { + return $this->sendMessage($method, $params); + } + + /** + * merge in another collection, elements are appended + * + * @param SimpleCollection $a_collection + * @return void + */ + public function merge(SimpleCollection $a_collection) + { + $this->storage = array_merge($this->storage, $a_collection->getArrayCopy()); + } +} diff --git a/lib/classes/SimpleORMap.class.php b/lib/classes/SimpleORMap.class.php deleted file mode 100644 index 7124cc4..0000000 --- a/lib/classes/SimpleORMap.class.php +++ /dev/null @@ -1,2483 +0,0 @@ - - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate -{ - /** - * Defines `_` as character used when joining composite primary keys. - */ - const ID_SEPARATOR = '_'; - - /** - * table row data - * @var array $content - */ - protected $content = []; - - /** - * table row data - * @var array $content_db - */ - protected $content_db = []; - - /** - * new state of entry - * @var boolean $is_new - */ - protected $is_new = true; - - /** - * deleted state of entry - * @var boolean $is_deleted - */ - protected $is_deleted = false; - - /** - * db table metadata - * @var ?array $schemes; - */ - public static $schemes = null; - - /** - * configuration data for subclasses - * @see self::configure() - * @var array $config; - */ - protected static $config = []; - - /** - * stores instantiated related objects - * @var array $relations - */ - protected $relations = []; - - /** - * assoc array for storing values for additional fields - * - * @var array $additional_data - */ - protected $additional_data = []; - - /** - * reserved indentifiers, fields with those names must not have an explicit getXXX() method - * @var array $reserved_slots - */ - protected static $reserved_slots = ['value','newid','iterator','tablemetadata', 'relationvalue','wherequery','relationoptions','data','new','id']; - - /** - * indicator for batch operations in findEachBySQL - * - * @var bool $performs_batch_operation - */ - protected static $performs_batch_operation = false; - - /** - * name of db table - * @return string - */ - protected static function db_table() - { - return static::config('db_table'); - } - - /** - * table columns - * @return array - */ - protected static function db_fields() - { - return static::config('db_fields'); - } - - /** - * primary key columns - * @return array - */ - protected static function pk() - { - return static::config('pk'); - } - - /** - * default values for columns - * @return array - */ - protected static function default_values() - { - return static::config('default_values'); - } - - /** - * list of columns to deserialize - * @return array key is name of column, value is name of ArrayObject class - */ - protected static function serialized_fields() - { - return static::config('serialized_fields'); - } - - /** - * aliases for columns - * alias => column - * @return array - */ - protected static function alias_fields() - { - return static::config('alias_fields'); - } - - /** - * multi-language fields - * name => boolean - * @return array - */ - protected static function i18n_fields() - { - return static::config('i18n_fields'); - } - - /** - * additional computed fields - * name => callable - * @return array - */ - protected static function additional_fields() - { - return static::config('additional_fields'); - } - - /** - * 1:n relation - * @return array - */ - protected static function has_many() - { - return static::config('has_many'); - } - - /** - * 1:1 relation - * @return array - */ - protected static function has_one() - { - return static::config('has_one'); - } - - /** - * n:1 relations - * @return array - */ - protected static function belongs_to() - { - return static::config('belongs_to'); - } - - /** - * n:m relations - * @return array - */ - protected static function has_and_belongs_to_many() - { - return static::config('has_and_belongs_to_many'); - } - - /** - * callbacks - * @return array> - */ - protected static function registered_callbacks() - { - return static::config('registered_callbacks'); - } - - /** - * contains an array of all used identifiers for fields - * (db columns + aliased columns + additional columns + relations) - * @return array - */ - protected static function known_slots() - { - return static::config('known_slots'); - } - - /** - * assoc array used to map SORM callback to NotificationCenter - * keys are SORM callbacks, values notifications - * eg. 'after_create' => 'FooDidCreate' - * - * @return array - */ - protected static function notification_map() - { - return static::config('notification_map'); - } - - /** - * assoc array for mapping get/set Methods - * - * @return array - */ - protected static function getter_setter_map() - { - return static::config('getter_setter_map'); - } - - ////////////////////////////////////////////////// - - /** - * set configuration data from subclass - * - * @param ?array $config configuration data - * @return void - */ - protected static function configure($config = []) - { - $class = static::class; - - if (empty($config['db_table'])) { - $config['db_table'] = strtolower($class); - } - - if (!isset($config['db_fields'])) { - if (static::tableScheme($config['db_table'])) { - $config['db_fields'] = self::$schemes[$config['db_table']]['db_fields']; - $config['pk'] = self::$schemes[$config['db_table']]['pk']; - } - } - - if (isset($config['pk']) - && !isset($config['db_fields']['id']) - && !isset($config['alias_fields']['id']) - && !isset($config['additional_fields']['id']) - ) { - if (count($config['pk']) === 1) { - $config['alias_fields']['id'] = $config['pk'][0]; - } else { - $config['additional_fields']['id'] = ['get' => '_getId', - 'set' => '_setId']; - } - } - if (isset($config['additional_fields'])) { - foreach ($config['additional_fields'] as $a_field => $a_config) { - if (is_array($a_config) && !(isset($a_config['get']) || isset($a_config['set']))) { - $relation = $a_config[0] ?? ''; - $relation_field = $a_config[1] ?? ''; - if (!$relation) { - [$relation, $relation_field] = explode('_', $a_field); - } - if (!$relation_field || !$relation) { - throw new UnexpectedValueException('no relation found for autoget/set additional field: ' . $a_field); - } - $config['additional_fields'][$a_field] = ['get' => '_getAdditionalValueFromRelation', - 'set' => '_setAdditionalValue', - 'relation' => $relation, - 'relation_field' => $relation_field]; - } - } - } - if (isset($config['serialized_fields'])) { - foreach ($config['serialized_fields'] as $a_field => $object_type) { - if (!(is_subclass_of($object_type, 'StudipArrayObject'))) { - throw new UnexpectedValueException(sprintf('serialized field %s must use subclass of StudipArrayObject', $a_field)); - } - } - } - - foreach (['default_values', 'serialized_fields', 'alias_fields', 'i18n_fields', 'additional_fields'] as $fields) { - if (!isset($config[$fields])) { - $config[$fields] = []; - } - } - - foreach (['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { - if (isset($config[$type]) && is_array($config[$type])) { - foreach (array_keys($config[$type]) as $one) { - $config['relations'][$one] = null; - } - } else { - $config[$type] = []; - } - } - - $callbacks = ['before_create', - 'before_update', - 'before_store', - 'before_delete', - 'before_initialize', - 'after_create', - 'after_update', - 'after_store', - 'after_delete', - 'after_initialize']; - - foreach ($callbacks as $callback) { - if (!isset($config['registered_callbacks'][$callback])) { - $config['registered_callbacks'][$callback] = []; - } - } - - $auto_notification_map['after_create'] = $class . 'DidCreate'; - $auto_notification_map['after_store'] = $class . 'DidStore'; - $auto_notification_map['after_delete'] = $class . 'DidDelete'; - $auto_notification_map['after_update'] = $class . 'DidUpdate'; - $auto_notification_map['before_create'] = $class . 'WillCreate'; - $auto_notification_map['before_store'] = $class . 'WillStore'; - $auto_notification_map['before_delete'] = $class . 'WillDelete'; - $auto_notification_map['before_update'] = $class . 'WillUpdate'; - - foreach ($auto_notification_map as $cb => $notification) { - if (isset($config['notification_map'][$cb])) { - if (strpos($config['notification_map'][$cb], $notification) !== false) { - $config['notification_map'][$cb] .= ' ' . $notification; - } - } else { - $config['notification_map'][$cb] = $notification; - } - } - - if (is_array($config['notification_map'])) { - foreach (array_keys($config['notification_map']) as $cb) { - $config['registered_callbacks'][$cb][] = 'cbNotificationMapper'; - } - } - - if (!I18N::isEnabled() || empty($config['i18n_fields'])) { - $config['i18n_fields'] = []; - } elseif (is_string($config['i18n_fields'])) { - $i18n_fields = words($config['i18n_fields']); - $config['i18n_fields'] = array_combine( - $i18n_fields, - array_fill(0, count($i18n_fields), true) - ); - } elseif (array_is_list($config['i18n_fields'])) { - $config['i18n_fields'] = array_combine( - $config['i18n_fields'], - array_fill(0, count($config['i18n_fields']), true) - ); - } - - array_unshift($config['registered_callbacks']['after_initialize'], 'cbAfterInitialize'); - - $config['known_slots'] = array_merge( - array_keys($config['db_fields']), - array_keys($config['alias_fields'] ?? []), - array_keys($config['additional_fields'] ?? []), - array_keys($config['relations'] ?? []) - ); - - foreach (array_map('strtolower', get_class_methods($class)) as $method) { - if (in_array(substr($method, 0, 3), ['get', 'set'])) { - $verb = substr($method, 0, 3); - $name = substr($method, 3); - if (in_array($name, $config['known_slots']) && !in_array($name, static::$reserved_slots) && !isset($config['additional_fields'][$name][$verb])) { - $config['getter_setter_map'][$name][$verb] = $method; - } - } - } - self::$config[$class] = $config; - } - - /** - * fetch config data for the called class - * - * @param string $key config key - * @return mixed value of config key (null if not set) - */ - protected static function config($key) - { - if (!array_key_exists(static::class, self::$config)) { - static::configure(); - } - - return self::$config[static::class][$key] ?? null; - } - - /** - * fetch table metadata from db or from local cache - * - * @param string $db_table - * @return bool true if metadata could be fetched - */ - public static function tableScheme($db_table) - { - if (self::$schemes === null) { - $cache = \Studip\Cache\Factory::getCache(); - self::$schemes = unserialize($cache->read('DB_TABLE_SCHEMES')); - } - if (!isset(self::$schemes[$db_table])) { - $db = DBManager::get()->query("SHOW COLUMNS FROM $db_table"); - while($rs = $db->fetch(PDO::FETCH_ASSOC)){ - $db_fields[strtolower($rs['Field'])] = [ - 'name' => $rs['Field'], - 'null' => $rs['Null'], - 'default' => $rs['Default'], - 'type' => $rs['Type'], - 'extra' => $rs['Extra'] - ]; - if ($rs['Key'] == 'PRI'){ - $pk[] = strtolower($rs['Field']); - } - } - self::$schemes[$db_table]['db_fields'] = $db_fields; - self::$schemes[$db_table]['pk'] = $pk; - $cache = \Studip\Cache\Factory::getCache(); - $cache->write('DB_TABLE_SCHEMES', serialize(self::$schemes)); - } - return isset(self::$schemes[$db_table]); - } - - /** - * force reload of cached table metadata - * @return void - */ - public static function expireTableScheme() - { - \Studip\Cache\Factory::getCache()->expire('DB_TABLE_SCHEMES'); - self::$schemes = null; - self::$config = []; - } - - /** - * Returns new instance for given key when found in the database, else null. - * - * @param string|array $id primary key - * @return static|null - */ - public static function find($id) - { - $ref = new ReflectionClass(static::class); - /** @var static $record */ - $record = $ref->newInstanceArgs(func_get_args()); - if (!$record->isNew()) { - return $record; - } else { - return null; - } - } - - /** - * Returns true if given key exists in the database. - * - * @param string|array $id primary key - * @return boolean - */ - public static function exists($id) - { - $ret = false; - $db_table = static::db_table(); - $record = new static(); - $record->setId(...func_get_args()); - $where_query = $record->getWhereQuery(); - if ($where_query) { - $query = "SELECT 1 FROM `$db_table` WHERE " - . join(" AND ", $where_query); - $ret = (bool)DBManager::get()->query($query)->fetchColumn(); - } - return $ret; - } - - /** - * returns number of records - * - * @param ?string $sql sql clause to use on the right side of WHERE - * @param ?array $params params for query - * @return int - */ - public static function countBySql($sql = '1', $params = []) - { - $db_table = static::db_table(); - $db = DBManager::get(); - $has_join = stripos($sql, 'JOIN '); - if ($has_join === false || $has_join > 10) { - $sql = 'WHERE ' . $sql; - } - $sql = "SELECT count(*) FROM `" . $db_table . "` " . $sql; - $st = $db->prepare($sql); - $st->execute($params); - return (int)$st->fetchColumn(); - } - - /** - * creates new record with given data in db - * returns the new object or null - * @param array $data assoc array of record - * @return ?static - */ - public static function create($data) - { - $record = new static(); - $record->setData($data, false); - if ($record->store()) { - return $record; - } else { - return null; - } - } - - /** - * build object with given data - * - * @param array $data assoc array of record - * @param ?bool $is_new set object to new state - * @return static - */ - public static function build($data, $is_new = true) - { - $record = new static(); - $record->setData($data, !$is_new); - $record->setNew($is_new); - return $record; - } - - /** - * build object with given data and mark it as existing - * - * @param array $data assoc array of record - * @return static - */ - public static function buildExisting($data) - { - return static::build($data, false); - } - - /** - * generate SimpleORMap object structure from assoc array - * if given array contains data of related objects in sub-arrays - * they are also generated. Existing records are updated, new records are created - * (but changes are not yet stored) - * - * @param array $data - * @return static - */ - public static function import($data) - { - $record_data = []; - $relation_data = []; - foreach ($data as $key => $value) { - $temp = static::alias_fields()[$key] ?? $key; - if (isset(static::db_fields()[$temp])) { - $record_data[$key] = $value; - } else { - $relation_data[$key] = $value; - } - } - $record = static::toObject($record_data); - if (!$record instanceof static) { - $record = new static(); - $record->setData($record_data, true); - } else { - $record->setData($record_data); - } - foreach ($relation_data as $relation => $data) { - if (!$record->isRelation($relation)) { - continue; - } - - $options = $record->getRelationOptions($relation); - if ($options['type'] == 'has_one') { - $record->{$relation} = call_user_func([$options['class_name'], 'import'], $data); - } - if ($options['type'] == 'has_many' || $options['type'] == 'has_and_belongs_to_many') { - foreach ($data as $one) { - $current = call_user_func([$options['class_name'], 'import'], $one); - if ($options['type'] == 'has_many') { - $foreign_key_value = call_user_func($options['assoc_func_params_func'], $record); - call_user_func($options['assoc_foreign_key_setter'], $current, $foreign_key_value); - } - if ($current->id !== null) { - $existing = $record->{$relation}->find($current->id); - if ($existing) { - $existing->setData($current); - } else { - $record->{$relation}->append($current); - } - } else { - $record->{$relation}->append($current); - } - } - } - } - return $record; - } - - /** - * returns array of instances of given class filtered by given sql - * @param string $sql sql clause to use on the right side of WHERE - * @param ?array $params parameters for query - * @return static[] array of "self" objects - */ - public static function findBySQL($sql, $params = []) - { - $db_table = static::db_table(); - - $has_join = stripos($sql, 'JOIN '); - if ($has_join === false || $has_join > 10) { - $sql = 'WHERE ' . $sql; - } - $sql = "SELECT `" . $db_table . "`.* FROM `" . $db_table . "` " . $sql; - $stmt = DBManager::get()->prepare($sql); - $stmt->execute($params); - - $record = static::build([], false); - - $ret = []; - do { - $clone = clone $record; - $clone->setNew(false); - $stmt->setFetchMode(PDO::FETCH_INTO, $clone); - - if ($clone = $stmt->fetch()) { - $clone->applyCallbacks('after_initialize'); - $ret[] = $clone; - } - } while ($clone); - return $ret; - } - - /** - * returns one instance of given class filtered by given sql - * only first row of query is used - * @param string $where sql clause to use on the right side of WHERE - * @param ?array $params parameters for query - * @return ?static - */ - public static function findOneBySQL($where, $params = []) - { - if (stripos($where, 'LIMIT') === false) { - $where .= " LIMIT 1"; - } - $found = static::findBySQL($where, $params); - return isset($found[0]) ? $found[0] : null; - } - - /** - * find related records for a n:m relation (has_many_and_belongs_to_many) - * using a combination table holding the keys - * - * @param string $foreign_key_value value of foreign key to find related records - * @param array $options relation options from other side of relation - * @return static[] array of "self" objects - */ - public static function findThru($foreign_key_value, $options) - { - $thru_table = $options['thru_table']; - $thru_key = $options['thru_key']; - $thru_assoc_key = $options['thru_assoc_key']; - $assoc_foreign_key = $options['assoc_foreign_key']; - - $db_table = static::db_table(); - - $sql = "SELECT `$db_table`.* FROM `$thru_table` - INNER JOIN `$db_table` ON `$thru_table`.`$thru_assoc_key` = `$db_table`.`$assoc_foreign_key` - WHERE `$thru_table`.`$thru_key` = ? " . ($options['order_by'] ?? ''); - $st = DBManager::get()->prepare($sql); - $st->execute([$foreign_key_value]); - - $record = static::build([], false); - - $ret = []; - do { - $clone = clone $record; - $clone->setNew(false); - $st->setFetchMode(PDO::FETCH_INTO, $clone); - - if ($clone = $st->fetch()) { - $clone->applyCallbacks('after_initialize'); - $ret[] = $clone; - } - } while ($clone); - return $ret; - } - - /** - * passes objects for given sql through given callback - * - * @param callable $callable callback which gets the current record as param - * @param string $sql where clause of sql - * @param ?array $params sql statement parameters - * @return integer number of found records - */ - public static function findEachBySQL($callable, $sql, $params = []) - { - $has_join = stripos($sql, 'JOIN '); - if ($has_join === false || $has_join > 10) { - $sql = "WHERE {$sql}"; - } - - $db_table = static::db_table(); - $st = DBManager::get()->prepare("SELECT `{$db_table}`.* FROM `{$db_table}` {$sql}"); - $st->execute($params); - - // Indicate that we are performing a batch operation - static::$performs_batch_operation = true; - - $record = static::build([], false); - - $ret = 0; - do { - $clone = clone $record; - $clone->setNew(false); - $st->setFetchMode(PDO::FETCH_INTO, $clone); - - if ($clone = $st->fetch()) { - $clone->applyCallbacks('after_initialize'); - $callable($clone, $ret++); - } - } while ($clone); - - // Reset batch operation indicator - static::$performs_batch_operation = false; - - return $ret; - } - - /** - * returns array of instances of given class for by given pks - * @param ?array $pks array of primary keys - * @param ?string $order order by clause - * @param ?array $order_params - * @return static[] - */ - public static function findMany($pks = [], $order = '', $order_params = []) - { - $db_table = static::db_table(); - $pk = static::pk(); - $db = DBManager::get(); - if (count($pk) > 1) { - throw new Exception('not implemented yet'); - } - $where = "`$db_table`.`{$pk[0]}` IN (" . $db->quote($pks) . ") "; - return static::findBySQL($where . $order, $order_params); - } - - /** - * passes objects for by given pks through given callback - * - * @param callable $callable callback which gets the current record as param - * @param ?array $pks array of primary keys of called class - * @param ?string $order order by sql - * @param ?array $order_params - * @return integer number of found records - */ - public static function findEachMany($callable, $pks = [], $order = '', $order_params = []) - { - $db_table = static::db_table(); - $pk = static::pk(); - $db = DBManager::get(); - if (count($pk) > 1) { - throw new Exception('not implemented yet'); - } - $where = "`$db_table`.`{$pk[0]}` IN (" . $db->quote($pks) . ") "; - return static::findEachBySQL($callable, $where . $order, $order_params); - } - - /** - * passes objects for given sql through given callback - * and returns an array of callback return values - * - * @param callable $callable callback which gets the current record as param - * @param string $where where clause of sql - * @param array $params sql statement parameters - * @return array return values of callback - */ - public static function findAndMapBySQL($callable, $where, $params = []) - { - $ret = []; - $calleach = function($m) use (&$ret, $callable) { - $ret[] = $callable($m); - }; - static::findEachBySQL($calleach, $where, $params); - return $ret; - } - - /** - * passes objects for by given pks through given callback - * and returns an array of callback return values - * - * @param callable $callable callback which gets the current record as param - * @param ?array $pks array of primary keys of called class - * @param ?string $order order by sql - * @param ?array $order_params - * @return array return values of callback - */ - public static function findAndMapMany($callable, $pks = [], $order = '', $order_params = []) - { - $ret = []; - $calleach = function($m) use (&$ret, $callable) { - $ret[] = $callable($m); - }; - $db_table = static::db_table(); - $pk = static::pk(); - $db = DBManager::get(); - if (count($pk) > 1) { - throw new Exception('not implemented yet'); - } - $where = "`$db_table`.`{$pk[0]}` IN (" . $db->quote($pks) . ") "; - static::findEachBySQL($calleach, $where . $order, $order_params); - return $ret; - } - - /** - * deletes objects specified by sql clause - * @param string $where sql clause to use on the right side of WHERE - * @param ?array $params parameters for query - * @return integer number of deleted records - */ - public static function deleteBySQL($where, $params = []) - { - $killeach = function($record) {$record->delete();}; - return static::findEachBySQL($killeach, $where, $params); - } - - /** - * returns object of given class for given id or null - * the param could be a string, an assoc array containing primary key field - * or an already matching object. In all these cases an object is returned - * - * @param string|static|array $id_or_object id as string, object or assoc array - * @return static - */ - public static function toObject($id_or_object) - { - if ($id_or_object instanceof static) { - return $id_or_object; - } - if (is_array($id_or_object)) { - $pk = static::pk(); - $key_values = []; - foreach ($pk as $key) { - if (array_key_exists($key, $id_or_object)) { - $key_values[] = $id_or_object[$key]; - } - } - if (count($pk) === count($key_values)) { - if (count($pk) === 1) { - $id = $key_values[0]; - } else { - $id = $key_values; - } - } else { - $id = null; - } - } else { - $id = $id_or_object; - } - return static::find($id); - } - - /** - * interceptor for static findByColumn / findEachByColumn / countByColumn / - * deleteByColumn magic - * - * @param string $name - * @param array $arguments - * @throws BadMethodCallException - * @return int|static|static[] - */ - public static function __callStatic(string $name, array $arguments) - { - $db_table = static::db_table(); - $alias_fields = static::alias_fields(); - $db_fields = static::db_fields(); - $name = strtolower($name); - $order = ''; - $param_arr = []; - $where = ''; - $where_param = is_array($arguments[0]) ? $arguments[0] : [$arguments[0]]; - $action = strstr($name, 'by', true); - $field = substr($name, strlen($action) + 2); - switch ($action) { - case 'findone': - $order = $arguments[1] ?? ''; - $param_arr[0] =& $where; - $param_arr[1] = [$where_param]; - $method = 'findonebysql'; - break; - case 'find': - case 'findmany': - $order = $arguments[1] ?? ''; - $param_arr[0] =& $where; - $param_arr[1] = [$where_param]; - $method = 'findbysql'; - break; - case 'findeach': - case 'findeachmany': - $order = $arguments[2] ?? ''; - $param_arr[0] = $arguments[0]; - $param_arr[1] =& $where; - $param_arr[2] = [$arguments[1]]; - $method = 'findeachbysql'; - break; - case 'count': - case 'delete': - $param_arr[0] =& $where; - $param_arr[1] = [$where_param]; - $method = "{$action}bysql"; - break; - default: - throw new BadMethodCallException("Method " . static::class . "::$name not found"); - } - if (isset($alias_fields[$field])) { - $field = $alias_fields[$field]; - } - if (isset($db_fields[$field])) { - $where = "`$db_table`.`$field` IN(?) " . $order; - return call_user_func_array([static::class, $method], $param_arr); - } - throw new BadMethodCallException("Method " . static::class . "::$name not found"); - } - - /** - * constructor, give primary key of record as param to fetch - * corresponding record from db if available, if not preset primary key - * with given value. Give null to create new record - * - * @param null|int|string|array $id primary key of table - */ - function __construct($id = null) - { - foreach(['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { - foreach (array_keys($this->$type()) as $one) { - $this->relations[$one] = null; - } - } - - if ($id) { - $this->setId($id); - } - $this->restore(); - } - - /** - * returns internal used id value (multiple keys concatenated with _) - * @param mixed $field unused parameter - * @return ?string - */ - protected function _getId($field) - { - return is_null($this->getId()) - ? null - : implode(self::ID_SEPARATOR, $this->getId()); - } - - /** - * sets internal used id value (multiple keys concatenated with _) - * @param string $field Field to set (unused since it's always the id) - * @param string $value Value for id field - * @return bool - */ - protected function _setId($field, $value) - { - return $this->setId(explode(self::ID_SEPARATOR, $value)); - } - - /** - * retrieves an additional field value from relation - * - * @param string $field - * @return mixed - */ - protected function _getAdditionalValueFromRelation($field) - { - [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], - $this->additional_fields()[$field]['relation_field']]; - if (!array_key_exists($field, $this->additional_data)) { - $this->_setAdditionalValue($field, $this->getRelationValue($relation, $relation_field)); - } - return $this->additional_data[$field]; - } - - /** - * sets additional value in field imported from relation - * - * @param string $field - * @param mixed $value - * @return mixed - */ - protected function _setAdditionalValueFromRelation($field, $value) - { - [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], - $this->additional_fields()[$field]['relation_field']]; - $this->$relation->$field = $value; - unset($this->additional_data[$field]); - return $this->_getAdditionalValueFromRelation($field); - } - - /** - * @param string $field - * @return mixed - */ - protected function _getAdditionalValue($field) - { - return $this->additional_data[$field]; - } - - /** - * @param string $field - * @param mixed $value - * @return mixed - */ - protected function _setAdditionalValue($field, $value) - { - return $this->additional_data[$field] = $value; - } - - /** - * clean up references after cloning - * @return void - */ - function __clone() - { - $this->setNew(true); - //all references link still to old object => reset all aliases - foreach ($this->alias_fields() as $alias => $field) { - if (isset($this->db_fields()[$field])) { - $content_value = $this->content[$field]; - $content_db_value = $this->content_db[$field]; - unset($this->content[$alias]); - unset($this->content_db[$alias]); - unset($this->content[$field]); - unset($this->content_db[$field]); - if (is_object($content_value)) { - $this->content[$field] = clone $content_value; - } else { - $this->content[$field] = $content_value; - } - if (is_object($content_db_value)) { - $this->content_db[$field] = clone $content_db_value; - } else { - $this->content_db[$field] = $content_db_value; - } - } - } - foreach ($this->alias_fields() as $alias => $field) { - if (isset($this->db_fields()[$field])) { - $this->content[$alias] =& $this->content[$field]; - $this->content_db[$alias] =& $this->content_db[$field]; - } - } - //unset all relations for now - //TODO: maybe a deep copy of all belonging objects is more appropriate - foreach(['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { - foreach (array_keys($this->$type()) as $one) { - $this->relations[$one] = null; - } - } - //begun the clone war has... hmpf - } - - /** - * try to determine all needed options for a relationship from - * configured options - * - * @param string $type - * @param string $name - * @param array $options - * @throws Exception if options for thru_table could not be determined - * @return array - */ - protected function parseRelationOptions($type, $name, $options) - { - if (empty($options['class_name'])) { - throw new Exception('Option class_name not set for relation ' . $name); - } - if (empty($options['assoc_foreign_key'])) { - if ($type === 'has_many' || $type === 'has_one') { - $options['assoc_foreign_key'] = $this->pk()[0]; - } else if ($type === 'belongs_to') { - $options['assoc_foreign_key'] = 'id'; - } - } - if ($type === 'has_and_belongs_to_many') { - $thru_table = $options['thru_table']; - if (empty($options['thru_key'])) { - $options['thru_key'] = $this->pk()[0]; - } - if (empty($options['thru_assoc_key']) || empty($options['assoc_foreign_key'])) { - $class = $options['class_name']; - $record = new $class(); - $meta = $record->getTableMetadata(); - if (empty($options['thru_assoc_key'])) { - $options['thru_assoc_key'] = $meta['pk'][0]; - } - if (empty($options['assoc_foreign_key'])) { - $options['assoc_foreign_key']= $meta['pk'][0]; - } - } - static::tableScheme($thru_table); - if (is_array(self::$schemes[$thru_table])) { - $thru_key_ok = isset(self::$schemes[$thru_table]['db_fields'][$options['thru_key']]); - $thru_assoc_key_ok = isset(self::$schemes[$thru_table]['db_fields'][$options['thru_assoc_key']]); - } - if (!$thru_assoc_key_ok || !$thru_key_ok) { - throw new Exception("Could not determine keys for relation " . $name . " through table " . $thru_table); - } - if ($options['assoc_foreign_key'] instanceof Closure) { - throw new Exception("For relation " . $name . " assoc_foreign_key must be a name of a column"); - } - } - if (empty($options['assoc_func'])) { - if ($type !== 'has_and_belongs_to_many') { - $options['assoc_func'] = $options['assoc_foreign_key'] === 'id' ? 'find' : 'findBy' . $options['assoc_foreign_key']; - } else { - $options['assoc_func'] = 'findThru'; - } - } - if (empty($options['foreign_key'])) { - $options['foreign_key'] = 'id'; - } - if (isset($options['foreign_key']) && $options['foreign_key'] instanceof Closure) { - $options['assoc_func_params_func'] = function($record) use ($name, $options) { return call_user_func($options['foreign_key'], $record, $name, $options);}; - } else { - $options['assoc_func_params_func'] = function($record) use ($name, $options) { return $options['foreign_key'] === 'id' ? $record->getId() : $record->getValue($options['foreign_key']);}; - } - if (isset($options['assoc_foreign_key']) && $options['assoc_foreign_key'] instanceof Closure) { - if ($type === 'belongs_to') { - $options['assoc_foreign_key_getter'] = function($record, $that) use ($name, $options) { return call_user_func($options['assoc_foreign_key'], $record, $name, $options, $that);}; - } else { - $options['assoc_foreign_key_setter'] = function($record, $params) use ($name, $options) { return call_user_func($options['assoc_foreign_key'], $record, $params, $name, $options);}; - } - } elseif (!empty($options['assoc_foreign_key'])) { - if ($type === 'belongs_to') { - $options['assoc_foreign_key_getter'] = function($record, $that) use ($name, $options) { return $record->getValue($options['assoc_foreign_key']);}; - } else { - $options['assoc_foreign_key_setter'] = function($record, $value) use ($name, $options) { return $record->setValue($options['assoc_foreign_key'], $value);}; - } - } else { - throw new Exception("Could not determine assoc_foreign_key for relation " . $name); - } - return $options; - } - - /** - * returns array with option for given relation - * available options: - * 'type': relation type, on of 'has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many' - * 'class_name': name of class for related records - * 'foreign_key': name of column with foreign key - * or callback to retrieve foreign key value - * 'assoc_foreign_key': name of foreign key column in related class - * 'assoc_func': name of static method to call on related class to find related records - * 'assoc_func_params_func': callback to retrieve params for assoc_func - * 'thru_table': name of relation table for n:m relation - * 'thru_key': name of column holding foreign key in relation table - * 'thru_assoc_key': name of column holding foreign key from related class in relation table - * 'on_delete': contains simply 'delete' to indicate that related records should be deleted - * or callback to invoke before record gets deleted - * 'on_store': contains simply 'store' to indicate that related records should be stored - * or callback to invoke after record gets stored - * - * @param string $relation name of relation - * @return array assoc array containing options - */ - function getRelationOptions($relation) - { - $options = []; - foreach(['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { - if (isset($this->$type()[$relation])) { - $options = self::$config[get_class($this)][$type][$relation]; - if (!isset($options['type'])) { - $options = $this->parseRelationOptions($type, $relation, $options, $this->db_table()); - $options['type'] = $type; - self::$config[get_class($this)][$type][$relation] = $options; - } - break; - } - } - return $options; - } - - /** - * returns table and columns metadata - * - * @return array assoc array with columns, primary keys and name of table - */ - function getTableMetadata() - { - return ['fields' => $this->db_fields(), - 'pk' => $this->pk(), - 'table' => $this->db_table(), - 'additional_fields' => $this->additional_fields(), - 'alias_fields' => $this->alias_fields(), - 'relations' => array_keys($this->relations)]; - } - - /** - * returns true, if table has an auto_increment column - * - * @return boolean - */ - function hasAutoIncrementColumn() - { - return $this->db_fields()[$this->pk()[0]]['extra'] == 'auto_increment'; - } - - /** - * set primary key for entry, combined keys must be passed as array - * @param int|string|array $id primary key - * @throws InvalidArgumentException if given key is not complete - * @return boolean - */ - public function setId($id) - { - if (!is_array($id)){ - $id = [$id]; - } - if (count($this->pk()) != count($id)){ - throw new InvalidArgumentException("Invalid ID, Primary Key {$this->db_table()} is " .join(",",$this->pk())); - } else { - foreach ($this->pk() as $count => $key){ - $this->content[$key] = $id[$count]; - } - return true; - } - } - - /** - * returns primary key, multiple keys as array - * @return null|string|array current primary key, null if not set - */ - function getId() - { - if (count($this->pk()) == 1) { - return $this->content[$this->pk()[0]] ?? null; - } else { - $id = []; - foreach ($this->pk() as $key) { - if (array_key_exists($key, $this->content)) { - $id[] = $this->content[$key]; - } - } - return (count($this->pk()) == count($id) ? $id : null); - } - } - - /** - * create new unique pk as md5 hash - * if pk consists of multiple columns, false is returned - * @return boolean|string - */ - function getNewId() - { - $id = false; - if (count($this->pk()) == 1) { - do { - $id = md5(uniqid($this->db_table(), 1)); - $db = DBManager::get()->query("SELECT `{$this->pk()[0]}` FROM `{$this->db_table()}` " - . "WHERE `{$this->pk()[0]}` = '$id'"); - } while($db->fetch()); - } - return $id; - } - - /** - * returns data of table row as assoc array - * pass array of fieldnames or ws separated string to limit - * fields - * - * @param null|array|string $only_these_fields limit returned fields - * @return array - */ - function toArray($only_these_fields = null) - { - $ret = []; - if (is_string($only_these_fields)) { - $only_these_fields = words($only_these_fields); - } - $fields = array_diff($this->known_slots(), array_keys($this->relations)); - if (is_array($only_these_fields)) { - $only_these_fields = array_filter(array_map(function($s) { - return is_string($s) ? strtolower($s) : null; - }, $only_these_fields)); - $fields = array_intersect($only_these_fields, $fields); - } - foreach ($fields as $field) { - $ret[$field] = $this->getValue($field); - if ($ret[$field] instanceof StudipArrayObject) { - $ret[$field] = $ret[$field]->getArrayCopy(); - } - } - return $ret; - } - - /** - * Returns data of table row as assoc array with raw contents like - * they are in the database. - * Pass array of fieldnames or ws separated string to limit - * fields. - * - * @param null|array|string $only_these_fields - * @return array - */ - function toRawArray($only_these_fields = null) - { - $ret = []; - if (is_string($only_these_fields)) { - $only_these_fields = words($only_these_fields); - } - $fields = array_keys($this->db_fields()); - if (is_array($only_these_fields)) { - $only_these_fields = array_filter(array_map(function ($s) { - return is_string($s) ? strtolower($s) : null; - }, $only_these_fields)); - $fields = array_intersect($only_these_fields, $fields); - } - foreach ($fields as $field) { - if ($this->content[$field] instanceof I18NString) { - $ret[$field] = $this->content[$field]->original(); - } elseif ($this->content[$field] === null) { - $ret[$field] = null; - } else { - $ret[$field] = (string)$this->content[$field]; - } - } - return $ret; - } - - /** - * returns data of table row as assoc array - * including related records with a 'has*' relationship - * recurses one level without param - * - * $only_these_fields limits output for relationships in this way: - * $only_these_fields = array('field_1', - * 'field_2', - * 'relation1', - * 'relation2' => array('rel2_f1', - * 'rel2_f2', - * 'rel2_rel11' => array( - * rel2_rel1_f1) - * ) - * ) - * Here all fields of relation1 will be returned. - * - * @param null|array|string $only_these_fields limit returned fields - * @return array - */ - function toArrayRecursive($only_these_fields = null) - { - if (is_string($only_these_fields)) { - $only_these_fields = words($only_these_fields); - } - if (is_null($only_these_fields)) { - $only_these_fields = $this->known_slots(); - } - $ret = $this->toArray($only_these_fields); - $relations = []; - if (is_array($only_these_fields)) { - foreach ($only_these_fields as $key => $value) { - if (!is_array($value) && - array_key_exists(strtolower($value), $this->relations) - ) { - $relations[strtolower($value)] = 0; //not null|array|string to stop recursion - } - if (array_key_exists(strtolower($key), $this->relations)) { - $relations[strtolower($key)] = $value; - } - } - } - if (count($relations)) { - foreach ($relations as $relation_name => $relation_only_these_fields) { - $options = $this->getRelationOptions($relation_name); - if ($options['type'] === 'has_one' || - $options['type'] === 'belongs_to') { - $ret[$relation_name] = - $this->{$relation_name}-> - toArrayRecursive($relation_only_these_fields); - } - if ($options['type'] === 'has_many' || - $options['type'] === 'has_and_belongs_to_many') { - $ret[$relation_name] = - $this->{$relation_name}-> - sendMessage('toArrayRecursive', - [$relation_only_these_fields]); - } - } - } - return $ret; - } - - /** - * returns value of a column - * - * @throws InvalidArgumentException if column could not be found - * @throws BadMethodCallException if getter for additional field could not be found - * @param string $field - * @return null|string|SimpleORMapCollection - */ - public function getValue($field) - { - $field = strtolower($field); - - // No value defined, throw exception - if (!in_array($field, $this->known_slots())) { - throw new InvalidArgumentException(static::class . '::'.$field . ' not found.'); - } - - // Get value by getter - if (isset($this->getter_setter_map()[$field]['get'])) { - return call_user_func([$this, $this->getter_setter_map()[$field]['get']]); - } - - // Get value from content - if (array_key_exists($field, $this->content)) { - return $this->content[$field]; - } - - // Get value from relation - if (array_key_exists($field, $this->relations)) { - $this->initRelation($field); - return $this->relations[$field]; - } - - // Get value from additional_field - if (isset($this->additional_fields()[$field]['get'])) { - // Getter is defined as a closure - if ($this->additional_fields()[$field]['get'] instanceof Closure) { - return call_user_func_array($this->additional_fields()[$field]['get'], [$this, $field]); - } - - // Getter is defined as a method of this object - return call_user_func([$this, $this->additional_fields()[$field]['get']], $field); - } - - // No value found, throw exception - throw new RuntimeException('No value could be found for ' . static::class . '::' . $field); - } - - /** - * gets a value from a related object - * only possible, if the relation has cardinality 1 - * e.g. 'has_one' or 'belongs_to' - * - * @param string $relation name of relation - * @param string $field name of column - * @throws InvalidArgumentException if no relation with given name is found - * @return mixed the value from the related object - */ - function getRelationValue($relation, $field) - { - $field = strtolower($field); - $options = $this->getRelationOptions($relation); - if ($options['type'] === 'has_one' || $options['type'] === 'belongs_to') { - return $this->{$relation}->{$field} ?? null; - } else { - throw new InvalidArgumentException('Relation ' . $relation . ' not found or not applicable.'); - } - } - - /** - * returns default value for column - * - * @param string $field name of column - * @return mixed the default value - */ - function getDefaultValue($field) - { - $default_value = null; - if (!isset($this->default_values()[$field])) { - if (!in_array($field, $this->pk())) { - $meta = $this->db_fields()[$field]; - if (isset($meta['default'])) { - $default_value = $meta['default']; - } elseif ($meta['null'] == 'NO') { - if (strpos($meta['type'], 'text') !== false || strpos($meta['type'], 'char') !== false) { - $default_value = ''; - } - if (strpos($meta['type'], 'int') !== false) { - $default_value = '0'; - } - } - } - } else { - $default_value = $this->default_values()[$field]; - } - return $default_value; - } - - /** - * sets value of a column - * - * @throws InvalidArgumentException if column could not be found - * @throws BadMethodCallException if setter for additional field could not be found - * @param string $field - * @param mixed $value - * @return string - */ - function setValue($field, $value) - { - $field = strtolower($field); - $ret = false; - if (in_array($field, $this->known_slots())) { - if (isset($this->getter_setter_map()[$field]['set'])) { - return call_user_func([$this, $this->getter_setter_map()[$field]['set']], $value); - } - if (array_key_exists($field, $this->content)) { - if (array_key_exists($field, $this->serialized_fields())) { - $ret = $this->setSerializedValue($field, $value); - } elseif ($this->isI18nField($field)) { - $ret = $this->setI18nValue($field, $value); - } else { - $ret = ($this->content[$field] = $value); - } - } elseif (isset($this->additional_fields()[$field]['set'])) { - if ($this->additional_fields()[$field]['set'] instanceof Closure) { - return call_user_func_array($this->additional_fields()[$field]['set'], [$this, $field, $value]); - } else { - return call_user_func([$this, $this->additional_fields()[$field]['set']], $field, $value); - } - } elseif (array_key_exists($field, $this->relations)) { - $options = $this->getRelationOptions($field); - if ($options['type'] === 'has_one' || $options['type'] === 'belongs_to') { - if (is_a($value, $options['class_name'])) { - $this->relations[$field] = $value; - if ($options['type'] == 'has_one') { - $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); - call_user_func($options['assoc_foreign_key_setter'], $value, $foreign_key_value); - } else { - $assoc_foreign_key_value = call_user_func($options['assoc_foreign_key_getter'], $value, $this); - if ($assoc_foreign_key_value === null) { - throw new InvalidArgumentException(sprintf('trying to set belongs_to object of type: %s, but assoc_foreign_key: %s is null', get_class($value), $options['assoc_foreign_key'])); - } - $this->setValue($options['foreign_key'], $assoc_foreign_key_value); - } - } elseif ( - $value === null - && $this->db_fields()[$options['foreign_key']]['null'] === 'YES' - ) { - $this->resetRelation($field); - $this->setValue($options['foreign_key'], null); - - } else { - throw new InvalidArgumentException(sprintf('relation %s expects object of type: %s', $field, $options['class_name'])); - } - } - if ($options['type'] == 'has_many' || $options['type'] == 'has_and_belongs_to_many') { - if (is_array($value) || $value instanceof Traversable) { - $new_ids = []; - $old_ids = $this->{$field}->pluck('id'); - foreach ($value as $current) { - if (!is_a($current, $options['class_name'])) { - throw new InvalidArgumentException(sprintf('relation %s expects object of type: %s', $field, $options['class_name'])); - } - if ($options['type'] == 'has_many') { - $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); - call_user_func($options['assoc_foreign_key_setter'], $current, $foreign_key_value); - } - if ($current->id !== null) { - $new_ids[] = $current->id; - $existing = $this->{$field}->find($current->id); - if ($existing) { - $existing->setData($current); - } else { - $this->{$field}->append($current); - } - } else { - $this->{$field}->append($current); - } - } - foreach (array_diff($old_ids, $new_ids) as $to_delete) { - $this->{$field}->unsetByPK($to_delete); - } - } else { - throw new InvalidArgumentException(sprintf('relation %s expects collection or array of objects of type: %s', $field, $options['class_name'])); - } - } - } - } else { - throw new InvalidArgumentException(get_class($this) . '::'. $field . ' not found.'); - } - return $ret; - } - - /** - * magic method for dynamic properties - * - * @throws InvalidArgumentException if column could not be found - * @throws BadMethodCallException if getter for additional field could not be found - * @param string $field the column or additional field - * @return null|string|SimpleORMapCollection - */ - function __get($field) - { - return $this->getValue($field); - } - /** - * magic method for dynamic properties - * - * @throws InvalidArgumentException if column could not be found - * @throws BadMethodCallException if setter for additional field could not be found - * @param string $field - * @param string $value - * @return string - */ - function __set($field, $value) - { - return $this->setValue($field, $value); - } - /** - * magic method for dynamic properties - * - * @param string $field - * @return bool - */ - function __isset($field) - { - $field = strtolower($field); - if (in_array($field, $this->known_slots())) { - $value = $this->getValue($field); - return $value instanceOf SimpleORMapCollection ? (bool)count($value) : !is_null($value); - } else { - return false; - } - } - - /** - * ArrayAccess: Check whether the given offset exists. - * - * @param string $offset - */ - public function offsetExists($offset): bool - { - return $this->__isset($offset); - } - - /** - * ArrayAccess: Get the value at the given offset. - * - * @throws InvalidArgumentException if column could not be found - * @throws BadMethodCallException if getter for additional field could not be found - * @param string $offset the column or additional field - * @return null|string|SimpleORMapCollection - */ - public function offsetGet($offset): mixed - { - return $this->getValue($offset); - } - - /** - * ArrayAccess: Set the value at the given offset. - * - * @throws InvalidArgumentException if column could not be found - * @throws BadMethodCallException if setter for additional field could not be found - * @param string $offset - * @param mixed $value - */ - public function offsetSet($offset, $value): void - { - $this->setValue($offset, $value); - } - - /** - * ArrayAccess: unset the value at the given offset (not applicable) - * - * @param string $offset - */ - public function offsetUnset($offset): void - { - - } - - /** - * IteratorAggregate - */ - public function getIterator(): ArrayIterator - { - return new ArrayIterator($this->toArray()); - } - - /** - * Countable - */ - public function count(): int - { - return count($this->known_slots()) - count($this->relations); - } - - /** - * check if given column exists in table - * @param string $field - * @return boolean - */ - function isField($field) - { - $field = strtolower($field); - return isset($this->db_fields()[$field]); - } - - /** - * check if given relation exists in this class - * @param string $field - * @return boolean - */ - function isRelation($field) - { - $field = strtolower($field); - return array_key_exists($field, $this->relations); - } - - /** - * check if given column is additional - * @param string $field - * @return boolean - */ - function isAdditionalField($field) - { - $field = strtolower($field); - return isset($this->additional_fields()[$field]); - } - - /** - * check if given column is an alias - * @param string $field - * @return boolean - */ - function isAliasField($field) - { - $field = strtolower($field); - return isset($this->alias_fields()[$field]); - } - - /** - * check if given column is a multi-language field - * @param string $field - * @return boolean - */ - function isI18nField($field) - { - $field = strtolower($field); - return isset($this->i18n_fields()[$field]); - } - - /** - * set multiple column values - * if second param is set, existing data in object will be - * discarded and dirty state is cleared, - * else new data overrides old data - * - * @param ?iterable $data assoc array - * @param ?boolean $reset existing data in object will be discarded - * @return int|bool number of columns changed - */ - function setData($data, $reset = false) - { - $count = 0; - if ($reset) { - if ($this->applyCallbacks('before_initialize') === false) { - return false; - } - $this->initializeContent(); - } - if (is_iterable($data)) { - foreach($data as $key => $value) { - $key = strtolower($key); - if (isset($this->db_fields()[$key]) - || isset($this->alias_fields()[$key]) - || isset($this->additional_fields()[$key]['set']) - ) { - $this->setValue($key, $value); - ++$count; - } - } - } - if ($reset) { - $this->applyCallbacks('after_initialize'); - } - return $count; - } - - /** - * check if object exists in database - * @return boolean - */ - function isNew() - { - return $this->is_new; - } - - /** - * check if object was deleted - * - * @return boolean - */ - function isDeleted() - { - return $this->is_deleted; - } - - /** - * set object to new state - * @param boolean $is_new - * @return boolean - */ - function setNew($is_new) - { - return $this->is_new = $is_new; - } - - /** - * returns sql clause with current table and pk - * @throws UnexpectedValueException if the primary key is incomplete - * @return boolean|array - */ - function getWhereQuery() - { - $where_query = null; - $pk_not_set = []; - foreach ($this->pk() as $key) { - $pk = $this->content_db[$key] ?? $this->content[$key] ?? null; - if (isset($pk)) { - $where_query[] = "`{$this->db_table()}`.`{$key}` = " . DBManager::get()->quote($pk); - } else { - $pk_not_set[] = $key; - } - } - if (!$where_query || count($pk_not_set)){ - if ($this->isNew()) { - return false; - } else { - throw new UnexpectedValueException(sprintf("primary key incomplete: %s must not be null", join(',',$pk_not_set))); - } - } - return $where_query; - } - - /** - * restore entry from database - * @return boolean - */ - function restore() - { - $where_query = $this->getWhereQuery(); - $id = $this->getId(); - if ($where_query) { - if ($this->applyCallbacks('before_initialize') === false) { - return false; - } - $this->initializeContent(); - $query = "SELECT * FROM `{$this->db_table()}` WHERE " - . join(" AND ", $where_query); - $st = DBManager::get()->prepare($query); - $st->execute(); - $st->setFetchMode(PDO::FETCH_INTO , $this); - if ($st->fetch()) { - $this->setNew(false); - $this->applyCallbacks('after_initialize'); - return true; - } - } - $this->setData([], true); - $this->setNew(true); - if (isset($id)) { - $this->setId($id); - } - return false; - } - - /** - * store entry in database - * - * @throws UnexpectedValueException if there are forbidden NULL values - * @return number|boolean - */ - function store() - { - // Set id or prepare setting of id - if ($this->isNew() && $this->getId() === null) { - // Explicitly set id to 0 if auto increment pk is null - if ($this->hasAutoIncrementColumn()) { - $this->setId(0); - } else { - $this->setId($this->getNewId()); - } - } - - if ($this->applyCallbacks('before_store') === false) { - return false; - } - - $ret = 0; - - if (!$this->isDeleted() && ($this->isDirty() || $this->isNew())) { - $callback = $this->isNew() ? 'before_create' : 'before_update'; - if ($this->applyCallbacks($callback) === false) { - return false; - } - - // Collect i18n contents - $i18ncontent = []; - foreach (array_keys($this->i18n_fields()) as $field) { - if ($this->content[$field] instanceof I18NString) { - $i18ncontent[$field] = $this->content[$field]; - $this->content[$field] = $this->content[$field]->original(); - $this->content_db[$field] = $this->content_db[$field]->original(); - } - } - - // Create sql data assignment chunks - foreach ($this->db_fields() as $field => $meta) { - $value = $this->content[$field]; - if ($field == 'chdate' && !$this->isFieldDirty($field) && $this->isDirty()) { - $value = time(); - } - if ($field == 'mkdate') { - if ($this->isNew()) { - if (!$this->isFieldDirty($field)) { - $value = time(); - } - } else { - continue; - } - } - if ($value === null && $meta['null'] == 'NO') { - throw new UnexpectedValueException($this->db_table() . '.' . $field . ' must not be null.'); - } - if (is_float($value)) { - $value = str_replace(',', '.', $value); - } - $this->content[$field] = $value; - $query_part[] = "`$field` = " . DBManager::get()->quote($value) . " "; - } - - // Create store query - if (!$this->isNew()) { - $where_query = $this->getWhereQuery(); - $query = "UPDATE `{$this->db_table()}` SET " - . implode(',', $query_part); - $query .= " WHERE " . join(" AND ", $where_query); - } else { - $query = "INSERT INTO `{$this->db_table()}` SET " - . implode(',', $query_part); - } - $ret = DBManager::get()->exec($query); - - // Retrieve generated id from database if pk is an auto increment - // column - if ($this->isNew()) { - if ($this->hasAutoIncrementColumn() && !$this->getId()) { - $this->setId(DBManager::get()->lastInsertId()); - } - } - - // Store i18n contents - foreach ($i18ncontent as $field => $one) { - $meta = [ - 'object_id' => $this->getId(), - 'table' => $this->db_table(), - 'field' => $field - ]; - $one->setMetadata($meta); - $one->storeTranslations(); - if (!$this->content[$field] instanceof I18NString) { - $this->content[$field] = $one; - $this->content_db[$field] = clone $one; - } - } - - // Apply callbacks - $this->applyCallbacks($this->isNew() ? 'after_create' : 'after_update'); - } - $rel_ret = $this->storeRelations(); - - $this->applyCallbacks('after_store'); - - if ($ret || $rel_ret) { - $this->restore(); - } - return $ret + $rel_ret; - } - - /** - * sends a store message to all initialized related objects - * if a relation has a callback for 'on_store' configured, the callback - * is instead invoked - * - * @param null|array|string $only_these - * @return int|false number addition of all return values, false if none was called - */ - protected function storeRelations($only_these = null) - { - $ret = false; - if (is_string($only_these)) { - $only_these = words($only_these); - } - $relations = array_keys($this->relations); - if (is_array($only_these)) { - $only_these = array_filter(array_map(function ($s) { - return is_string($s) ? strtolower($s) : null; - }, $only_these)); - $relations = array_intersect($only_these, $relations); - } - foreach ($relations as $relation) { - $options = $this->getRelationOptions($relation); - if (isset($options['on_store']) && - ($options['type'] === 'has_one' || - $options['type'] === 'has_many' || - $options['type'] === 'has_and_belongs_to_many')) { - if ($options['on_store'] instanceof Closure) { - $ret += call_user_func($options['on_store'], $this, $relation); - } elseif (isset($this->relations[$relation])) { - $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); - if ($options['type'] === 'has_one') { - call_user_func($options['assoc_foreign_key_setter'], $this->{$relation}, $foreign_key_value); - $ret = call_user_func([$this->{$relation}, 'store']); - } elseif ($options['type'] === 'has_many') { - foreach ($this->{$relation} as $r) { - call_user_func($options['assoc_foreign_key_setter'], $r, $foreign_key_value); - } - $ret += array_sum(call_user_func([$this->{$relation}, 'sendMessage'], 'store')); - $ret += array_sum(call_user_func([$this->{$relation}->getDeleted(), 'sendMessage'], 'delete')); - } else { - call_user_func([$this->{$relation}, 'sendMessage'], 'store'); - $to_delete = array_filter($this->{$relation}->getDeleted()->pluck($options['assoc_foreign_key'])); - $to_insert = array_filter($this->{$relation}->pluck($options['assoc_foreign_key'])); - $sql = "DELETE FROM `" . $options['thru_table'] ."` WHERE `" . $options['thru_key'] ."` = ? AND `" . $options['thru_assoc_key'] . "` = ?"; - $st = DBManager::get()->prepare($sql); - foreach ($to_delete as $one_value) { - $st->execute([$foreign_key_value, $one_value]); - $ret += $st->rowCount(); - } - $sql = "INSERT IGNORE INTO `" . $options['thru_table'] ."` SET `" . $options['thru_key'] ."` = ?, `" . $options['thru_assoc_key'] . "` = ?"; - $st = DBManager::get()->prepare($sql); - foreach ($to_insert as $one_value) { - $st->execute([$foreign_key_value, $one_value]); - $ret += $st->rowCount(); - } - } - } - } - } - return $ret; - } - - /** - * set chdate column to current timestamp - * @return boolean - */ - function triggerChdate() - { - if ($this->db_fields()['chdate']) { - $this->content['chdate'] = time(); - if ($where_query = $this->getWhereQuery()) { - DBManager::get()->exec("UPDATE `{$this->db_table()}` SET chdate={$this->content['chdate']} - WHERE ". join(" AND ", $where_query)); - return true; - } - } - - return false; - } - - /** - * delete entry from database - * the object is cleared, but is not(!) turned to new state - * @return bool|int number of deleted rows - */ - function delete() - { - $ret = false; - if (!$this->isDeleted() && !$this->isNew()) { - if ($this->applyCallbacks('before_delete') === false) { - return false; - } - $ret = $this->deleteRelations(); - $where_query = $this->getWhereQuery(); - if ($where_query) { - $query = "DELETE FROM `{$this->db_table()}` WHERE " - . join(" AND ", $where_query); - $ret += DBManager::get()->exec($query); - } - $this->is_deleted = true; - $this->applyCallbacks('after_delete'); - - // Remove i18n translations - if (I18N::isEnabled()) { - foreach (array_keys($this->i18n_fields()) as $field) { - if ($this->content[$field] instanceof I18NString) { - $this->content[$field]->removeTranslations(); - } - } - } - } - $this->setData([], true); - return $ret; - } - - /** - * sends a delete message to all related objects - * if a relation has a callback for 'on_delete' configured, the callback - * is invoked instead - * - * @return bool|int addition of all return values, false if none was called - */ - protected function deleteRelations() - { - $ret = false; - foreach (array_keys($this->relations) as $relation) { - $options = $this->getRelationOptions($relation); - if (isset($options['on_delete']) && - ($options['type'] === 'has_one' || - $options['type'] === 'has_many' || - $options['type'] === 'has_and_belongs_to_many')) { - if ($options['on_delete'] instanceof Closure) { - $ret += call_user_func($options['on_delete'], $this, $relation); - } else { - if ($options['type'] === 'has_one' || $options['type'] === 'has_many') { - $this->initRelation($relation); - if (isset($this->relations[$relation])) { - if ($options['type'] === 'has_one') { - $ret += call_user_func([$this->{$relation}, 'delete']); - } elseif ($options['type'] === 'has_many') { - $ret += array_sum(call_user_func([$this->{$relation}, 'sendMessage'], 'delete')); - } - } - } else { - $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); - $sql = "DELETE FROM `" . $options['thru_table'] ."` WHERE `" . $options['thru_key'] ."` = ?"; - $st = DBManager::get()->prepare($sql); - $st->execute([$foreign_key_value]); - $ret += $st->rowCount(); - } - } - $this->relations[$relation] = null; - } - } - return $ret; - } - - /** - * init internal content arrays with nulls or defaults - * - * @throws UnexpectedValueException if there is an unmatched alias - * @return void - */ - protected function initializeContent() - { - $this->content = []; - foreach (array_keys($this->db_fields()) as $field) { - $this->content[$field] = null; - $this->content_db[$field] = null; - $this->setValue($field, $this->getDefaultValue($field)); - } - foreach ($this->alias_fields() as $alias => $field) { - if (isset($this->db_fields()[$field])) { - $this->content[$alias] =& $this->content[$field]; - $this->content_db[$alias] =& $this->content_db[$field]; - } else { - throw new UnexpectedValueException(sprintf('Column %s not found for alias %s', $field, $alias)); - } - } - foreach (array_keys($this->relations) as $one) { - $this->relations[$one] = null; - } - $this->additional_data = []; - } - - /** - * checks if at least one field was modified since last restore - * - * @return boolean - */ - public function isDirty() - { - foreach (array_keys($this->db_fields()) as $field) { - if ($this->isFieldDirty($field)) { - return true; - } - } - return false; - } - - /** - * checks if given field was modified since last restore - * - * @param string $field - * @return boolean - */ - public function isFieldDirty($field) - { - $field = strtolower($field); - if ($this->content[$field] === null || $this->content_db[$field] === null) { - return $this->content[$field] !== $this->content_db[$field]; - } else if ($this->content[$field] instanceof I18NString || $this->content_db[$field] instanceof I18NString) { - return $this->content[$field] != $this->content_db[$field]; - } else { - return (string)$this->content[$field] !== (string)$this->content_db[$field]; - } - } - - /** - * reverts value of given field to last restored value - * - * @param string $field - * @return mixed the restored value - */ - public function revertValue($field) - { - $field = strtolower($field); - return ($this->content[$field] = $this->content_db[$field]); - } - - /** - * returns unmodified value of given field - * - * @param string $field - * @throws InvalidArgumentException - * @return mixed - */ - public function getPristineValue($field) - { - $field = strtolower($field); - if (array_key_exists($field, $this->content_db)) { - return $this->content_db[$field]; - } else { - throw new InvalidArgumentException(get_class($this) . '::'. $field . ' not found.'); - } - } - - /** - * intitalize a relationship and get related record(s) - * - * @param string $relation name of relation - * @throws InvalidArgumentException if the relation does not exists - * @return void - */ - public function initRelation($relation) - { - if (!array_key_exists($relation, $this->relations)) { - throw new InvalidArgumentException('Unknown relation: ' . $relation); - } - if ($this->relations[$relation] === null) { - $options = $this->getRelationOptions($relation); - $to_call = [$options['class_name'], $options['assoc_func']]; - if (!is_callable($to_call)) { - throw new RuntimeException('assoc_func: ' . join('::', $to_call) . ' is not callable.' ); - } - $params = $options['assoc_func_params_func']; - if ($options['type'] === 'has_many') { - $records = function($record) use ($to_call, $params, $options) { - $p = (array)$params($record); - return call_user_func_array($to_call, array_merge(count($p) ? $p : [null], [$options['order_by'] ?? null])); - }; - $this->relations[$relation] = new SimpleORMapCollection($records, $options, $this); - } elseif ($options['type'] === 'has_and_belongs_to_many') { - $records = function($record) use ($to_call, $params, $options) {$p = (array)$params($record); return call_user_func_array($to_call, array_merge(count($p) ? $p : [null], [$options]));}; - $this->relations[$relation] = new SimpleORMapCollection($records, $options, $this); - } else { - $p = (array)$params($this); - $records = call_user_func_array($to_call, count($p) ? $p : [null]); - $result = is_array($records) ? ($records[0] ?? null) : $records; - $this->relations[$relation] = $result; - } - } - } - - /** - * clear data for a relationship - * - * @param string $relation name of relation - * @throws InvalidArgumentException if teh relation does not exists - * @return void - */ - public function resetRelation($relation) - { - if (!array_key_exists($relation, $this->relations)) { - throw new InvalidArgumentException('Unknown relation: ' . $relation); - } - $this->relations[$relation] = null; - } - - /** - * invoke registered callbacks for given type - * if one callback returns false the following will not - * be invoked - * - * @param string $type type of callback - * @return bool return value from last callback - */ - protected function applyCallbacks($type) - { - $ok = true; - foreach ($this->registered_callbacks()[$type] as $cb) { - if ($cb instanceof Closure) { - $function = $cb; - $params = [$this, $type, $cb]; - } else { - $function = [$this, $cb]; - $params = [$type]; - }; - $ok = call_user_func_array($function, $params); - if ($ok === false) { - break; - } - } - return $ok; - } - - /** - * register given callback for one or many possible callback types - * callback param could be a closure or method name of current class - * - * @param string|array $types types to register callback for - * @param callable $cb callback - * @throws InvalidArgumentException if the callback type is not known - * @return number of registered callbacks - */ - protected static function registerCallback($types, $cb) - { - trigger_error(__METHOD__ . ' is deprecated. Please use the configuration in configure().', E_USER_DEPRECATED); - - $types = is_array($types) ? $types : words($types); - $reg = 0; - foreach ($types as $type) { - if (isset(static::registered_callbacks()[$type])) { - $found = array_search($cb, self::$config[static::class]['registered_callbacks'][$type], true); - if ($found === false) { - self::$config[static::class]['registered_callbacks'][$type][] = $cb; - $reg++; - } - } else { - throw new InvalidArgumentException('Unknown callback type: ' . $type); - } - } - return $reg; - } - - /** - * unregister given callback for one or many possible callback types - * - * @param string|array $types types to unregister callback for - * @param mixed $cb - * @throws InvalidArgumentException if the callback type is not known - * @return number of unregistered callbacks - */ - protected static function unregisterCallback($types, $cb) - { - trigger_error(__METHOD__ . ' is deprecated. Please use the configuration in configure().', E_USER_DEPRECATED); - - $types = is_array($types) ? $types : words($types); - foreach ($types as $type) { - if (isset(static::registered_callbacks()[$type])) { - $found = array_search($cb, self::$config[static::class]['registered_callbacks'][$type], true); - if ($found !== false) { - $unreg++; - unset(self::$config[static::class]['registered_callbacks'][$type][$found]); - } - } else { - throw new InvalidArgumentException('Unknown callback type: ' . $type); - } - } - return $unreg; - } - - /** - * default callback used to map specific callbacks to NotificationCenter - * - * @param string $cb_type callback type - * @return void|boolean - */ - protected function cbNotificationMapper($cb_type) - { - if (isset($this->notification_map()[$cb_type])) { - try { - foreach(words($this->notification_map()[$cb_type]) as $notification) { - NotificationCenter::postNotification($notification, $this); - } - } catch (NotificationVetoException $e) { - return false; - } - } - } - - /** - * default callback used to map specific callbacks to NotificationCenter - * - * @param string $cb_type callback type - * @return void|boolean - */ - protected function cbAfterInitialize($cb_type) - { - foreach (array_keys($this->db_fields()) as $field) { - if (is_object($this->content[$field])) { - $this->content_db[$field] = clone $this->content[$field]; - } else { - $this->content_db[$field] = $this->content[$field]; - } - } - } - - /** - * default setter used to proxy serialized fields with - * ArrayObjects - * - * @param string $field column name - * @param mixed $value value - * @return mixed - */ - protected function setSerializedValue($field, $value) - { - $object_type = $this->serialized_fields()[$field]; - if (is_null($value) || $value instanceof $object_type) { - $this->content[$field] = $value; - } else { - $this->content[$field] = new $object_type($value); - } - return $this->content[$field]; - } - - /** - * default setter used to proxy I18N fields with - * I18NString - * - * @param string $field column name - * @param mixed $value value - * @return mixed - */ - protected function setI18nValue($field, $value) - { - $meta = ['object_id' => $this->getId(), - 'table' => $this->db_table(), - 'field' => $field]; - if ($value instanceof I18NString) { - $value->setMetadata($meta); - $this->content[$field] = $value; - } else { - $this->content[$field] = new I18NString($value, null, $meta); - } - return $this->content[$field]; - } - - /** - * Cleans up this object. This essentially reset all relations of - * this object and marks them as unused so that the garbage collector may - * remove the objects. - * - * Use this function when you ran into memory problems and need to free - * some memory; - * - * @return void - */ - public function cleanup() - { - foreach ($this->relations as $relation => $object) { - if ($object instanceof SimpleORMap || $object instanceof SimpleORMapCollection) { - $this->relations[$relation]->cleanup(); - } - $this->resetRelation($relation); - } - } -} diff --git a/lib/classes/SimpleORMap.php b/lib/classes/SimpleORMap.php new file mode 100644 index 0000000..7124cc4 --- /dev/null +++ b/lib/classes/SimpleORMap.php @@ -0,0 +1,2483 @@ + + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate +{ + /** + * Defines `_` as character used when joining composite primary keys. + */ + const ID_SEPARATOR = '_'; + + /** + * table row data + * @var array $content + */ + protected $content = []; + + /** + * table row data + * @var array $content_db + */ + protected $content_db = []; + + /** + * new state of entry + * @var boolean $is_new + */ + protected $is_new = true; + + /** + * deleted state of entry + * @var boolean $is_deleted + */ + protected $is_deleted = false; + + /** + * db table metadata + * @var ?array $schemes; + */ + public static $schemes = null; + + /** + * configuration data for subclasses + * @see self::configure() + * @var array $config; + */ + protected static $config = []; + + /** + * stores instantiated related objects + * @var array $relations + */ + protected $relations = []; + + /** + * assoc array for storing values for additional fields + * + * @var array $additional_data + */ + protected $additional_data = []; + + /** + * reserved indentifiers, fields with those names must not have an explicit getXXX() method + * @var array $reserved_slots + */ + protected static $reserved_slots = ['value','newid','iterator','tablemetadata', 'relationvalue','wherequery','relationoptions','data','new','id']; + + /** + * indicator for batch operations in findEachBySQL + * + * @var bool $performs_batch_operation + */ + protected static $performs_batch_operation = false; + + /** + * name of db table + * @return string + */ + protected static function db_table() + { + return static::config('db_table'); + } + + /** + * table columns + * @return array + */ + protected static function db_fields() + { + return static::config('db_fields'); + } + + /** + * primary key columns + * @return array + */ + protected static function pk() + { + return static::config('pk'); + } + + /** + * default values for columns + * @return array + */ + protected static function default_values() + { + return static::config('default_values'); + } + + /** + * list of columns to deserialize + * @return array key is name of column, value is name of ArrayObject class + */ + protected static function serialized_fields() + { + return static::config('serialized_fields'); + } + + /** + * aliases for columns + * alias => column + * @return array + */ + protected static function alias_fields() + { + return static::config('alias_fields'); + } + + /** + * multi-language fields + * name => boolean + * @return array + */ + protected static function i18n_fields() + { + return static::config('i18n_fields'); + } + + /** + * additional computed fields + * name => callable + * @return array + */ + protected static function additional_fields() + { + return static::config('additional_fields'); + } + + /** + * 1:n relation + * @return array + */ + protected static function has_many() + { + return static::config('has_many'); + } + + /** + * 1:1 relation + * @return array + */ + protected static function has_one() + { + return static::config('has_one'); + } + + /** + * n:1 relations + * @return array + */ + protected static function belongs_to() + { + return static::config('belongs_to'); + } + + /** + * n:m relations + * @return array + */ + protected static function has_and_belongs_to_many() + { + return static::config('has_and_belongs_to_many'); + } + + /** + * callbacks + * @return array> + */ + protected static function registered_callbacks() + { + return static::config('registered_callbacks'); + } + + /** + * contains an array of all used identifiers for fields + * (db columns + aliased columns + additional columns + relations) + * @return array + */ + protected static function known_slots() + { + return static::config('known_slots'); + } + + /** + * assoc array used to map SORM callback to NotificationCenter + * keys are SORM callbacks, values notifications + * eg. 'after_create' => 'FooDidCreate' + * + * @return array + */ + protected static function notification_map() + { + return static::config('notification_map'); + } + + /** + * assoc array for mapping get/set Methods + * + * @return array + */ + protected static function getter_setter_map() + { + return static::config('getter_setter_map'); + } + + ////////////////////////////////////////////////// + + /** + * set configuration data from subclass + * + * @param ?array $config configuration data + * @return void + */ + protected static function configure($config = []) + { + $class = static::class; + + if (empty($config['db_table'])) { + $config['db_table'] = strtolower($class); + } + + if (!isset($config['db_fields'])) { + if (static::tableScheme($config['db_table'])) { + $config['db_fields'] = self::$schemes[$config['db_table']]['db_fields']; + $config['pk'] = self::$schemes[$config['db_table']]['pk']; + } + } + + if (isset($config['pk']) + && !isset($config['db_fields']['id']) + && !isset($config['alias_fields']['id']) + && !isset($config['additional_fields']['id']) + ) { + if (count($config['pk']) === 1) { + $config['alias_fields']['id'] = $config['pk'][0]; + } else { + $config['additional_fields']['id'] = ['get' => '_getId', + 'set' => '_setId']; + } + } + if (isset($config['additional_fields'])) { + foreach ($config['additional_fields'] as $a_field => $a_config) { + if (is_array($a_config) && !(isset($a_config['get']) || isset($a_config['set']))) { + $relation = $a_config[0] ?? ''; + $relation_field = $a_config[1] ?? ''; + if (!$relation) { + [$relation, $relation_field] = explode('_', $a_field); + } + if (!$relation_field || !$relation) { + throw new UnexpectedValueException('no relation found for autoget/set additional field: ' . $a_field); + } + $config['additional_fields'][$a_field] = ['get' => '_getAdditionalValueFromRelation', + 'set' => '_setAdditionalValue', + 'relation' => $relation, + 'relation_field' => $relation_field]; + } + } + } + if (isset($config['serialized_fields'])) { + foreach ($config['serialized_fields'] as $a_field => $object_type) { + if (!(is_subclass_of($object_type, 'StudipArrayObject'))) { + throw new UnexpectedValueException(sprintf('serialized field %s must use subclass of StudipArrayObject', $a_field)); + } + } + } + + foreach (['default_values', 'serialized_fields', 'alias_fields', 'i18n_fields', 'additional_fields'] as $fields) { + if (!isset($config[$fields])) { + $config[$fields] = []; + } + } + + foreach (['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { + if (isset($config[$type]) && is_array($config[$type])) { + foreach (array_keys($config[$type]) as $one) { + $config['relations'][$one] = null; + } + } else { + $config[$type] = []; + } + } + + $callbacks = ['before_create', + 'before_update', + 'before_store', + 'before_delete', + 'before_initialize', + 'after_create', + 'after_update', + 'after_store', + 'after_delete', + 'after_initialize']; + + foreach ($callbacks as $callback) { + if (!isset($config['registered_callbacks'][$callback])) { + $config['registered_callbacks'][$callback] = []; + } + } + + $auto_notification_map['after_create'] = $class . 'DidCreate'; + $auto_notification_map['after_store'] = $class . 'DidStore'; + $auto_notification_map['after_delete'] = $class . 'DidDelete'; + $auto_notification_map['after_update'] = $class . 'DidUpdate'; + $auto_notification_map['before_create'] = $class . 'WillCreate'; + $auto_notification_map['before_store'] = $class . 'WillStore'; + $auto_notification_map['before_delete'] = $class . 'WillDelete'; + $auto_notification_map['before_update'] = $class . 'WillUpdate'; + + foreach ($auto_notification_map as $cb => $notification) { + if (isset($config['notification_map'][$cb])) { + if (strpos($config['notification_map'][$cb], $notification) !== false) { + $config['notification_map'][$cb] .= ' ' . $notification; + } + } else { + $config['notification_map'][$cb] = $notification; + } + } + + if (is_array($config['notification_map'])) { + foreach (array_keys($config['notification_map']) as $cb) { + $config['registered_callbacks'][$cb][] = 'cbNotificationMapper'; + } + } + + if (!I18N::isEnabled() || empty($config['i18n_fields'])) { + $config['i18n_fields'] = []; + } elseif (is_string($config['i18n_fields'])) { + $i18n_fields = words($config['i18n_fields']); + $config['i18n_fields'] = array_combine( + $i18n_fields, + array_fill(0, count($i18n_fields), true) + ); + } elseif (array_is_list($config['i18n_fields'])) { + $config['i18n_fields'] = array_combine( + $config['i18n_fields'], + array_fill(0, count($config['i18n_fields']), true) + ); + } + + array_unshift($config['registered_callbacks']['after_initialize'], 'cbAfterInitialize'); + + $config['known_slots'] = array_merge( + array_keys($config['db_fields']), + array_keys($config['alias_fields'] ?? []), + array_keys($config['additional_fields'] ?? []), + array_keys($config['relations'] ?? []) + ); + + foreach (array_map('strtolower', get_class_methods($class)) as $method) { + if (in_array(substr($method, 0, 3), ['get', 'set'])) { + $verb = substr($method, 0, 3); + $name = substr($method, 3); + if (in_array($name, $config['known_slots']) && !in_array($name, static::$reserved_slots) && !isset($config['additional_fields'][$name][$verb])) { + $config['getter_setter_map'][$name][$verb] = $method; + } + } + } + self::$config[$class] = $config; + } + + /** + * fetch config data for the called class + * + * @param string $key config key + * @return mixed value of config key (null if not set) + */ + protected static function config($key) + { + if (!array_key_exists(static::class, self::$config)) { + static::configure(); + } + + return self::$config[static::class][$key] ?? null; + } + + /** + * fetch table metadata from db or from local cache + * + * @param string $db_table + * @return bool true if metadata could be fetched + */ + public static function tableScheme($db_table) + { + if (self::$schemes === null) { + $cache = \Studip\Cache\Factory::getCache(); + self::$schemes = unserialize($cache->read('DB_TABLE_SCHEMES')); + } + if (!isset(self::$schemes[$db_table])) { + $db = DBManager::get()->query("SHOW COLUMNS FROM $db_table"); + while($rs = $db->fetch(PDO::FETCH_ASSOC)){ + $db_fields[strtolower($rs['Field'])] = [ + 'name' => $rs['Field'], + 'null' => $rs['Null'], + 'default' => $rs['Default'], + 'type' => $rs['Type'], + 'extra' => $rs['Extra'] + ]; + if ($rs['Key'] == 'PRI'){ + $pk[] = strtolower($rs['Field']); + } + } + self::$schemes[$db_table]['db_fields'] = $db_fields; + self::$schemes[$db_table]['pk'] = $pk; + $cache = \Studip\Cache\Factory::getCache(); + $cache->write('DB_TABLE_SCHEMES', serialize(self::$schemes)); + } + return isset(self::$schemes[$db_table]); + } + + /** + * force reload of cached table metadata + * @return void + */ + public static function expireTableScheme() + { + \Studip\Cache\Factory::getCache()->expire('DB_TABLE_SCHEMES'); + self::$schemes = null; + self::$config = []; + } + + /** + * Returns new instance for given key when found in the database, else null. + * + * @param string|array $id primary key + * @return static|null + */ + public static function find($id) + { + $ref = new ReflectionClass(static::class); + /** @var static $record */ + $record = $ref->newInstanceArgs(func_get_args()); + if (!$record->isNew()) { + return $record; + } else { + return null; + } + } + + /** + * Returns true if given key exists in the database. + * + * @param string|array $id primary key + * @return boolean + */ + public static function exists($id) + { + $ret = false; + $db_table = static::db_table(); + $record = new static(); + $record->setId(...func_get_args()); + $where_query = $record->getWhereQuery(); + if ($where_query) { + $query = "SELECT 1 FROM `$db_table` WHERE " + . join(" AND ", $where_query); + $ret = (bool)DBManager::get()->query($query)->fetchColumn(); + } + return $ret; + } + + /** + * returns number of records + * + * @param ?string $sql sql clause to use on the right side of WHERE + * @param ?array $params params for query + * @return int + */ + public static function countBySql($sql = '1', $params = []) + { + $db_table = static::db_table(); + $db = DBManager::get(); + $has_join = stripos($sql, 'JOIN '); + if ($has_join === false || $has_join > 10) { + $sql = 'WHERE ' . $sql; + } + $sql = "SELECT count(*) FROM `" . $db_table . "` " . $sql; + $st = $db->prepare($sql); + $st->execute($params); + return (int)$st->fetchColumn(); + } + + /** + * creates new record with given data in db + * returns the new object or null + * @param array $data assoc array of record + * @return ?static + */ + public static function create($data) + { + $record = new static(); + $record->setData($data, false); + if ($record->store()) { + return $record; + } else { + return null; + } + } + + /** + * build object with given data + * + * @param array $data assoc array of record + * @param ?bool $is_new set object to new state + * @return static + */ + public static function build($data, $is_new = true) + { + $record = new static(); + $record->setData($data, !$is_new); + $record->setNew($is_new); + return $record; + } + + /** + * build object with given data and mark it as existing + * + * @param array $data assoc array of record + * @return static + */ + public static function buildExisting($data) + { + return static::build($data, false); + } + + /** + * generate SimpleORMap object structure from assoc array + * if given array contains data of related objects in sub-arrays + * they are also generated. Existing records are updated, new records are created + * (but changes are not yet stored) + * + * @param array $data + * @return static + */ + public static function import($data) + { + $record_data = []; + $relation_data = []; + foreach ($data as $key => $value) { + $temp = static::alias_fields()[$key] ?? $key; + if (isset(static::db_fields()[$temp])) { + $record_data[$key] = $value; + } else { + $relation_data[$key] = $value; + } + } + $record = static::toObject($record_data); + if (!$record instanceof static) { + $record = new static(); + $record->setData($record_data, true); + } else { + $record->setData($record_data); + } + foreach ($relation_data as $relation => $data) { + if (!$record->isRelation($relation)) { + continue; + } + + $options = $record->getRelationOptions($relation); + if ($options['type'] == 'has_one') { + $record->{$relation} = call_user_func([$options['class_name'], 'import'], $data); + } + if ($options['type'] == 'has_many' || $options['type'] == 'has_and_belongs_to_many') { + foreach ($data as $one) { + $current = call_user_func([$options['class_name'], 'import'], $one); + if ($options['type'] == 'has_many') { + $foreign_key_value = call_user_func($options['assoc_func_params_func'], $record); + call_user_func($options['assoc_foreign_key_setter'], $current, $foreign_key_value); + } + if ($current->id !== null) { + $existing = $record->{$relation}->find($current->id); + if ($existing) { + $existing->setData($current); + } else { + $record->{$relation}->append($current); + } + } else { + $record->{$relation}->append($current); + } + } + } + } + return $record; + } + + /** + * returns array of instances of given class filtered by given sql + * @param string $sql sql clause to use on the right side of WHERE + * @param ?array $params parameters for query + * @return static[] array of "self" objects + */ + public static function findBySQL($sql, $params = []) + { + $db_table = static::db_table(); + + $has_join = stripos($sql, 'JOIN '); + if ($has_join === false || $has_join > 10) { + $sql = 'WHERE ' . $sql; + } + $sql = "SELECT `" . $db_table . "`.* FROM `" . $db_table . "` " . $sql; + $stmt = DBManager::get()->prepare($sql); + $stmt->execute($params); + + $record = static::build([], false); + + $ret = []; + do { + $clone = clone $record; + $clone->setNew(false); + $stmt->setFetchMode(PDO::FETCH_INTO, $clone); + + if ($clone = $stmt->fetch()) { + $clone->applyCallbacks('after_initialize'); + $ret[] = $clone; + } + } while ($clone); + return $ret; + } + + /** + * returns one instance of given class filtered by given sql + * only first row of query is used + * @param string $where sql clause to use on the right side of WHERE + * @param ?array $params parameters for query + * @return ?static + */ + public static function findOneBySQL($where, $params = []) + { + if (stripos($where, 'LIMIT') === false) { + $where .= " LIMIT 1"; + } + $found = static::findBySQL($where, $params); + return isset($found[0]) ? $found[0] : null; + } + + /** + * find related records for a n:m relation (has_many_and_belongs_to_many) + * using a combination table holding the keys + * + * @param string $foreign_key_value value of foreign key to find related records + * @param array $options relation options from other side of relation + * @return static[] array of "self" objects + */ + public static function findThru($foreign_key_value, $options) + { + $thru_table = $options['thru_table']; + $thru_key = $options['thru_key']; + $thru_assoc_key = $options['thru_assoc_key']; + $assoc_foreign_key = $options['assoc_foreign_key']; + + $db_table = static::db_table(); + + $sql = "SELECT `$db_table`.* FROM `$thru_table` + INNER JOIN `$db_table` ON `$thru_table`.`$thru_assoc_key` = `$db_table`.`$assoc_foreign_key` + WHERE `$thru_table`.`$thru_key` = ? " . ($options['order_by'] ?? ''); + $st = DBManager::get()->prepare($sql); + $st->execute([$foreign_key_value]); + + $record = static::build([], false); + + $ret = []; + do { + $clone = clone $record; + $clone->setNew(false); + $st->setFetchMode(PDO::FETCH_INTO, $clone); + + if ($clone = $st->fetch()) { + $clone->applyCallbacks('after_initialize'); + $ret[] = $clone; + } + } while ($clone); + return $ret; + } + + /** + * passes objects for given sql through given callback + * + * @param callable $callable callback which gets the current record as param + * @param string $sql where clause of sql + * @param ?array $params sql statement parameters + * @return integer number of found records + */ + public static function findEachBySQL($callable, $sql, $params = []) + { + $has_join = stripos($sql, 'JOIN '); + if ($has_join === false || $has_join > 10) { + $sql = "WHERE {$sql}"; + } + + $db_table = static::db_table(); + $st = DBManager::get()->prepare("SELECT `{$db_table}`.* FROM `{$db_table}` {$sql}"); + $st->execute($params); + + // Indicate that we are performing a batch operation + static::$performs_batch_operation = true; + + $record = static::build([], false); + + $ret = 0; + do { + $clone = clone $record; + $clone->setNew(false); + $st->setFetchMode(PDO::FETCH_INTO, $clone); + + if ($clone = $st->fetch()) { + $clone->applyCallbacks('after_initialize'); + $callable($clone, $ret++); + } + } while ($clone); + + // Reset batch operation indicator + static::$performs_batch_operation = false; + + return $ret; + } + + /** + * returns array of instances of given class for by given pks + * @param ?array $pks array of primary keys + * @param ?string $order order by clause + * @param ?array $order_params + * @return static[] + */ + public static function findMany($pks = [], $order = '', $order_params = []) + { + $db_table = static::db_table(); + $pk = static::pk(); + $db = DBManager::get(); + if (count($pk) > 1) { + throw new Exception('not implemented yet'); + } + $where = "`$db_table`.`{$pk[0]}` IN (" . $db->quote($pks) . ") "; + return static::findBySQL($where . $order, $order_params); + } + + /** + * passes objects for by given pks through given callback + * + * @param callable $callable callback which gets the current record as param + * @param ?array $pks array of primary keys of called class + * @param ?string $order order by sql + * @param ?array $order_params + * @return integer number of found records + */ + public static function findEachMany($callable, $pks = [], $order = '', $order_params = []) + { + $db_table = static::db_table(); + $pk = static::pk(); + $db = DBManager::get(); + if (count($pk) > 1) { + throw new Exception('not implemented yet'); + } + $where = "`$db_table`.`{$pk[0]}` IN (" . $db->quote($pks) . ") "; + return static::findEachBySQL($callable, $where . $order, $order_params); + } + + /** + * passes objects for given sql through given callback + * and returns an array of callback return values + * + * @param callable $callable callback which gets the current record as param + * @param string $where where clause of sql + * @param array $params sql statement parameters + * @return array return values of callback + */ + public static function findAndMapBySQL($callable, $where, $params = []) + { + $ret = []; + $calleach = function($m) use (&$ret, $callable) { + $ret[] = $callable($m); + }; + static::findEachBySQL($calleach, $where, $params); + return $ret; + } + + /** + * passes objects for by given pks through given callback + * and returns an array of callback return values + * + * @param callable $callable callback which gets the current record as param + * @param ?array $pks array of primary keys of called class + * @param ?string $order order by sql + * @param ?array $order_params + * @return array return values of callback + */ + public static function findAndMapMany($callable, $pks = [], $order = '', $order_params = []) + { + $ret = []; + $calleach = function($m) use (&$ret, $callable) { + $ret[] = $callable($m); + }; + $db_table = static::db_table(); + $pk = static::pk(); + $db = DBManager::get(); + if (count($pk) > 1) { + throw new Exception('not implemented yet'); + } + $where = "`$db_table`.`{$pk[0]}` IN (" . $db->quote($pks) . ") "; + static::findEachBySQL($calleach, $where . $order, $order_params); + return $ret; + } + + /** + * deletes objects specified by sql clause + * @param string $where sql clause to use on the right side of WHERE + * @param ?array $params parameters for query + * @return integer number of deleted records + */ + public static function deleteBySQL($where, $params = []) + { + $killeach = function($record) {$record->delete();}; + return static::findEachBySQL($killeach, $where, $params); + } + + /** + * returns object of given class for given id or null + * the param could be a string, an assoc array containing primary key field + * or an already matching object. In all these cases an object is returned + * + * @param string|static|array $id_or_object id as string, object or assoc array + * @return static + */ + public static function toObject($id_or_object) + { + if ($id_or_object instanceof static) { + return $id_or_object; + } + if (is_array($id_or_object)) { + $pk = static::pk(); + $key_values = []; + foreach ($pk as $key) { + if (array_key_exists($key, $id_or_object)) { + $key_values[] = $id_or_object[$key]; + } + } + if (count($pk) === count($key_values)) { + if (count($pk) === 1) { + $id = $key_values[0]; + } else { + $id = $key_values; + } + } else { + $id = null; + } + } else { + $id = $id_or_object; + } + return static::find($id); + } + + /** + * interceptor for static findByColumn / findEachByColumn / countByColumn / + * deleteByColumn magic + * + * @param string $name + * @param array $arguments + * @throws BadMethodCallException + * @return int|static|static[] + */ + public static function __callStatic(string $name, array $arguments) + { + $db_table = static::db_table(); + $alias_fields = static::alias_fields(); + $db_fields = static::db_fields(); + $name = strtolower($name); + $order = ''; + $param_arr = []; + $where = ''; + $where_param = is_array($arguments[0]) ? $arguments[0] : [$arguments[0]]; + $action = strstr($name, 'by', true); + $field = substr($name, strlen($action) + 2); + switch ($action) { + case 'findone': + $order = $arguments[1] ?? ''; + $param_arr[0] =& $where; + $param_arr[1] = [$where_param]; + $method = 'findonebysql'; + break; + case 'find': + case 'findmany': + $order = $arguments[1] ?? ''; + $param_arr[0] =& $where; + $param_arr[1] = [$where_param]; + $method = 'findbysql'; + break; + case 'findeach': + case 'findeachmany': + $order = $arguments[2] ?? ''; + $param_arr[0] = $arguments[0]; + $param_arr[1] =& $where; + $param_arr[2] = [$arguments[1]]; + $method = 'findeachbysql'; + break; + case 'count': + case 'delete': + $param_arr[0] =& $where; + $param_arr[1] = [$where_param]; + $method = "{$action}bysql"; + break; + default: + throw new BadMethodCallException("Method " . static::class . "::$name not found"); + } + if (isset($alias_fields[$field])) { + $field = $alias_fields[$field]; + } + if (isset($db_fields[$field])) { + $where = "`$db_table`.`$field` IN(?) " . $order; + return call_user_func_array([static::class, $method], $param_arr); + } + throw new BadMethodCallException("Method " . static::class . "::$name not found"); + } + + /** + * constructor, give primary key of record as param to fetch + * corresponding record from db if available, if not preset primary key + * with given value. Give null to create new record + * + * @param null|int|string|array $id primary key of table + */ + function __construct($id = null) + { + foreach(['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { + foreach (array_keys($this->$type()) as $one) { + $this->relations[$one] = null; + } + } + + if ($id) { + $this->setId($id); + } + $this->restore(); + } + + /** + * returns internal used id value (multiple keys concatenated with _) + * @param mixed $field unused parameter + * @return ?string + */ + protected function _getId($field) + { + return is_null($this->getId()) + ? null + : implode(self::ID_SEPARATOR, $this->getId()); + } + + /** + * sets internal used id value (multiple keys concatenated with _) + * @param string $field Field to set (unused since it's always the id) + * @param string $value Value for id field + * @return bool + */ + protected function _setId($field, $value) + { + return $this->setId(explode(self::ID_SEPARATOR, $value)); + } + + /** + * retrieves an additional field value from relation + * + * @param string $field + * @return mixed + */ + protected function _getAdditionalValueFromRelation($field) + { + [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], + $this->additional_fields()[$field]['relation_field']]; + if (!array_key_exists($field, $this->additional_data)) { + $this->_setAdditionalValue($field, $this->getRelationValue($relation, $relation_field)); + } + return $this->additional_data[$field]; + } + + /** + * sets additional value in field imported from relation + * + * @param string $field + * @param mixed $value + * @return mixed + */ + protected function _setAdditionalValueFromRelation($field, $value) + { + [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], + $this->additional_fields()[$field]['relation_field']]; + $this->$relation->$field = $value; + unset($this->additional_data[$field]); + return $this->_getAdditionalValueFromRelation($field); + } + + /** + * @param string $field + * @return mixed + */ + protected function _getAdditionalValue($field) + { + return $this->additional_data[$field]; + } + + /** + * @param string $field + * @param mixed $value + * @return mixed + */ + protected function _setAdditionalValue($field, $value) + { + return $this->additional_data[$field] = $value; + } + + /** + * clean up references after cloning + * @return void + */ + function __clone() + { + $this->setNew(true); + //all references link still to old object => reset all aliases + foreach ($this->alias_fields() as $alias => $field) { + if (isset($this->db_fields()[$field])) { + $content_value = $this->content[$field]; + $content_db_value = $this->content_db[$field]; + unset($this->content[$alias]); + unset($this->content_db[$alias]); + unset($this->content[$field]); + unset($this->content_db[$field]); + if (is_object($content_value)) { + $this->content[$field] = clone $content_value; + } else { + $this->content[$field] = $content_value; + } + if (is_object($content_db_value)) { + $this->content_db[$field] = clone $content_db_value; + } else { + $this->content_db[$field] = $content_db_value; + } + } + } + foreach ($this->alias_fields() as $alias => $field) { + if (isset($this->db_fields()[$field])) { + $this->content[$alias] =& $this->content[$field]; + $this->content_db[$alias] =& $this->content_db[$field]; + } + } + //unset all relations for now + //TODO: maybe a deep copy of all belonging objects is more appropriate + foreach(['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { + foreach (array_keys($this->$type()) as $one) { + $this->relations[$one] = null; + } + } + //begun the clone war has... hmpf + } + + /** + * try to determine all needed options for a relationship from + * configured options + * + * @param string $type + * @param string $name + * @param array $options + * @throws Exception if options for thru_table could not be determined + * @return array + */ + protected function parseRelationOptions($type, $name, $options) + { + if (empty($options['class_name'])) { + throw new Exception('Option class_name not set for relation ' . $name); + } + if (empty($options['assoc_foreign_key'])) { + if ($type === 'has_many' || $type === 'has_one') { + $options['assoc_foreign_key'] = $this->pk()[0]; + } else if ($type === 'belongs_to') { + $options['assoc_foreign_key'] = 'id'; + } + } + if ($type === 'has_and_belongs_to_many') { + $thru_table = $options['thru_table']; + if (empty($options['thru_key'])) { + $options['thru_key'] = $this->pk()[0]; + } + if (empty($options['thru_assoc_key']) || empty($options['assoc_foreign_key'])) { + $class = $options['class_name']; + $record = new $class(); + $meta = $record->getTableMetadata(); + if (empty($options['thru_assoc_key'])) { + $options['thru_assoc_key'] = $meta['pk'][0]; + } + if (empty($options['assoc_foreign_key'])) { + $options['assoc_foreign_key']= $meta['pk'][0]; + } + } + static::tableScheme($thru_table); + if (is_array(self::$schemes[$thru_table])) { + $thru_key_ok = isset(self::$schemes[$thru_table]['db_fields'][$options['thru_key']]); + $thru_assoc_key_ok = isset(self::$schemes[$thru_table]['db_fields'][$options['thru_assoc_key']]); + } + if (!$thru_assoc_key_ok || !$thru_key_ok) { + throw new Exception("Could not determine keys for relation " . $name . " through table " . $thru_table); + } + if ($options['assoc_foreign_key'] instanceof Closure) { + throw new Exception("For relation " . $name . " assoc_foreign_key must be a name of a column"); + } + } + if (empty($options['assoc_func'])) { + if ($type !== 'has_and_belongs_to_many') { + $options['assoc_func'] = $options['assoc_foreign_key'] === 'id' ? 'find' : 'findBy' . $options['assoc_foreign_key']; + } else { + $options['assoc_func'] = 'findThru'; + } + } + if (empty($options['foreign_key'])) { + $options['foreign_key'] = 'id'; + } + if (isset($options['foreign_key']) && $options['foreign_key'] instanceof Closure) { + $options['assoc_func_params_func'] = function($record) use ($name, $options) { return call_user_func($options['foreign_key'], $record, $name, $options);}; + } else { + $options['assoc_func_params_func'] = function($record) use ($name, $options) { return $options['foreign_key'] === 'id' ? $record->getId() : $record->getValue($options['foreign_key']);}; + } + if (isset($options['assoc_foreign_key']) && $options['assoc_foreign_key'] instanceof Closure) { + if ($type === 'belongs_to') { + $options['assoc_foreign_key_getter'] = function($record, $that) use ($name, $options) { return call_user_func($options['assoc_foreign_key'], $record, $name, $options, $that);}; + } else { + $options['assoc_foreign_key_setter'] = function($record, $params) use ($name, $options) { return call_user_func($options['assoc_foreign_key'], $record, $params, $name, $options);}; + } + } elseif (!empty($options['assoc_foreign_key'])) { + if ($type === 'belongs_to') { + $options['assoc_foreign_key_getter'] = function($record, $that) use ($name, $options) { return $record->getValue($options['assoc_foreign_key']);}; + } else { + $options['assoc_foreign_key_setter'] = function($record, $value) use ($name, $options) { return $record->setValue($options['assoc_foreign_key'], $value);}; + } + } else { + throw new Exception("Could not determine assoc_foreign_key for relation " . $name); + } + return $options; + } + + /** + * returns array with option for given relation + * available options: + * 'type': relation type, on of 'has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many' + * 'class_name': name of class for related records + * 'foreign_key': name of column with foreign key + * or callback to retrieve foreign key value + * 'assoc_foreign_key': name of foreign key column in related class + * 'assoc_func': name of static method to call on related class to find related records + * 'assoc_func_params_func': callback to retrieve params for assoc_func + * 'thru_table': name of relation table for n:m relation + * 'thru_key': name of column holding foreign key in relation table + * 'thru_assoc_key': name of column holding foreign key from related class in relation table + * 'on_delete': contains simply 'delete' to indicate that related records should be deleted + * or callback to invoke before record gets deleted + * 'on_store': contains simply 'store' to indicate that related records should be stored + * or callback to invoke after record gets stored + * + * @param string $relation name of relation + * @return array assoc array containing options + */ + function getRelationOptions($relation) + { + $options = []; + foreach(['has_many', 'belongs_to', 'has_one', 'has_and_belongs_to_many'] as $type) { + if (isset($this->$type()[$relation])) { + $options = self::$config[get_class($this)][$type][$relation]; + if (!isset($options['type'])) { + $options = $this->parseRelationOptions($type, $relation, $options, $this->db_table()); + $options['type'] = $type; + self::$config[get_class($this)][$type][$relation] = $options; + } + break; + } + } + return $options; + } + + /** + * returns table and columns metadata + * + * @return array assoc array with columns, primary keys and name of table + */ + function getTableMetadata() + { + return ['fields' => $this->db_fields(), + 'pk' => $this->pk(), + 'table' => $this->db_table(), + 'additional_fields' => $this->additional_fields(), + 'alias_fields' => $this->alias_fields(), + 'relations' => array_keys($this->relations)]; + } + + /** + * returns true, if table has an auto_increment column + * + * @return boolean + */ + function hasAutoIncrementColumn() + { + return $this->db_fields()[$this->pk()[0]]['extra'] == 'auto_increment'; + } + + /** + * set primary key for entry, combined keys must be passed as array + * @param int|string|array $id primary key + * @throws InvalidArgumentException if given key is not complete + * @return boolean + */ + public function setId($id) + { + if (!is_array($id)){ + $id = [$id]; + } + if (count($this->pk()) != count($id)){ + throw new InvalidArgumentException("Invalid ID, Primary Key {$this->db_table()} is " .join(",",$this->pk())); + } else { + foreach ($this->pk() as $count => $key){ + $this->content[$key] = $id[$count]; + } + return true; + } + } + + /** + * returns primary key, multiple keys as array + * @return null|string|array current primary key, null if not set + */ + function getId() + { + if (count($this->pk()) == 1) { + return $this->content[$this->pk()[0]] ?? null; + } else { + $id = []; + foreach ($this->pk() as $key) { + if (array_key_exists($key, $this->content)) { + $id[] = $this->content[$key]; + } + } + return (count($this->pk()) == count($id) ? $id : null); + } + } + + /** + * create new unique pk as md5 hash + * if pk consists of multiple columns, false is returned + * @return boolean|string + */ + function getNewId() + { + $id = false; + if (count($this->pk()) == 1) { + do { + $id = md5(uniqid($this->db_table(), 1)); + $db = DBManager::get()->query("SELECT `{$this->pk()[0]}` FROM `{$this->db_table()}` " + . "WHERE `{$this->pk()[0]}` = '$id'"); + } while($db->fetch()); + } + return $id; + } + + /** + * returns data of table row as assoc array + * pass array of fieldnames or ws separated string to limit + * fields + * + * @param null|array|string $only_these_fields limit returned fields + * @return array + */ + function toArray($only_these_fields = null) + { + $ret = []; + if (is_string($only_these_fields)) { + $only_these_fields = words($only_these_fields); + } + $fields = array_diff($this->known_slots(), array_keys($this->relations)); + if (is_array($only_these_fields)) { + $only_these_fields = array_filter(array_map(function($s) { + return is_string($s) ? strtolower($s) : null; + }, $only_these_fields)); + $fields = array_intersect($only_these_fields, $fields); + } + foreach ($fields as $field) { + $ret[$field] = $this->getValue($field); + if ($ret[$field] instanceof StudipArrayObject) { + $ret[$field] = $ret[$field]->getArrayCopy(); + } + } + return $ret; + } + + /** + * Returns data of table row as assoc array with raw contents like + * they are in the database. + * Pass array of fieldnames or ws separated string to limit + * fields. + * + * @param null|array|string $only_these_fields + * @return array + */ + function toRawArray($only_these_fields = null) + { + $ret = []; + if (is_string($only_these_fields)) { + $only_these_fields = words($only_these_fields); + } + $fields = array_keys($this->db_fields()); + if (is_array($only_these_fields)) { + $only_these_fields = array_filter(array_map(function ($s) { + return is_string($s) ? strtolower($s) : null; + }, $only_these_fields)); + $fields = array_intersect($only_these_fields, $fields); + } + foreach ($fields as $field) { + if ($this->content[$field] instanceof I18NString) { + $ret[$field] = $this->content[$field]->original(); + } elseif ($this->content[$field] === null) { + $ret[$field] = null; + } else { + $ret[$field] = (string)$this->content[$field]; + } + } + return $ret; + } + + /** + * returns data of table row as assoc array + * including related records with a 'has*' relationship + * recurses one level without param + * + * $only_these_fields limits output for relationships in this way: + * $only_these_fields = array('field_1', + * 'field_2', + * 'relation1', + * 'relation2' => array('rel2_f1', + * 'rel2_f2', + * 'rel2_rel11' => array( + * rel2_rel1_f1) + * ) + * ) + * Here all fields of relation1 will be returned. + * + * @param null|array|string $only_these_fields limit returned fields + * @return array + */ + function toArrayRecursive($only_these_fields = null) + { + if (is_string($only_these_fields)) { + $only_these_fields = words($only_these_fields); + } + if (is_null($only_these_fields)) { + $only_these_fields = $this->known_slots(); + } + $ret = $this->toArray($only_these_fields); + $relations = []; + if (is_array($only_these_fields)) { + foreach ($only_these_fields as $key => $value) { + if (!is_array($value) && + array_key_exists(strtolower($value), $this->relations) + ) { + $relations[strtolower($value)] = 0; //not null|array|string to stop recursion + } + if (array_key_exists(strtolower($key), $this->relations)) { + $relations[strtolower($key)] = $value; + } + } + } + if (count($relations)) { + foreach ($relations as $relation_name => $relation_only_these_fields) { + $options = $this->getRelationOptions($relation_name); + if ($options['type'] === 'has_one' || + $options['type'] === 'belongs_to') { + $ret[$relation_name] = + $this->{$relation_name}-> + toArrayRecursive($relation_only_these_fields); + } + if ($options['type'] === 'has_many' || + $options['type'] === 'has_and_belongs_to_many') { + $ret[$relation_name] = + $this->{$relation_name}-> + sendMessage('toArrayRecursive', + [$relation_only_these_fields]); + } + } + } + return $ret; + } + + /** + * returns value of a column + * + * @throws InvalidArgumentException if column could not be found + * @throws BadMethodCallException if getter for additional field could not be found + * @param string $field + * @return null|string|SimpleORMapCollection + */ + public function getValue($field) + { + $field = strtolower($field); + + // No value defined, throw exception + if (!in_array($field, $this->known_slots())) { + throw new InvalidArgumentException(static::class . '::'.$field . ' not found.'); + } + + // Get value by getter + if (isset($this->getter_setter_map()[$field]['get'])) { + return call_user_func([$this, $this->getter_setter_map()[$field]['get']]); + } + + // Get value from content + if (array_key_exists($field, $this->content)) { + return $this->content[$field]; + } + + // Get value from relation + if (array_key_exists($field, $this->relations)) { + $this->initRelation($field); + return $this->relations[$field]; + } + + // Get value from additional_field + if (isset($this->additional_fields()[$field]['get'])) { + // Getter is defined as a closure + if ($this->additional_fields()[$field]['get'] instanceof Closure) { + return call_user_func_array($this->additional_fields()[$field]['get'], [$this, $field]); + } + + // Getter is defined as a method of this object + return call_user_func([$this, $this->additional_fields()[$field]['get']], $field); + } + + // No value found, throw exception + throw new RuntimeException('No value could be found for ' . static::class . '::' . $field); + } + + /** + * gets a value from a related object + * only possible, if the relation has cardinality 1 + * e.g. 'has_one' or 'belongs_to' + * + * @param string $relation name of relation + * @param string $field name of column + * @throws InvalidArgumentException if no relation with given name is found + * @return mixed the value from the related object + */ + function getRelationValue($relation, $field) + { + $field = strtolower($field); + $options = $this->getRelationOptions($relation); + if ($options['type'] === 'has_one' || $options['type'] === 'belongs_to') { + return $this->{$relation}->{$field} ?? null; + } else { + throw new InvalidArgumentException('Relation ' . $relation . ' not found or not applicable.'); + } + } + + /** + * returns default value for column + * + * @param string $field name of column + * @return mixed the default value + */ + function getDefaultValue($field) + { + $default_value = null; + if (!isset($this->default_values()[$field])) { + if (!in_array($field, $this->pk())) { + $meta = $this->db_fields()[$field]; + if (isset($meta['default'])) { + $default_value = $meta['default']; + } elseif ($meta['null'] == 'NO') { + if (strpos($meta['type'], 'text') !== false || strpos($meta['type'], 'char') !== false) { + $default_value = ''; + } + if (strpos($meta['type'], 'int') !== false) { + $default_value = '0'; + } + } + } + } else { + $default_value = $this->default_values()[$field]; + } + return $default_value; + } + + /** + * sets value of a column + * + * @throws InvalidArgumentException if column could not be found + * @throws BadMethodCallException if setter for additional field could not be found + * @param string $field + * @param mixed $value + * @return string + */ + function setValue($field, $value) + { + $field = strtolower($field); + $ret = false; + if (in_array($field, $this->known_slots())) { + if (isset($this->getter_setter_map()[$field]['set'])) { + return call_user_func([$this, $this->getter_setter_map()[$field]['set']], $value); + } + if (array_key_exists($field, $this->content)) { + if (array_key_exists($field, $this->serialized_fields())) { + $ret = $this->setSerializedValue($field, $value); + } elseif ($this->isI18nField($field)) { + $ret = $this->setI18nValue($field, $value); + } else { + $ret = ($this->content[$field] = $value); + } + } elseif (isset($this->additional_fields()[$field]['set'])) { + if ($this->additional_fields()[$field]['set'] instanceof Closure) { + return call_user_func_array($this->additional_fields()[$field]['set'], [$this, $field, $value]); + } else { + return call_user_func([$this, $this->additional_fields()[$field]['set']], $field, $value); + } + } elseif (array_key_exists($field, $this->relations)) { + $options = $this->getRelationOptions($field); + if ($options['type'] === 'has_one' || $options['type'] === 'belongs_to') { + if (is_a($value, $options['class_name'])) { + $this->relations[$field] = $value; + if ($options['type'] == 'has_one') { + $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); + call_user_func($options['assoc_foreign_key_setter'], $value, $foreign_key_value); + } else { + $assoc_foreign_key_value = call_user_func($options['assoc_foreign_key_getter'], $value, $this); + if ($assoc_foreign_key_value === null) { + throw new InvalidArgumentException(sprintf('trying to set belongs_to object of type: %s, but assoc_foreign_key: %s is null', get_class($value), $options['assoc_foreign_key'])); + } + $this->setValue($options['foreign_key'], $assoc_foreign_key_value); + } + } elseif ( + $value === null + && $this->db_fields()[$options['foreign_key']]['null'] === 'YES' + ) { + $this->resetRelation($field); + $this->setValue($options['foreign_key'], null); + + } else { + throw new InvalidArgumentException(sprintf('relation %s expects object of type: %s', $field, $options['class_name'])); + } + } + if ($options['type'] == 'has_many' || $options['type'] == 'has_and_belongs_to_many') { + if (is_array($value) || $value instanceof Traversable) { + $new_ids = []; + $old_ids = $this->{$field}->pluck('id'); + foreach ($value as $current) { + if (!is_a($current, $options['class_name'])) { + throw new InvalidArgumentException(sprintf('relation %s expects object of type: %s', $field, $options['class_name'])); + } + if ($options['type'] == 'has_many') { + $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); + call_user_func($options['assoc_foreign_key_setter'], $current, $foreign_key_value); + } + if ($current->id !== null) { + $new_ids[] = $current->id; + $existing = $this->{$field}->find($current->id); + if ($existing) { + $existing->setData($current); + } else { + $this->{$field}->append($current); + } + } else { + $this->{$field}->append($current); + } + } + foreach (array_diff($old_ids, $new_ids) as $to_delete) { + $this->{$field}->unsetByPK($to_delete); + } + } else { + throw new InvalidArgumentException(sprintf('relation %s expects collection or array of objects of type: %s', $field, $options['class_name'])); + } + } + } + } else { + throw new InvalidArgumentException(get_class($this) . '::'. $field . ' not found.'); + } + return $ret; + } + + /** + * magic method for dynamic properties + * + * @throws InvalidArgumentException if column could not be found + * @throws BadMethodCallException if getter for additional field could not be found + * @param string $field the column or additional field + * @return null|string|SimpleORMapCollection + */ + function __get($field) + { + return $this->getValue($field); + } + /** + * magic method for dynamic properties + * + * @throws InvalidArgumentException if column could not be found + * @throws BadMethodCallException if setter for additional field could not be found + * @param string $field + * @param string $value + * @return string + */ + function __set($field, $value) + { + return $this->setValue($field, $value); + } + /** + * magic method for dynamic properties + * + * @param string $field + * @return bool + */ + function __isset($field) + { + $field = strtolower($field); + if (in_array($field, $this->known_slots())) { + $value = $this->getValue($field); + return $value instanceOf SimpleORMapCollection ? (bool)count($value) : !is_null($value); + } else { + return false; + } + } + + /** + * ArrayAccess: Check whether the given offset exists. + * + * @param string $offset + */ + public function offsetExists($offset): bool + { + return $this->__isset($offset); + } + + /** + * ArrayAccess: Get the value at the given offset. + * + * @throws InvalidArgumentException if column could not be found + * @throws BadMethodCallException if getter for additional field could not be found + * @param string $offset the column or additional field + * @return null|string|SimpleORMapCollection + */ + public function offsetGet($offset): mixed + { + return $this->getValue($offset); + } + + /** + * ArrayAccess: Set the value at the given offset. + * + * @throws InvalidArgumentException if column could not be found + * @throws BadMethodCallException if setter for additional field could not be found + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value): void + { + $this->setValue($offset, $value); + } + + /** + * ArrayAccess: unset the value at the given offset (not applicable) + * + * @param string $offset + */ + public function offsetUnset($offset): void + { + + } + + /** + * IteratorAggregate + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->toArray()); + } + + /** + * Countable + */ + public function count(): int + { + return count($this->known_slots()) - count($this->relations); + } + + /** + * check if given column exists in table + * @param string $field + * @return boolean + */ + function isField($field) + { + $field = strtolower($field); + return isset($this->db_fields()[$field]); + } + + /** + * check if given relation exists in this class + * @param string $field + * @return boolean + */ + function isRelation($field) + { + $field = strtolower($field); + return array_key_exists($field, $this->relations); + } + + /** + * check if given column is additional + * @param string $field + * @return boolean + */ + function isAdditionalField($field) + { + $field = strtolower($field); + return isset($this->additional_fields()[$field]); + } + + /** + * check if given column is an alias + * @param string $field + * @return boolean + */ + function isAliasField($field) + { + $field = strtolower($field); + return isset($this->alias_fields()[$field]); + } + + /** + * check if given column is a multi-language field + * @param string $field + * @return boolean + */ + function isI18nField($field) + { + $field = strtolower($field); + return isset($this->i18n_fields()[$field]); + } + + /** + * set multiple column values + * if second param is set, existing data in object will be + * discarded and dirty state is cleared, + * else new data overrides old data + * + * @param ?iterable $data assoc array + * @param ?boolean $reset existing data in object will be discarded + * @return int|bool number of columns changed + */ + function setData($data, $reset = false) + { + $count = 0; + if ($reset) { + if ($this->applyCallbacks('before_initialize') === false) { + return false; + } + $this->initializeContent(); + } + if (is_iterable($data)) { + foreach($data as $key => $value) { + $key = strtolower($key); + if (isset($this->db_fields()[$key]) + || isset($this->alias_fields()[$key]) + || isset($this->additional_fields()[$key]['set']) + ) { + $this->setValue($key, $value); + ++$count; + } + } + } + if ($reset) { + $this->applyCallbacks('after_initialize'); + } + return $count; + } + + /** + * check if object exists in database + * @return boolean + */ + function isNew() + { + return $this->is_new; + } + + /** + * check if object was deleted + * + * @return boolean + */ + function isDeleted() + { + return $this->is_deleted; + } + + /** + * set object to new state + * @param boolean $is_new + * @return boolean + */ + function setNew($is_new) + { + return $this->is_new = $is_new; + } + + /** + * returns sql clause with current table and pk + * @throws UnexpectedValueException if the primary key is incomplete + * @return boolean|array + */ + function getWhereQuery() + { + $where_query = null; + $pk_not_set = []; + foreach ($this->pk() as $key) { + $pk = $this->content_db[$key] ?? $this->content[$key] ?? null; + if (isset($pk)) { + $where_query[] = "`{$this->db_table()}`.`{$key}` = " . DBManager::get()->quote($pk); + } else { + $pk_not_set[] = $key; + } + } + if (!$where_query || count($pk_not_set)){ + if ($this->isNew()) { + return false; + } else { + throw new UnexpectedValueException(sprintf("primary key incomplete: %s must not be null", join(',',$pk_not_set))); + } + } + return $where_query; + } + + /** + * restore entry from database + * @return boolean + */ + function restore() + { + $where_query = $this->getWhereQuery(); + $id = $this->getId(); + if ($where_query) { + if ($this->applyCallbacks('before_initialize') === false) { + return false; + } + $this->initializeContent(); + $query = "SELECT * FROM `{$this->db_table()}` WHERE " + . join(" AND ", $where_query); + $st = DBManager::get()->prepare($query); + $st->execute(); + $st->setFetchMode(PDO::FETCH_INTO , $this); + if ($st->fetch()) { + $this->setNew(false); + $this->applyCallbacks('after_initialize'); + return true; + } + } + $this->setData([], true); + $this->setNew(true); + if (isset($id)) { + $this->setId($id); + } + return false; + } + + /** + * store entry in database + * + * @throws UnexpectedValueException if there are forbidden NULL values + * @return number|boolean + */ + function store() + { + // Set id or prepare setting of id + if ($this->isNew() && $this->getId() === null) { + // Explicitly set id to 0 if auto increment pk is null + if ($this->hasAutoIncrementColumn()) { + $this->setId(0); + } else { + $this->setId($this->getNewId()); + } + } + + if ($this->applyCallbacks('before_store') === false) { + return false; + } + + $ret = 0; + + if (!$this->isDeleted() && ($this->isDirty() || $this->isNew())) { + $callback = $this->isNew() ? 'before_create' : 'before_update'; + if ($this->applyCallbacks($callback) === false) { + return false; + } + + // Collect i18n contents + $i18ncontent = []; + foreach (array_keys($this->i18n_fields()) as $field) { + if ($this->content[$field] instanceof I18NString) { + $i18ncontent[$field] = $this->content[$field]; + $this->content[$field] = $this->content[$field]->original(); + $this->content_db[$field] = $this->content_db[$field]->original(); + } + } + + // Create sql data assignment chunks + foreach ($this->db_fields() as $field => $meta) { + $value = $this->content[$field]; + if ($field == 'chdate' && !$this->isFieldDirty($field) && $this->isDirty()) { + $value = time(); + } + if ($field == 'mkdate') { + if ($this->isNew()) { + if (!$this->isFieldDirty($field)) { + $value = time(); + } + } else { + continue; + } + } + if ($value === null && $meta['null'] == 'NO') { + throw new UnexpectedValueException($this->db_table() . '.' . $field . ' must not be null.'); + } + if (is_float($value)) { + $value = str_replace(',', '.', $value); + } + $this->content[$field] = $value; + $query_part[] = "`$field` = " . DBManager::get()->quote($value) . " "; + } + + // Create store query + if (!$this->isNew()) { + $where_query = $this->getWhereQuery(); + $query = "UPDATE `{$this->db_table()}` SET " + . implode(',', $query_part); + $query .= " WHERE " . join(" AND ", $where_query); + } else { + $query = "INSERT INTO `{$this->db_table()}` SET " + . implode(',', $query_part); + } + $ret = DBManager::get()->exec($query); + + // Retrieve generated id from database if pk is an auto increment + // column + if ($this->isNew()) { + if ($this->hasAutoIncrementColumn() && !$this->getId()) { + $this->setId(DBManager::get()->lastInsertId()); + } + } + + // Store i18n contents + foreach ($i18ncontent as $field => $one) { + $meta = [ + 'object_id' => $this->getId(), + 'table' => $this->db_table(), + 'field' => $field + ]; + $one->setMetadata($meta); + $one->storeTranslations(); + if (!$this->content[$field] instanceof I18NString) { + $this->content[$field] = $one; + $this->content_db[$field] = clone $one; + } + } + + // Apply callbacks + $this->applyCallbacks($this->isNew() ? 'after_create' : 'after_update'); + } + $rel_ret = $this->storeRelations(); + + $this->applyCallbacks('after_store'); + + if ($ret || $rel_ret) { + $this->restore(); + } + return $ret + $rel_ret; + } + + /** + * sends a store message to all initialized related objects + * if a relation has a callback for 'on_store' configured, the callback + * is instead invoked + * + * @param null|array|string $only_these + * @return int|false number addition of all return values, false if none was called + */ + protected function storeRelations($only_these = null) + { + $ret = false; + if (is_string($only_these)) { + $only_these = words($only_these); + } + $relations = array_keys($this->relations); + if (is_array($only_these)) { + $only_these = array_filter(array_map(function ($s) { + return is_string($s) ? strtolower($s) : null; + }, $only_these)); + $relations = array_intersect($only_these, $relations); + } + foreach ($relations as $relation) { + $options = $this->getRelationOptions($relation); + if (isset($options['on_store']) && + ($options['type'] === 'has_one' || + $options['type'] === 'has_many' || + $options['type'] === 'has_and_belongs_to_many')) { + if ($options['on_store'] instanceof Closure) { + $ret += call_user_func($options['on_store'], $this, $relation); + } elseif (isset($this->relations[$relation])) { + $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); + if ($options['type'] === 'has_one') { + call_user_func($options['assoc_foreign_key_setter'], $this->{$relation}, $foreign_key_value); + $ret = call_user_func([$this->{$relation}, 'store']); + } elseif ($options['type'] === 'has_many') { + foreach ($this->{$relation} as $r) { + call_user_func($options['assoc_foreign_key_setter'], $r, $foreign_key_value); + } + $ret += array_sum(call_user_func([$this->{$relation}, 'sendMessage'], 'store')); + $ret += array_sum(call_user_func([$this->{$relation}->getDeleted(), 'sendMessage'], 'delete')); + } else { + call_user_func([$this->{$relation}, 'sendMessage'], 'store'); + $to_delete = array_filter($this->{$relation}->getDeleted()->pluck($options['assoc_foreign_key'])); + $to_insert = array_filter($this->{$relation}->pluck($options['assoc_foreign_key'])); + $sql = "DELETE FROM `" . $options['thru_table'] ."` WHERE `" . $options['thru_key'] ."` = ? AND `" . $options['thru_assoc_key'] . "` = ?"; + $st = DBManager::get()->prepare($sql); + foreach ($to_delete as $one_value) { + $st->execute([$foreign_key_value, $one_value]); + $ret += $st->rowCount(); + } + $sql = "INSERT IGNORE INTO `" . $options['thru_table'] ."` SET `" . $options['thru_key'] ."` = ?, `" . $options['thru_assoc_key'] . "` = ?"; + $st = DBManager::get()->prepare($sql); + foreach ($to_insert as $one_value) { + $st->execute([$foreign_key_value, $one_value]); + $ret += $st->rowCount(); + } + } + } + } + } + return $ret; + } + + /** + * set chdate column to current timestamp + * @return boolean + */ + function triggerChdate() + { + if ($this->db_fields()['chdate']) { + $this->content['chdate'] = time(); + if ($where_query = $this->getWhereQuery()) { + DBManager::get()->exec("UPDATE `{$this->db_table()}` SET chdate={$this->content['chdate']} + WHERE ". join(" AND ", $where_query)); + return true; + } + } + + return false; + } + + /** + * delete entry from database + * the object is cleared, but is not(!) turned to new state + * @return bool|int number of deleted rows + */ + function delete() + { + $ret = false; + if (!$this->isDeleted() && !$this->isNew()) { + if ($this->applyCallbacks('before_delete') === false) { + return false; + } + $ret = $this->deleteRelations(); + $where_query = $this->getWhereQuery(); + if ($where_query) { + $query = "DELETE FROM `{$this->db_table()}` WHERE " + . join(" AND ", $where_query); + $ret += DBManager::get()->exec($query); + } + $this->is_deleted = true; + $this->applyCallbacks('after_delete'); + + // Remove i18n translations + if (I18N::isEnabled()) { + foreach (array_keys($this->i18n_fields()) as $field) { + if ($this->content[$field] instanceof I18NString) { + $this->content[$field]->removeTranslations(); + } + } + } + } + $this->setData([], true); + return $ret; + } + + /** + * sends a delete message to all related objects + * if a relation has a callback for 'on_delete' configured, the callback + * is invoked instead + * + * @return bool|int addition of all return values, false if none was called + */ + protected function deleteRelations() + { + $ret = false; + foreach (array_keys($this->relations) as $relation) { + $options = $this->getRelationOptions($relation); + if (isset($options['on_delete']) && + ($options['type'] === 'has_one' || + $options['type'] === 'has_many' || + $options['type'] === 'has_and_belongs_to_many')) { + if ($options['on_delete'] instanceof Closure) { + $ret += call_user_func($options['on_delete'], $this, $relation); + } else { + if ($options['type'] === 'has_one' || $options['type'] === 'has_many') { + $this->initRelation($relation); + if (isset($this->relations[$relation])) { + if ($options['type'] === 'has_one') { + $ret += call_user_func([$this->{$relation}, 'delete']); + } elseif ($options['type'] === 'has_many') { + $ret += array_sum(call_user_func([$this->{$relation}, 'sendMessage'], 'delete')); + } + } + } else { + $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this); + $sql = "DELETE FROM `" . $options['thru_table'] ."` WHERE `" . $options['thru_key'] ."` = ?"; + $st = DBManager::get()->prepare($sql); + $st->execute([$foreign_key_value]); + $ret += $st->rowCount(); + } + } + $this->relations[$relation] = null; + } + } + return $ret; + } + + /** + * init internal content arrays with nulls or defaults + * + * @throws UnexpectedValueException if there is an unmatched alias + * @return void + */ + protected function initializeContent() + { + $this->content = []; + foreach (array_keys($this->db_fields()) as $field) { + $this->content[$field] = null; + $this->content_db[$field] = null; + $this->setValue($field, $this->getDefaultValue($field)); + } + foreach ($this->alias_fields() as $alias => $field) { + if (isset($this->db_fields()[$field])) { + $this->content[$alias] =& $this->content[$field]; + $this->content_db[$alias] =& $this->content_db[$field]; + } else { + throw new UnexpectedValueException(sprintf('Column %s not found for alias %s', $field, $alias)); + } + } + foreach (array_keys($this->relations) as $one) { + $this->relations[$one] = null; + } + $this->additional_data = []; + } + + /** + * checks if at least one field was modified since last restore + * + * @return boolean + */ + public function isDirty() + { + foreach (array_keys($this->db_fields()) as $field) { + if ($this->isFieldDirty($field)) { + return true; + } + } + return false; + } + + /** + * checks if given field was modified since last restore + * + * @param string $field + * @return boolean + */ + public function isFieldDirty($field) + { + $field = strtolower($field); + if ($this->content[$field] === null || $this->content_db[$field] === null) { + return $this->content[$field] !== $this->content_db[$field]; + } else if ($this->content[$field] instanceof I18NString || $this->content_db[$field] instanceof I18NString) { + return $this->content[$field] != $this->content_db[$field]; + } else { + return (string)$this->content[$field] !== (string)$this->content_db[$field]; + } + } + + /** + * reverts value of given field to last restored value + * + * @param string $field + * @return mixed the restored value + */ + public function revertValue($field) + { + $field = strtolower($field); + return ($this->content[$field] = $this->content_db[$field]); + } + + /** + * returns unmodified value of given field + * + * @param string $field + * @throws InvalidArgumentException + * @return mixed + */ + public function getPristineValue($field) + { + $field = strtolower($field); + if (array_key_exists($field, $this->content_db)) { + return $this->content_db[$field]; + } else { + throw new InvalidArgumentException(get_class($this) . '::'. $field . ' not found.'); + } + } + + /** + * intitalize a relationship and get related record(s) + * + * @param string $relation name of relation + * @throws InvalidArgumentException if the relation does not exists + * @return void + */ + public function initRelation($relation) + { + if (!array_key_exists($relation, $this->relations)) { + throw new InvalidArgumentException('Unknown relation: ' . $relation); + } + if ($this->relations[$relation] === null) { + $options = $this->getRelationOptions($relation); + $to_call = [$options['class_name'], $options['assoc_func']]; + if (!is_callable($to_call)) { + throw new RuntimeException('assoc_func: ' . join('::', $to_call) . ' is not callable.' ); + } + $params = $options['assoc_func_params_func']; + if ($options['type'] === 'has_many') { + $records = function($record) use ($to_call, $params, $options) { + $p = (array)$params($record); + return call_user_func_array($to_call, array_merge(count($p) ? $p : [null], [$options['order_by'] ?? null])); + }; + $this->relations[$relation] = new SimpleORMapCollection($records, $options, $this); + } elseif ($options['type'] === 'has_and_belongs_to_many') { + $records = function($record) use ($to_call, $params, $options) {$p = (array)$params($record); return call_user_func_array($to_call, array_merge(count($p) ? $p : [null], [$options]));}; + $this->relations[$relation] = new SimpleORMapCollection($records, $options, $this); + } else { + $p = (array)$params($this); + $records = call_user_func_array($to_call, count($p) ? $p : [null]); + $result = is_array($records) ? ($records[0] ?? null) : $records; + $this->relations[$relation] = $result; + } + } + } + + /** + * clear data for a relationship + * + * @param string $relation name of relation + * @throws InvalidArgumentException if teh relation does not exists + * @return void + */ + public function resetRelation($relation) + { + if (!array_key_exists($relation, $this->relations)) { + throw new InvalidArgumentException('Unknown relation: ' . $relation); + } + $this->relations[$relation] = null; + } + + /** + * invoke registered callbacks for given type + * if one callback returns false the following will not + * be invoked + * + * @param string $type type of callback + * @return bool return value from last callback + */ + protected function applyCallbacks($type) + { + $ok = true; + foreach ($this->registered_callbacks()[$type] as $cb) { + if ($cb instanceof Closure) { + $function = $cb; + $params = [$this, $type, $cb]; + } else { + $function = [$this, $cb]; + $params = [$type]; + }; + $ok = call_user_func_array($function, $params); + if ($ok === false) { + break; + } + } + return $ok; + } + + /** + * register given callback for one or many possible callback types + * callback param could be a closure or method name of current class + * + * @param string|array $types types to register callback for + * @param callable $cb callback + * @throws InvalidArgumentException if the callback type is not known + * @return number of registered callbacks + */ + protected static function registerCallback($types, $cb) + { + trigger_error(__METHOD__ . ' is deprecated. Please use the configuration in configure().', E_USER_DEPRECATED); + + $types = is_array($types) ? $types : words($types); + $reg = 0; + foreach ($types as $type) { + if (isset(static::registered_callbacks()[$type])) { + $found = array_search($cb, self::$config[static::class]['registered_callbacks'][$type], true); + if ($found === false) { + self::$config[static::class]['registered_callbacks'][$type][] = $cb; + $reg++; + } + } else { + throw new InvalidArgumentException('Unknown callback type: ' . $type); + } + } + return $reg; + } + + /** + * unregister given callback for one or many possible callback types + * + * @param string|array $types types to unregister callback for + * @param mixed $cb + * @throws InvalidArgumentException if the callback type is not known + * @return number of unregistered callbacks + */ + protected static function unregisterCallback($types, $cb) + { + trigger_error(__METHOD__ . ' is deprecated. Please use the configuration in configure().', E_USER_DEPRECATED); + + $types = is_array($types) ? $types : words($types); + foreach ($types as $type) { + if (isset(static::registered_callbacks()[$type])) { + $found = array_search($cb, self::$config[static::class]['registered_callbacks'][$type], true); + if ($found !== false) { + $unreg++; + unset(self::$config[static::class]['registered_callbacks'][$type][$found]); + } + } else { + throw new InvalidArgumentException('Unknown callback type: ' . $type); + } + } + return $unreg; + } + + /** + * default callback used to map specific callbacks to NotificationCenter + * + * @param string $cb_type callback type + * @return void|boolean + */ + protected function cbNotificationMapper($cb_type) + { + if (isset($this->notification_map()[$cb_type])) { + try { + foreach(words($this->notification_map()[$cb_type]) as $notification) { + NotificationCenter::postNotification($notification, $this); + } + } catch (NotificationVetoException $e) { + return false; + } + } + } + + /** + * default callback used to map specific callbacks to NotificationCenter + * + * @param string $cb_type callback type + * @return void|boolean + */ + protected function cbAfterInitialize($cb_type) + { + foreach (array_keys($this->db_fields()) as $field) { + if (is_object($this->content[$field])) { + $this->content_db[$field] = clone $this->content[$field]; + } else { + $this->content_db[$field] = $this->content[$field]; + } + } + } + + /** + * default setter used to proxy serialized fields with + * ArrayObjects + * + * @param string $field column name + * @param mixed $value value + * @return mixed + */ + protected function setSerializedValue($field, $value) + { + $object_type = $this->serialized_fields()[$field]; + if (is_null($value) || $value instanceof $object_type) { + $this->content[$field] = $value; + } else { + $this->content[$field] = new $object_type($value); + } + return $this->content[$field]; + } + + /** + * default setter used to proxy I18N fields with + * I18NString + * + * @param string $field column name + * @param mixed $value value + * @return mixed + */ + protected function setI18nValue($field, $value) + { + $meta = ['object_id' => $this->getId(), + 'table' => $this->db_table(), + 'field' => $field]; + if ($value instanceof I18NString) { + $value->setMetadata($meta); + $this->content[$field] = $value; + } else { + $this->content[$field] = new I18NString($value, null, $meta); + } + return $this->content[$field]; + } + + /** + * Cleans up this object. This essentially reset all relations of + * this object and marks them as unused so that the garbage collector may + * remove the objects. + * + * Use this function when you ran into memory problems and need to free + * some memory; + * + * @return void + */ + public function cleanup() + { + foreach ($this->relations as $relation => $object) { + if ($object instanceof SimpleORMap || $object instanceof SimpleORMapCollection) { + $this->relations[$relation]->cleanup(); + } + $this->resetRelation($relation); + } + } +} diff --git a/lib/classes/SimpleORMapCollection.class.php b/lib/classes/SimpleORMapCollection.class.php deleted file mode 100644 index 7debb80..0000000 --- a/lib/classes/SimpleORMapCollection.class.php +++ /dev/null @@ -1,258 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @extends SimpleCollection - * - * @template T of SimpleORMap - */ -class SimpleORMapCollection extends SimpleCollection -{ - /** - * @var int Exception error code denoting a wrong type of objects. - */ - const WRONG_OBJECT_TYPE = 1; - - /** - * @var int Exception error code denoting that an object of this `id` already exists. - */ - const OBJECT_EXISTS = 2; - - /** - * the record object this collection belongs to - * - * @var ?SimpleORMap - */ - protected $related_record; - - /** - * relation options - * @var array - */ - protected $relation_options = []; - - /** - * creates a collection from an array of objects - * all objects should be of the same type - * - * @throws InvalidArgumentException if first entry is not SimpleOrMap - * @param T[] $data array with SimpleORMap objects - * @param bool $strict check every element for correct type and unique pk - * @return SimpleORMapCollection - */ - public static function createFromArray(array $data, $strict = true) - { - $ret = new SimpleORMapCollection(); - if (count($data)) { - $first = current($data); - if ($first instanceof SimpleORMap) { - $ret->setClassName(get_class($first)); - if ($strict) { - foreach ($data as $one) { - $ret[] = $one; - } - } else { - $ret->exchangeArray($data); - } - } else { - throw new InvalidArgumentException('This collection only accepts objects derived from SimpleORMap', self::WRONG_OBJECT_TYPE); - } - } - return $ret; - } - - /** - * Constructor - * - * @param ?Closure $finder callable to fill collection - * @param ?array $options relationship options - * @param SimpleORMap|null $record related record - */ - public function __construct(Closure $finder = null, array $options = null, SimpleORMap $record = null) - { - $this->relation_options = $options; - $this->related_record = $record; - parent::__construct($finder === null ? [] : $finder); - } - - /** - * Sets the value at the specified index - * checks if the value is an object of specified class - * - * @see ArrayObject::offsetSet() - * @throws InvalidArgumentException if the given model does not fit (wrong type or id) - */ - public function offsetSet($index, $newval): void - { - if (!is_null($index)) { - $index = (int)$index; - } - if (!is_a($newval, $this->getClassName())) { - throw new InvalidArgumentException('This collection only accepts objects of type: ' . $this->getClassName(), self::WRONG_OBJECT_TYPE); - } - if ($this->related_record && $this->relation_options['type'] === 'has_many') { - $foreign_key_value = call_user_func($this->relation_options['assoc_func_params_func'], $this->related_record); - call_user_func($this->relation_options['assoc_foreign_key_setter'], $newval, $foreign_key_value); - } - if ($newval->id !== null) { - $exists = $this->find($newval->id); - if ($exists) { - throw new InvalidArgumentException('Element could not be appended, element with id: ' . $exists->id . ' is in the way', self::OBJECT_EXISTS); - } - } - parent::offsetSet($index, $newval); - } - - /** - * sets the allowed class name - * @param class-string $class_name - * @return void - */ - public function setClassName($class_name) - { - $this->relation_options['class_name'] = strtolower($class_name); - $this->deleted->relation_options['class_name'] = strtolower($class_name); - } - - /** - * sets the related record - * - * @param SimpleORMap $record - * @return void - */ - public function setRelatedRecord(SimpleORMap $record) - { - $this->related_record = $record; - } - - /** - * gets the allowed classname - * - * @return string - */ - public function getClassName() - { - return strtolower($this->relation_options['class_name']); - } - - /** - * reloads the elements of the collection - * by calling the finder function - * - * @throws InvalidArgumentException - * @return ?int number of records after refresh - */ - public function refresh() - { - if (is_callable($this->finder)) { - $data = call_user_func($this->finder, $this->related_record); - foreach ($data as $one) { - if (!is_a($one, $this->getClassName())) { - throw new InvalidArgumentException('This collection only accepts objects of type: ' . $this->getClassName(), self::WRONG_OBJECT_TYPE); - } - } - $this->exchangeArray($data); - $this->deleted->exchangeArray([]); - return $this->last_count = $this->count(); - } - - return null; - } - - /** - * returns element with given primary key value - * - * @param string $value primary key value to search for - * @return ?T - */ - public function find($value) - { - return $this->findOneBy('id', $value); - } - - /** - * returns the collection as grouped array - * first param is the column to group by, it becomes the key in - * the resulting array, default is pk. Limit returned fields with second param - * The grouped entries can optoionally go through the given - * callback. If no callback is provided, only the first grouped - * entry is returned, suitable for grouping by unique column - * - * @param string $group_by the column to group by, pk if ommitted - * @param mixed $only_these_fields limit returned fields - * @param ?callable $group_func closure to aggregate grouped entries - * @return array assoc array - */ - public function toGroupedArray($group_by = 'id', $only_these_fields = null, callable $group_func = null) - { - $result = []; - foreach ($this as $record) { - $key = $record->getValue($group_by); - if (is_array($key)) { - $key = join('_', $key); - } - $result[$key][] = $record->toArray($only_these_fields); - } - if ($group_func === null) { - $group_func = 'current'; - } - return array_map($group_func, $result); - } - - /** - * mark element(s) for deletion - * element(s) with given primary key are moved to - * internal deleted collection - * - * @param string $id primary key of element - * @return int number of unsetted elements - */ - public function unsetByPk($id) - { - return $this->unsetBy('id', $id); - } - - /** - * merge in another collection, elements must be of - * the same type, if an element already exists it is - * replaced or ignored depending on second param - * - * @param SimpleORMapCollection $a_collection - * @param string $mode 'replace' or 'ignore' - * @return void - */ - public function merge(SimpleCollection $a_collection, string $mode = 'ignore') - { - $mode = func_get_arg(1); - foreach ($a_collection as $element) { - try { - /** - * @throws InvalidArgumentException - * @see SimpleORMapCollection::offsetSet() - */ - $this[] = $element; - } catch (InvalidArgumentException $e) { - if ($e->getCode() === self::OBJECT_EXISTS) { - if ($mode === 'replace') { - $this->unsetByPk($element->id); - $this[] = $element; - } // else $mode means 'ignore' - } else { - throw $e; - } - } - } - $this->storage = array_values($this->storage); - } -} diff --git a/lib/classes/SimpleORMapCollection.php b/lib/classes/SimpleORMapCollection.php new file mode 100644 index 0000000..7debb80 --- /dev/null +++ b/lib/classes/SimpleORMapCollection.php @@ -0,0 +1,258 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @extends SimpleCollection + * + * @template T of SimpleORMap + */ +class SimpleORMapCollection extends SimpleCollection +{ + /** + * @var int Exception error code denoting a wrong type of objects. + */ + const WRONG_OBJECT_TYPE = 1; + + /** + * @var int Exception error code denoting that an object of this `id` already exists. + */ + const OBJECT_EXISTS = 2; + + /** + * the record object this collection belongs to + * + * @var ?SimpleORMap + */ + protected $related_record; + + /** + * relation options + * @var array + */ + protected $relation_options = []; + + /** + * creates a collection from an array of objects + * all objects should be of the same type + * + * @throws InvalidArgumentException if first entry is not SimpleOrMap + * @param T[] $data array with SimpleORMap objects + * @param bool $strict check every element for correct type and unique pk + * @return SimpleORMapCollection + */ + public static function createFromArray(array $data, $strict = true) + { + $ret = new SimpleORMapCollection(); + if (count($data)) { + $first = current($data); + if ($first instanceof SimpleORMap) { + $ret->setClassName(get_class($first)); + if ($strict) { + foreach ($data as $one) { + $ret[] = $one; + } + } else { + $ret->exchangeArray($data); + } + } else { + throw new InvalidArgumentException('This collection only accepts objects derived from SimpleORMap', self::WRONG_OBJECT_TYPE); + } + } + return $ret; + } + + /** + * Constructor + * + * @param ?Closure $finder callable to fill collection + * @param ?array $options relationship options + * @param SimpleORMap|null $record related record + */ + public function __construct(Closure $finder = null, array $options = null, SimpleORMap $record = null) + { + $this->relation_options = $options; + $this->related_record = $record; + parent::__construct($finder === null ? [] : $finder); + } + + /** + * Sets the value at the specified index + * checks if the value is an object of specified class + * + * @see ArrayObject::offsetSet() + * @throws InvalidArgumentException if the given model does not fit (wrong type or id) + */ + public function offsetSet($index, $newval): void + { + if (!is_null($index)) { + $index = (int)$index; + } + if (!is_a($newval, $this->getClassName())) { + throw new InvalidArgumentException('This collection only accepts objects of type: ' . $this->getClassName(), self::WRONG_OBJECT_TYPE); + } + if ($this->related_record && $this->relation_options['type'] === 'has_many') { + $foreign_key_value = call_user_func($this->relation_options['assoc_func_params_func'], $this->related_record); + call_user_func($this->relation_options['assoc_foreign_key_setter'], $newval, $foreign_key_value); + } + if ($newval->id !== null) { + $exists = $this->find($newval->id); + if ($exists) { + throw new InvalidArgumentException('Element could not be appended, element with id: ' . $exists->id . ' is in the way', self::OBJECT_EXISTS); + } + } + parent::offsetSet($index, $newval); + } + + /** + * sets the allowed class name + * @param class-string $class_name + * @return void + */ + public function setClassName($class_name) + { + $this->relation_options['class_name'] = strtolower($class_name); + $this->deleted->relation_options['class_name'] = strtolower($class_name); + } + + /** + * sets the related record + * + * @param SimpleORMap $record + * @return void + */ + public function setRelatedRecord(SimpleORMap $record) + { + $this->related_record = $record; + } + + /** + * gets the allowed classname + * + * @return string + */ + public function getClassName() + { + return strtolower($this->relation_options['class_name']); + } + + /** + * reloads the elements of the collection + * by calling the finder function + * + * @throws InvalidArgumentException + * @return ?int number of records after refresh + */ + public function refresh() + { + if (is_callable($this->finder)) { + $data = call_user_func($this->finder, $this->related_record); + foreach ($data as $one) { + if (!is_a($one, $this->getClassName())) { + throw new InvalidArgumentException('This collection only accepts objects of type: ' . $this->getClassName(), self::WRONG_OBJECT_TYPE); + } + } + $this->exchangeArray($data); + $this->deleted->exchangeArray([]); + return $this->last_count = $this->count(); + } + + return null; + } + + /** + * returns element with given primary key value + * + * @param string $value primary key value to search for + * @return ?T + */ + public function find($value) + { + return $this->findOneBy('id', $value); + } + + /** + * returns the collection as grouped array + * first param is the column to group by, it becomes the key in + * the resulting array, default is pk. Limit returned fields with second param + * The grouped entries can optoionally go through the given + * callback. If no callback is provided, only the first grouped + * entry is returned, suitable for grouping by unique column + * + * @param string $group_by the column to group by, pk if ommitted + * @param mixed $only_these_fields limit returned fields + * @param ?callable $group_func closure to aggregate grouped entries + * @return array assoc array + */ + public function toGroupedArray($group_by = 'id', $only_these_fields = null, callable $group_func = null) + { + $result = []; + foreach ($this as $record) { + $key = $record->getValue($group_by); + if (is_array($key)) { + $key = join('_', $key); + } + $result[$key][] = $record->toArray($only_these_fields); + } + if ($group_func === null) { + $group_func = 'current'; + } + return array_map($group_func, $result); + } + + /** + * mark element(s) for deletion + * element(s) with given primary key are moved to + * internal deleted collection + * + * @param string $id primary key of element + * @return int number of unsetted elements + */ + public function unsetByPk($id) + { + return $this->unsetBy('id', $id); + } + + /** + * merge in another collection, elements must be of + * the same type, if an element already exists it is + * replaced or ignored depending on second param + * + * @param SimpleORMapCollection $a_collection + * @param string $mode 'replace' or 'ignore' + * @return void + */ + public function merge(SimpleCollection $a_collection, string $mode = 'ignore') + { + $mode = func_get_arg(1); + foreach ($a_collection as $element) { + try { + /** + * @throws InvalidArgumentException + * @see SimpleORMapCollection::offsetSet() + */ + $this[] = $element; + } catch (InvalidArgumentException $e) { + if ($e->getCode() === self::OBJECT_EXISTS) { + if ($mode === 'replace') { + $this->unsetByPk($element->id); + $this[] = $element; + } // else $mode means 'ignore' + } else { + throw $e; + } + } + } + $this->storage = array_values($this->storage); + } +} diff --git a/lib/classes/StudipArrayObject.class.php b/lib/classes/StudipArrayObject.class.php deleted file mode 100644 index da7e66e..0000000 --- a/lib/classes/StudipArrayObject.class.php +++ /dev/null @@ -1,471 +0,0 @@ - - * - * Zend Framework (http://framework.zend.com/) - * - * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) - * @license http://framework.zend.com/license/new-bsd New BSD License - */ -class StudipArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable -{ - /** - * Properties of the object have their normal functionality - * when accessed as list (var_dump, foreach, etc.). - */ - const STD_PROP_LIST = 1; - - /** - * Entries can be accessed as properties (read and write). - */ - const ARRAY_AS_PROPS = 2; - - /** - * @var array - */ - protected $storage; - - /** - * @var int - */ - protected $flag; - - /** - * @var string - */ - protected $iteratorClass; - - /** - * @var array - */ - protected $protectedProperties; - - /** - * Constructor - * - * @param array $input - * @param int $flags - * @param string $iteratorClass - */ - public function __construct($input = [], $flags = self::STD_PROP_LIST, $iteratorClass = 'ArrayIterator') - { - $this->setFlags($flags); - $this->storage = $input; - $this->setIteratorClass($iteratorClass); - $this->protectedProperties = array_keys(get_object_vars($this)); - } - - /** - * Returns whether the requested key exists - * - * @param mixed $key - * @return bool - */ - public function __isset($key) - { - if ($this->flag == self::ARRAY_AS_PROPS) { - return $this->offsetExists($key); - } - - $this->validateKeyUsage($key); - - return isset($this->$key); - } - - /** - * Sets the value at the specified key to value - * - * @param mixed $key - * @param mixed $value - * @return void - */ - public function __set($key, $value) - { - if ($this->flag == self::ARRAY_AS_PROPS) { - $this->offsetSet($key, $value); - return; - } - - $this->validateKeyUsage($key); - - $this->$key = $value; - } - - /** - * Unsets the value at the specified key - * - * @param mixed $key - * @return void - */ - public function __unset($key) - { - if ($this->flag == self::ARRAY_AS_PROPS) { - $this->offsetUnset($key); - return; - } - - $this->validateKeyUsage($key); - - unset($this->$key); - } - - /** - * Returns the value at the specified key - * - * @param mixed $key - * @return mixed - */ - public function __get($key) - { - if ($this->flag == self::ARRAY_AS_PROPS) { - $ret = $this->offsetGet($key); - return $ret; - } - - $this->validateKeyUsage($key); - - return $this->$key; - } - - /** - * Called when serializing an ArrayObject - */ - public function __serialize(): array - { - return get_object_vars($this); - } - - /** - * Called when unserializing an ArrayObject - */ - public function __unserialize(array $data): void - { - foreach ($data as $k => $v) { - switch ($k) { - case 'flag': - $this->setFlags($v); - break; - case 'storage': - $this->exchangeArray($v); - break; - case 'iteratorClass': - $this->setIteratorClass($v); - break; - case 'protectedProperties': - break; - default: - $this->__set($k, $v); - } - } - } - - /** - * Appends the value - * - * @param mixed $value - * @return void - */ - public function append($value) - { - $this->storage[] = $value; - } - - /** - * Sort the entries by value - * - * @return void - */ - public function asort() - { - asort($this->storage); - } - - /** - * Get the number of public properties in the ArrayObject - */ - public function count(): int - { - return count($this->storage); - } - - /** - * Exchange the array for another one. - * - * @param array|ArrayObject $data - * @return array - */ - public function exchangeArray($data) - { - if (!is_array($data) && !is_object($data)) { - throw new InvalidArgumentException('Passed variable is not an array or object, using empty array instead'); - } - - if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) { - $data = $data->getArrayCopy(); - } - if (!is_array($data)) { - $data = (array) $data; - } - - $storage = $this->storage; - - $this->storage = $data; - - return $storage; - } - - /** - * Creates a copy of the ArrayObject. - * - * @return array - */ - public function getArrayCopy() - { - return $this->storage; - } - - /** - * Gets the behavior flags. - * - * @return int - */ - public function getFlags() - { - return $this->flag; - } - - /** - * Create a new iterator from an ArrayObject instance - */ - public function getIterator(): Traversable - { - $class = $this->iteratorClass; - - return new $class($this->storage); - } - - /** - * Gets the iterator classname for the ArrayObject. - * - * @return string - */ - public function getIteratorClass() - { - return $this->iteratorClass; - } - - /** - * Sort the entries by key - * - * @return void - */ - public function ksort() - { - ksort($this->storage); - } - - /** - * Sort an array using a case insensitive "natural order" algorithm - * - * @return void - */ - public function natcasesort() - { - natcasesort($this->storage); - } - - /** - * Sort entries using a "natural order" algorithm - * - * @return void - */ - public function natsort() - { - natsort($this->storage); - } - - /** - * Returns whether the requested key exists - * - * @param mixed $key - */ - public function offsetExists($key): bool - { - return isset($this->storage[$key]); - } - - /** - * Returns the value at the specified key - * - * @param mixed $key - */ - public function offsetGet($key): mixed - { - $ret = null; - if (!$this->offsetExists($key)) { - return $ret; - } - $ret = $this->storage[$key]; - - return $ret; - } - - /** - * Sets the value at the specified key to value - * - * @param mixed $key - * @param mixed $value - */ - public function offsetSet($key, $value): void - { - if (is_null($key)) { - $this->append($value); - return; - } - $this->storage[$key] = $value; - } - - /** - * Unsets the value at the specified key - * - * @param mixed $key - */ - public function offsetUnset($key): void - { - if ($this->offsetExists($key)) { - unset($this->storage[$key]); - } - } - - /** - * Serialize an ArrayObject - * - * @return string - */ - public function serialize() - { - return serialize(get_object_vars($this)); - } - - /** - * Sets the behavior flags - * - * @param int $flags - * @return void - */ - public function setFlags($flags) - { - $this->flag = $flags; - } - - /** - * Sets the iterator classname for the ArrayObject - * - * @param string $class - * @return void - */ - public function setIteratorClass($class) - { - if (class_exists($class)) { - $this->iteratorClass = $class; - - return ; - } - - if (mb_strpos($class, '\\') === 0) { - $class = '\\' . $class; - if (class_exists($class)) { - $this->iteratorClass = $class; - - return ; - } - } - - throw new InvalidArgumentException('The iterator class does not exist'); - } - - /** - * Sort the entries with a user-defined comparison function and maintain key association - * - * @param callable $function - * @return void - */ - public function uasort($function) - { - if (is_callable($function)) { - uasort($this->storage, $function); - } - } - - /** - * Sort the entries by keys using a user-defined comparison function - * - * @param callable $function - * @return void - */ - public function uksort($function) - { - if (is_callable($function)) { - uksort($this->storage, $function); - } - } - - /** - * Unserialize an ArrayObject - * - * @param string $data - * @return void - */ - public function unserialize($data) - { - $ar = unserialize($data); - $this->setFlags($ar['flag']); - $this->exchangeArray($ar['storage']); - $this->setIteratorClass($ar['iteratorClass']); - foreach ($ar as $k => $v) { - switch ($k) { - case 'flag': - $this->setFlags($v); - break; - case 'storage': - $this->exchangeArray($v); - break; - case 'iteratorClass': - $this->setIteratorClass($v); - break; - case 'protectedProperties': - break; - default: - $this->__set($k, $v); - } - } - } - - /** - * Validates whether the given key is a protected property. - * - * @param string $key The key to validate - * @throws InvalidArgumentException when key is invalid - */ - protected function validateKeyUsage($key) - { - if (in_array($key, $this->protectedProperties)) { - throw new InvalidArgumentException("{$key} is a protected property, use a different key"); - } - } - - /** - * Returns whether the given value is in the underlying array. - */ - public function contains($value): bool - { - return in_array($value, $this->storage); - } -} diff --git a/lib/classes/StudipArrayObject.php b/lib/classes/StudipArrayObject.php new file mode 100644 index 0000000..da7e66e --- /dev/null +++ b/lib/classes/StudipArrayObject.php @@ -0,0 +1,471 @@ + + * + * Zend Framework (http://framework.zend.com/) + * + * @link http://github.com/zendframework/zf2 for the canonical source repository + * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class StudipArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable +{ + /** + * Properties of the object have their normal functionality + * when accessed as list (var_dump, foreach, etc.). + */ + const STD_PROP_LIST = 1; + + /** + * Entries can be accessed as properties (read and write). + */ + const ARRAY_AS_PROPS = 2; + + /** + * @var array + */ + protected $storage; + + /** + * @var int + */ + protected $flag; + + /** + * @var string + */ + protected $iteratorClass; + + /** + * @var array + */ + protected $protectedProperties; + + /** + * Constructor + * + * @param array $input + * @param int $flags + * @param string $iteratorClass + */ + public function __construct($input = [], $flags = self::STD_PROP_LIST, $iteratorClass = 'ArrayIterator') + { + $this->setFlags($flags); + $this->storage = $input; + $this->setIteratorClass($iteratorClass); + $this->protectedProperties = array_keys(get_object_vars($this)); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + * @return bool + */ + public function __isset($key) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + return $this->offsetExists($key); + } + + $this->validateKeyUsage($key); + + return isset($this->$key); + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + $this->offsetSet($key, $value); + return; + } + + $this->validateKeyUsage($key); + + $this->$key = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + * @return void + */ + public function __unset($key) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + $this->offsetUnset($key); + return; + } + + $this->validateKeyUsage($key); + + unset($this->$key); + } + + /** + * Returns the value at the specified key + * + * @param mixed $key + * @return mixed + */ + public function __get($key) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + $ret = $this->offsetGet($key); + return $ret; + } + + $this->validateKeyUsage($key); + + return $this->$key; + } + + /** + * Called when serializing an ArrayObject + */ + public function __serialize(): array + { + return get_object_vars($this); + } + + /** + * Called when unserializing an ArrayObject + */ + public function __unserialize(array $data): void + { + foreach ($data as $k => $v) { + switch ($k) { + case 'flag': + $this->setFlags($v); + break; + case 'storage': + $this->exchangeArray($v); + break; + case 'iteratorClass': + $this->setIteratorClass($v); + break; + case 'protectedProperties': + break; + default: + $this->__set($k, $v); + } + } + } + + /** + * Appends the value + * + * @param mixed $value + * @return void + */ + public function append($value) + { + $this->storage[] = $value; + } + + /** + * Sort the entries by value + * + * @return void + */ + public function asort() + { + asort($this->storage); + } + + /** + * Get the number of public properties in the ArrayObject + */ + public function count(): int + { + return count($this->storage); + } + + /** + * Exchange the array for another one. + * + * @param array|ArrayObject $data + * @return array + */ + public function exchangeArray($data) + { + if (!is_array($data) && !is_object($data)) { + throw new InvalidArgumentException('Passed variable is not an array or object, using empty array instead'); + } + + if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) { + $data = $data->getArrayCopy(); + } + if (!is_array($data)) { + $data = (array) $data; + } + + $storage = $this->storage; + + $this->storage = $data; + + return $storage; + } + + /** + * Creates a copy of the ArrayObject. + * + * @return array + */ + public function getArrayCopy() + { + return $this->storage; + } + + /** + * Gets the behavior flags. + * + * @return int + */ + public function getFlags() + { + return $this->flag; + } + + /** + * Create a new iterator from an ArrayObject instance + */ + public function getIterator(): Traversable + { + $class = $this->iteratorClass; + + return new $class($this->storage); + } + + /** + * Gets the iterator classname for the ArrayObject. + * + * @return string + */ + public function getIteratorClass() + { + return $this->iteratorClass; + } + + /** + * Sort the entries by key + * + * @return void + */ + public function ksort() + { + ksort($this->storage); + } + + /** + * Sort an array using a case insensitive "natural order" algorithm + * + * @return void + */ + public function natcasesort() + { + natcasesort($this->storage); + } + + /** + * Sort entries using a "natural order" algorithm + * + * @return void + */ + public function natsort() + { + natsort($this->storage); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + */ + public function offsetExists($key): bool + { + return isset($this->storage[$key]); + } + + /** + * Returns the value at the specified key + * + * @param mixed $key + */ + public function offsetGet($key): mixed + { + $ret = null; + if (!$this->offsetExists($key)) { + return $ret; + } + $ret = $this->storage[$key]; + + return $ret; + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + */ + public function offsetSet($key, $value): void + { + if (is_null($key)) { + $this->append($value); + return; + } + $this->storage[$key] = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + */ + public function offsetUnset($key): void + { + if ($this->offsetExists($key)) { + unset($this->storage[$key]); + } + } + + /** + * Serialize an ArrayObject + * + * @return string + */ + public function serialize() + { + return serialize(get_object_vars($this)); + } + + /** + * Sets the behavior flags + * + * @param int $flags + * @return void + */ + public function setFlags($flags) + { + $this->flag = $flags; + } + + /** + * Sets the iterator classname for the ArrayObject + * + * @param string $class + * @return void + */ + public function setIteratorClass($class) + { + if (class_exists($class)) { + $this->iteratorClass = $class; + + return ; + } + + if (mb_strpos($class, '\\') === 0) { + $class = '\\' . $class; + if (class_exists($class)) { + $this->iteratorClass = $class; + + return ; + } + } + + throw new InvalidArgumentException('The iterator class does not exist'); + } + + /** + * Sort the entries with a user-defined comparison function and maintain key association + * + * @param callable $function + * @return void + */ + public function uasort($function) + { + if (is_callable($function)) { + uasort($this->storage, $function); + } + } + + /** + * Sort the entries by keys using a user-defined comparison function + * + * @param callable $function + * @return void + */ + public function uksort($function) + { + if (is_callable($function)) { + uksort($this->storage, $function); + } + } + + /** + * Unserialize an ArrayObject + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $ar = unserialize($data); + $this->setFlags($ar['flag']); + $this->exchangeArray($ar['storage']); + $this->setIteratorClass($ar['iteratorClass']); + foreach ($ar as $k => $v) { + switch ($k) { + case 'flag': + $this->setFlags($v); + break; + case 'storage': + $this->exchangeArray($v); + break; + case 'iteratorClass': + $this->setIteratorClass($v); + break; + case 'protectedProperties': + break; + default: + $this->__set($k, $v); + } + } + } + + /** + * Validates whether the given key is a protected property. + * + * @param string $key The key to validate + * @throws InvalidArgumentException when key is invalid + */ + protected function validateKeyUsage($key) + { + if (in_array($key, $this->protectedProperties)) { + throw new InvalidArgumentException("{$key} is a protected property, use a different key"); + } + } + + /** + * Returns whether the given value is in the underlying array. + */ + public function contains($value): bool + { + return in_array($value, $this->storage); + } +} diff --git a/lib/classes/StudipForm.class.php b/lib/classes/StudipForm.class.php deleted file mode 100644 index 024e2e3..0000000 --- a/lib/classes/StudipForm.class.php +++ /dev/null @@ -1,590 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -use Studip\Button, Studip\LinkButton; - -/** -* Class to build Studip HTML forms -* -* @deprecated -* -* @access public -* @author André Noack -* @package -**/ -class StudipForm { - - var $form_name; - - var $field_attributes_default = []; - - var $form_fields = []; - - var $form_buttons = []; - - var $persistent_values = true; - - var $form_values = []; - - var $value_changed = []; - - - static function TimestampToSQLDate($tstamp){ - return date("Y-m-d", $tstamp); - } - - static function SQLDateToTimestamp($sqldate){ - $date_values = explode("-", $sqldate); //YYYY-MM-DD - if (checkdate((int)$date_values[1],(int)$date_values[2],(int)$date_values[0])){ - return mktime(12,0,0,$date_values[1],$date_values[2],$date_values[0]); - } else { - return false; - } - } - - static function _GetRawFieldValue($field_name, $form_name) { - return Request::get($form_name. '_' . $field_name); - } - - static function _IsSended($form_name){ - return Request::get($form_name . "_" . md5("is_sended")) !== null; - } - - static function _IsClicked($button, $form_name){ - return Request::submitted($form_name . "_" . $button); - } - - function __construct($form_fields, $form_buttons, $form_name = "studipform", $persistent_values = true) { - $this->form_name = $form_name; - $this->persistent_values = $persistent_values; - $this->form_fields = $form_fields; - $this->form_buttons = $form_buttons; - if ($this->persistent_values){ - $this->form_values =& $_SESSION["_p_values"]["_" . $this->form_name . "_values"]; - } - if ($this->isSended()){ - foreach ($this->form_fields as $name => $foo){ - if (empty($foo['disabled'])) { - if ( ($field_value = Request::get($this->form_name . "_" . $name)) !== null) { - $new_form_values[$name] = trim($field_value); - } elseif ( is_array($field_value = Request::getArray($this->form_name . "_" . $name))) { - foreach ($field_value as $key => $value){ - $new_form_values[$name][$key] = trim($value); - } - } else { - $new_form_values[$name] = null; - } - } - } - foreach ($this->form_fields as $name => $value){ - if (empty($value['disabled'])) { - if ($value['type'] == 'combo'){ - if ($this->form_values[$name] != $new_form_values[$value['text']]){ //textfeld wurde verändert - $new_form_values[$name] = $new_form_values[$value['text']]; - } else if ($this->form_values[$name] != $new_form_values[$value['select']] && !$new_form_values[$value['text']]){ //textfeld nicht geändert, select geändert - $new_form_values[$name] = $new_form_values[$value['select']]; - } else { - $new_form_values[$name] = $this->form_values[$name]; - } - } - if ($value['type'] == 'date'){ - $new_form_values[$name] = Request::int($this->form_name . "_" . $name . "_year") . "-" - . sprintf('%02s', Request::int($this->form_name . "_" . $name . "_month")) . "-" - . sprintf('%02s', Request::int($this->form_name . "_" . $name . "_day")); - } - if($value['type'] == 'datepicker'){ - $date = explode('.',Request::get($this->form_name . "_" . $name)); - $new_form_values[$name] = $date[2] . "-" - . sprintf('%02s', $date[1]) . "-" - . sprintf('%02s', $date[0]); - } - if ($value['type'] == 'time'){ - $new_form_values[$name] = sprintf('%02s', Request::int($this->form_name . "_" . $name . "_hours")) . ":" - . sprintf('%02s', Request::int($this->form_name . "_" . $name . "_minutes")); - } - if ($value['type'] == 'checkbox'){ - $new_form_values[$name] = Request::int($this->form_name . "_" . $name, 0); - } - if ( (isset($this->form_values[$name]) && $this->form_values[$name] != $new_form_values[$name]) - || (!isset($this->form_values[$name]) && !empty($new_form_values[$name]) && !empty($this->form_fields[$name]['default_value']) - && $new_form_values[$name] != $this->form_fields[$name]['default_value']) ){ - $this->value_changed[$name] = true; - } - } - } - $this->form_values = array_merge((array)$this->form_values, (array)$new_form_values); - } - } - - function getDefaultValues(){ - foreach ($this->form_fields as $name => $value){ - $this->form_values[$name] = $value['default_value']; - } - } - - function checkDefaultValues(){ - if (is_array($this->form_values)){ - foreach ($this->form_fields as $name => $value){ - if (!$value['ignore_check']){ - if((is_null($this->form_values[$name]) ? 0 : $this->form_values[$name]) - != (is_null($value['default_value']) ? 0 : $value['default_value']) ){ - return true; - } - } - } - } - return false; - } - - function getFormField($name, $attributes = false, $default = false, $subtype = false){ - if (!$attributes){ - $attributes = $this->field_attributes_default; - } - if (empty($default)) { - if (isset($this->form_values[$name])) { - $default = $this->form_values[$name]; - } else { - $default = $this->form_fields[$name]['default_value'] ?? ''; - } - } - if (!empty($this->form_fields[$name]['attributes']) && is_array($this->form_fields[$name]['attributes'])) { - $attributes = array_merge((array)$attributes, (array)$this->form_fields[$name]['attributes']); - } - - if (!empty($this->form_fields[$name]['disabled'])) { - $attributes['disabled'] = 'disabled'; - } - - if (!empty($this->form_fields[$name]['required'])) { - $attributes['required'] = 'required'; - } - - if (empty($attributes['id'])) { - $attributes['id'] = $this->form_name . '_' . $name; - } - - if($this->form_fields[$name]['type']){ - $method = "getFormField" . $this->form_fields[$name]['type']; - return $this->$method($name,$attributes,$default,$subtype); - } - } - - function getFormFieldNoForm($name, $attributes, $default){ - $ret = "\ngetAttributes($attributes); - $ret .= ">"; - if(is_array($default)) $default = join('; ', $default); - $ret .= htmlReady($default,1,1); - $ret .= ""; - if (!$attributes['disabled']) $ret .= $this->getHiddenField($name, $default); - return $ret; - } - - function getFormFieldText($name, $attributes, $default){ - $ret = "\nform_name}_{$name}\" " . (($default) ? "value=\"".htmlReady($default)."\" " : ""); - $ret .= $this->getAttributes($attributes); - $ret .= ">"; - return $ret; - } - - function getFormFieldCheckbox($name, $attributes, $default){ - $ret = "\nform_name}_{$name}\" value=\"1\"" . (($default) ? " checked " : ""); - $ret .= $this->getAttributes($attributes); - $ret .= ">"; - return $ret; - } - - function getFormFieldRadio($name, $attributes, $default, $subtype){ - if (is_array($this->form_fields[$name]['options'])){ - $options = $this->form_fields[$name]['options']; - } else if ($this->form_fields[$name]['options_callback']){ - $options = call_user_func($this->form_fields[$name]['options_callback'],$this,$name); - } - if($subtype !== false){ - return $this->getOneRadio($name, $attributes, ($default == $options[$subtype]['value']), $subtype); - } else { - $ret = '
'; - for ($i = 0; $i < count($options); ++$i){ - $ret .= $this->getOneRadio($name, $attributes, ($default == $options[$i]['value']), $i); - $ret .= "\n" . $this->form_fields[$name]['separator']; - } - $ret .= '
'; - } - return $ret; - } - - function getOneRadio($name, $attributes, $default, $subtype){ - $attributes['id'] = $this->form_name . '_' . $name . '_' . $subtype; - $ret = "\nform_name}_{$name}\" value=\"{$this->form_fields[$name]['options'][$subtype]['value']}\"" . (($default) ? " checked " : ""); - $ret .= $this->getAttributes($attributes); - $ret .= ">"; - $attributes['for'] = $attributes['id']; - unset($attributes['id']); - $ret .= $this->getFormFieldCaption($this->form_fields[$name]['options'][$subtype]['name'], $attributes); - return $ret; - } - - function getFormFieldTextarea($name, $attributes, $default){ - $ret = "\n"; - return $ret; - } - - function getFormFieldDatepicker($name, $attributes, $default) - { - $date_values = explode("-", $default); //YYYY-MM-DD - $value = ''; - $ret = ''; - if(count($date_values)==3){ - $value = $date_values[2]. '.' . $date_values[1]. '.' .$date_values[0]; - } - $ret .= $this->getFormFieldText($name, array_merge(['size'=>11,'maxlength'=>11], (array)$attributes), $value); - $ret .=''; - return $ret; - } - - function getFormFieldTime($name, $attributes, $default) { - $date_values = explode(":", $default); //hh:mm - $ret = '
'; - unset($attributes['id']); - $ret .= $this->getFormFieldText($name . "_hours", array_merge(['size'=>2,'maxlength'=>2], (array)$attributes), $date_values[0]); - $ret .= "\n" . $this->form_fields[$name]['separator']; - $ret .= $this->getFormFieldText($name . "_minutes", array_merge(['size'=>2,'maxlength'=>2], (array)$attributes), $date_values[1]); - $ret .= '
'; - return $ret; - } - - function getFormFieldSelect($name, $attributes, $default){ - $ret = "\n"; - return $ret; - } - - function getFormFieldSelectBox($name, $attributes, $default){ - $box_attributes = $this->form_fields[$name]['box_attributes'] ? $this->form_fields[$name]['box_attributes'] : []; - $ret = "\n
getAttributes($box_attributes)." >"; - $ret .= "\n
"; - unset($attributes['id']); - if ($this->form_fields[$name]['multiple']) { - $element = 'checkbox'; - $element_name = $this->form_name . '_' . $name . '[]'; - } else { - $element = 'radio'; - $element_name = $this->form_name . '_' . $name; - } - if ($default === false){ - $default = $this->form_fields[$name]['default_value']; - } - if (is_array($this->form_fields[$name]['options'])){ - $options = $this->form_fields[$name]['options']; - } else if ($this->form_fields[$name]['options_callback']){ - $options = call_user_func($this->form_fields[$name]['options_callback'],$this,$name); - } - for ($i = 0; $i < count($options); ++$i) { - $options_name = (is_array($options[$i])) ? $options[$i]['name'] : $options[$i]; - $options_value = (is_array($options[$i])) ? $options[$i]['value'] : $options[$i]; - $options_attributes = (is_array($options[$i])) ? $options[$i]['attributes'] : []; - $selected = false; - if ((is_array($default) && in_array("" . $options_value, $default)) - || (!is_array($default) && ($default == "" . $options_value))){ - $selected = true; - } - if ($this->form_fields[$name]['max_length']){ - $options_name = my_substr($options_name,0, $this->form_fields[$name]['max_length']); - } - $id = $this->form_name . '_' . $name . '_' . $i; - $ret .= "\n
getAttributes($attributes); - $ret .= ">"; - $ret .= "\n
"; - } - $ret .= "\n
\n
"; - return $ret; - } - - function getFormFieldCombo($name, $attributes, $default , $subtype = false){ - $ret = '
'; - unset($attributes['id']); - $combo_text_name = $this->form_fields[$name]['text']; - $combo_select_name = $this->form_fields[$name]['select']; - $select_attributes = ['onChange' => "document.{$this->form_name}.{$this->form_name}_{$combo_text_name}.value=" - ."document.{$this->form_name}.{$this->form_name}_{$combo_select_name}.options[document.{$this->form_name}.{$this->form_name}_{$combo_select_name}.selectedIndex].text; "]; - if (is_array($attributes)){ - $select_attributes = array_merge((array)$select_attributes, (array)$attributes); - } - if (!$subtype){ - $ret .= "\n" . $this->getFormFieldSelect($combo_select_name, $select_attributes, $default); - $ret .= "\n" . $this->form_fields[$name]['separator']; - $ret .= $this->getFormFieldText($combo_text_name, $attributes, $default); - } else if ($subtype == "text"){ - $ret .= "\n" . $this->getFormFieldText($combo_text_name, $attributes, $default); - } else { - $ret .= $this->getFormFieldSelect($combo_select_name, $select_attributes, $default); - } - $ret .= "
"; - return $ret; - } - - function getFormButton($name, $attributes = []){ - if (!empty($this->form_buttons[$name]['attributes']) && is_array($this->form_buttons[$name]['attributes'])) { - $attributes = array_merge((array)$attributes, (array)$this->form_buttons[$name]['attributes']); - } - if (empty($this->form_buttons[$name]['is_picture'])) { - if (isset($this->form_buttons[$name]['info']) && !isset($attributes['title'])) { - $attributes['title'] = $this->form_buttons[$name]['info']; - } - $caption = $this->form_buttons[$name]['caption'] ? $this->form_buttons[$name]['caption'] : $this->form_buttons[$name]['type']; - if (!empty($this->form_buttons[$name]['type']) && in_array($this->form_buttons[$name]['type'], ['cancel', 'accept'])) { - $create = 'create' . $this->form_buttons[$name]['type']; - } else { - $create = 'create'; - } - $ret = Button::$create($caption, $this->form_name . "_" . $name, $attributes); - } else { - // Yes, this is kinda ugly - $ret = Assets::input($this->form_buttons[$name]['type'], - tooltip2($this->form_buttons[$name]['info']) - + (array)$attributes - + ['name' => $this->form_name . '_' . $name]); - } - return $ret; - } - - function getFormFieldCaption($name, $attributes = false){ - $_name = $name; - if (!isset($attributes['for'])) { - $attributes['for'] = $this->form_name . '_' . $name; - } - if (isset($this->form_fields[$name]['caption'])) { - $name = $this->form_fields[$name]['caption']; - } - $res = ''; - return $res; - } - - function getFormFieldInfo($name){ - return tooltipIcon($this->form_fields[$name]['info']); - } - - function getFormStart($action = false, $attributes = false){ - if (!$action){ - $action = URLHelper::getLink(); - } - $ret = "\nform_name}\" " . $this->getAttributes($attributes) . ">"; - $ret .= CSRFProtection::tokenTag(); - return $ret; - } - - function getFormEnd(){ - $ret = ""; - foreach ($this->form_fields as $field_name => $field_content){ - if ($field_content['type'] == 'hidden'){ - $ret .= $this->getHiddenField($field_name); - } - } - $ret .= $this->getHiddenField(md5("is_sended"),1); - return $ret . "\n"; - } - - function getFormFieldValue($name){ - if (isset($this->form_values[$name])){ - $value = $this->form_values[$name]; - } else { - $value = $this->form_fields[$name]['default_value'] ?? ''; - } - return $value; - } - - function getFormFieldsByName($only_editable = false){ - $ret = []; - foreach ($this->form_fields as $name => $detail){ - if( !($only_editable && ($detail['type'] == 'noform' || !empty($detail['disabled']))) ){ - $ret[] = $name; - } - } - return $ret; - } - - function getHiddenField($name, $value = false){ - if (!$value){ - $value = $this->getFormFieldValue($name); - } - return "\nform_name}_{$name}\" value=\"".htmlReady($value)."\">"; - } - - function doFormReset(){ - $this->form_values = null; - return true; - } - - function isChanged($name){ - return isset($this->value_changed[$name]); - } - - function getRawFieldValue($field_name) { - return self::_GetRawFieldValue($field_name, $this->form_name); - } - - function isSended() { - return self::_IsSended($this->form_name); - } - - function isClicked($button) { - return self::_IsClicked($button, $this->form_name); - } - - function getClickedKillButton(){ - foreach($this->form_buttons as $name => $value){ - if ($value['is_kill_button']){ - if ($this->isClicked($name)){ - return $name; - } - } - } - return false; - } - - function getAttributes($attributes){ - $ret = ""; - if (is_array($attributes)) { - foreach($attributes as $key => $value){ - $ret .= " ".$key."=\"".htmlReady($value)."\""; - } - } - return $ret; - } - - function getFormFieldRequired($name){ - if ($this->form_fields[$name]['required']) - return "\n" . '*'; - else return ""; - } - - -} - -// test & demo -/* -function getSomeOptions(&$caller, $name){ - $options[] = md5($name); - foreach($caller->form_fields as $key => $value){ - $options[]=$key; - } - return $options; -} - -page_open(array("sess" => "Seminar_Session")); -$_language = DEFAULT_LANGUAGE; -$_language_path = $INSTALLED_LANGUAGES[$_language]["path"]; - -$form_fields = array('text1' => array('type' => 'text', 'caption' => 'Testtextfeld1', 'info' => 'Hier Schwachsinn eingeben'), - 'text2' => array('type' => 'textarea','caption' => 'Testtextfeld2', 'info' => 'Hier Schwachsinn eingeben','default' => 'blablubb'), - 'select1' => array('type' => 'select', 'options' => array( array('name' =>_("UND"),'value' => 'AND'), - array('name' =>_("ODER"),'value' => 'OR'))), - 'select2' => array('type' => 'select','options_callback' => 'getSomeOptions'), - 'combo1_text' => array('type' => 'text'), - 'combo1_select' => array('type' => 'select', 'options' => array("",_("Eins"),_("Zwei"), _("Drei"))), - 'combo1' => array('type' => 'combo', 'text' => 'combo1_text', 'select' => 'combo1_select', 'separator' => '--'), - 'date1' => array('type' => 'date', 'separator' => '.', 'default' => 'YYYY-MM-DD'), - 'checkbox' => array('type' => 'checkbox', 'caption' => 'Tolle Checkbox ?', value => '1'), - 'radio_group' => array('type' => 'radio', 'separator' => " ", 'options' => array( array('name' =>_("UND"),'value' => 'AND'), - array('name' =>_("ODER"),'value' => 'OR'), - array('name' =>_("NICHT"),'value' => 'NOT'))) - ); - -$form_buttons = array('send' => array('type' => 'abschicken', 'info' => _("Dieses Formular abschicken")), - 'not_send' => array('type' => 'abbrechen', 'info' => _("Eingabe abbrechen"))); - -$test = new StudipForm($form_fields, $form_buttons); -echo "
"; -echo $test->getFormStart(); -echo $test->getFormFieldCaption("text1"); -echo " " . $test->getFormFieldInfo("text1") . " "; -echo $test->getFormField("text1"); -echo $test->getFormField("text2"); -echo $test->getFormFieldCaption("select1"); -echo " " . $test->getFormFieldInfo("select1") . " "; -echo $test->getFormField("select1"); -echo $test->getFormFieldCaption("select2"); -echo " " . $test->getFormFieldInfo("select2") . " "; -echo $test->getFormField("select2"); -echo $test->getFormField("date1", array('style' => 'vertical-align:middle')); -echo "
" . $test->getFormField("combo1",array('style' => 'vertical-align:middle')); -echo $test->getFormFieldCaption("checkbox", array('style' => 'vertical-align:middle')); -echo " " . $test->getFormField("checkbox",array('style' => 'vertical-align:middle')); -echo "
" . $test->getFormField("radio_group",array('style' => 'vertical-align:middle;font-size:10pt;')); -echo $test->getFormButton("send",array('style' => 'vertical-align:middle;')); -echo $test->getFormEnd(); -echo "
"; -echo "
";
-page_close();
-*/
-?>
diff --git a/lib/classes/StudipForm.php b/lib/classes/StudipForm.php
new file mode 100644
index 0000000..024e2e3
--- /dev/null
+++ b/lib/classes/StudipForm.php
@@ -0,0 +1,590 @@
+
+// +---------------------------------------------------------------------------+
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or any later version.
+// +---------------------------------------------------------------------------+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+// +---------------------------------------------------------------------------+
+
+use Studip\Button, Studip\LinkButton;
+
+/**
+* Class to build Studip HTML forms
+*
+* @deprecated
+*
+* @access   public
+* @author   André Noack 
+* @package
+**/
+class StudipForm {
+
+    var $form_name;
+
+    var $field_attributes_default = [];
+
+    var $form_fields = [];
+
+    var $form_buttons = [];
+
+    var $persistent_values = true;
+
+    var $form_values = [];
+
+    var $value_changed = [];
+
+
+    static function TimestampToSQLDate($tstamp){
+        return date("Y-m-d", $tstamp);
+    }
+
+    static function SQLDateToTimestamp($sqldate){
+        $date_values = explode("-", $sqldate); //YYYY-MM-DD
+        if (checkdate((int)$date_values[1],(int)$date_values[2],(int)$date_values[0])){
+            return mktime(12,0,0,$date_values[1],$date_values[2],$date_values[0]);
+        } else {
+            return false;
+        }
+    }
+
+    static function _GetRawFieldValue($field_name, $form_name) {
+        return Request::get($form_name. '_' . $field_name);
+    }
+
+    static function _IsSended($form_name){
+        return Request::get($form_name . "_" . md5("is_sended")) !== null;
+    }
+
+    static function _IsClicked($button, $form_name){
+        return Request::submitted($form_name . "_" . $button);
+    }
+
+    function __construct($form_fields, $form_buttons, $form_name = "studipform", $persistent_values = true) {
+        $this->form_name = $form_name;
+        $this->persistent_values = $persistent_values;
+        $this->form_fields = $form_fields;
+        $this->form_buttons = $form_buttons;
+        if ($this->persistent_values){
+            $this->form_values =& $_SESSION["_p_values"]["_" . $this->form_name . "_values"];
+        }
+        if ($this->isSended()){
+            foreach ($this->form_fields as $name => $foo){
+                if (empty($foo['disabled'])) {
+                    if ( ($field_value = Request::get($this->form_name . "_" . $name)) !== null) {
+                            $new_form_values[$name] = trim($field_value);
+                    } elseif ( is_array($field_value = Request::getArray($this->form_name . "_" . $name))) {
+                        foreach ($field_value as $key => $value){
+                            $new_form_values[$name][$key] = trim($value);
+                        }
+                    } else {
+                        $new_form_values[$name] = null;
+                    }
+                }
+            }
+            foreach ($this->form_fields as $name => $value){
+                if (empty($value['disabled'])) {
+                    if ($value['type'] == 'combo'){
+                        if ($this->form_values[$name] != $new_form_values[$value['text']]){ //textfeld wurde verändert
+                            $new_form_values[$name] = $new_form_values[$value['text']];
+                        } else if ($this->form_values[$name] != $new_form_values[$value['select']] && !$new_form_values[$value['text']]){ //textfeld nicht geändert, select geändert
+                            $new_form_values[$name] = $new_form_values[$value['select']];
+                        } else {
+                            $new_form_values[$name] = $this->form_values[$name];
+                        }
+                    }
+                    if ($value['type'] == 'date'){
+                        $new_form_values[$name] = Request::int($this->form_name . "_" . $name . "_year") . "-"
+                                                . sprintf('%02s', Request::int($this->form_name . "_" . $name . "_month")) . "-"
+                                                . sprintf('%02s', Request::int($this->form_name . "_" . $name . "_day"));
+                    }
+                    if($value['type'] == 'datepicker'){
+                        $date = explode('.',Request::get($this->form_name . "_" . $name));
+                        $new_form_values[$name] = $date[2] . "-"
+                                                . sprintf('%02s', $date[1]) . "-"
+                                                . sprintf('%02s', $date[0]);
+                    }
+                    if ($value['type'] == 'time'){
+                        $new_form_values[$name] = sprintf('%02s', Request::int($this->form_name . "_" . $name . "_hours")) . ":"
+                                                . sprintf('%02s', Request::int($this->form_name . "_" . $name . "_minutes"));
+                    }
+                    if ($value['type'] == 'checkbox'){
+                        $new_form_values[$name] = Request::int($this->form_name . "_" . $name, 0);
+                    }
+                    if ( (isset($this->form_values[$name]) && $this->form_values[$name] != $new_form_values[$name])
+                        || (!isset($this->form_values[$name]) && !empty($new_form_values[$name]) && !empty($this->form_fields[$name]['default_value'])
+                            && $new_form_values[$name] != $this->form_fields[$name]['default_value']) ){
+                        $this->value_changed[$name] = true;
+                    }
+                }
+            }
+            $this->form_values = array_merge((array)$this->form_values, (array)$new_form_values);
+        }
+    }
+
+    function getDefaultValues(){
+        foreach ($this->form_fields as $name => $value){
+            $this->form_values[$name] = $value['default_value'];
+        }
+    }
+
+    function checkDefaultValues(){
+        if (is_array($this->form_values)){
+            foreach ($this->form_fields as $name => $value){
+                if (!$value['ignore_check']){
+                    if((is_null($this->form_values[$name]) ? 0 : $this->form_values[$name])
+                        != (is_null($value['default_value']) ? 0 : $value['default_value']) ){
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    function getFormField($name, $attributes = false, $default = false, $subtype = false){
+        if (!$attributes){
+            $attributes = $this->field_attributes_default;
+        }
+        if (empty($default)) {
+            if (isset($this->form_values[$name])) {
+                $default = $this->form_values[$name];
+            } else {
+                $default = $this->form_fields[$name]['default_value'] ?? '';
+            }
+        }
+        if (!empty($this->form_fields[$name]['attributes']) && is_array($this->form_fields[$name]['attributes'])) {
+            $attributes = array_merge((array)$attributes, (array)$this->form_fields[$name]['attributes']);
+        }
+
+        if (!empty($this->form_fields[$name]['disabled'])) {
+            $attributes['disabled'] = 'disabled';
+        }
+
+        if (!empty($this->form_fields[$name]['required'])) {
+            $attributes['required'] = 'required';
+        }
+
+        if (empty($attributes['id'])) {
+            $attributes['id'] = $this->form_name . '_' . $name;
+        }
+
+        if($this->form_fields[$name]['type']){
+            $method = "getFormField" . $this->form_fields[$name]['type'];
+            return $this->$method($name,$attributes,$default,$subtype);
+        }
+    }
+
+    function getFormFieldNoForm($name, $attributes, $default){
+        $ret = "\ngetAttributes($attributes);
+        $ret .= ">";
+        if(is_array($default)) $default = join('; ', $default);
+        $ret .= htmlReady($default,1,1);
+        $ret .= "";
+        if (!$attributes['disabled']) $ret .= $this->getHiddenField($name, $default);
+        return $ret;
+    }
+
+    function getFormFieldText($name, $attributes, $default){
+        $ret = "\nform_name}_{$name}\" " . (($default) ? "value=\"".htmlReady($default)."\" " : "");
+        $ret .= $this->getAttributes($attributes);
+        $ret .= ">";
+        return $ret;
+    }
+
+    function getFormFieldCheckbox($name, $attributes, $default){
+        $ret = "\nform_name}_{$name}\" value=\"1\"" . (($default) ? " checked " : "");
+        $ret .= $this->getAttributes($attributes);
+        $ret .= ">";
+        return $ret;
+    }
+
+    function getFormFieldRadio($name, $attributes, $default, $subtype){
+        if (is_array($this->form_fields[$name]['options'])){
+            $options = $this->form_fields[$name]['options'];
+        } else if ($this->form_fields[$name]['options_callback']){
+            $options = call_user_func($this->form_fields[$name]['options_callback'],$this,$name);
+        }
+        if($subtype !== false){
+            return $this->getOneRadio($name, $attributes, ($default == $options[$subtype]['value']), $subtype);
+        } else {
+            $ret = '
'; + for ($i = 0; $i < count($options); ++$i){ + $ret .= $this->getOneRadio($name, $attributes, ($default == $options[$i]['value']), $i); + $ret .= "\n" . $this->form_fields[$name]['separator']; + } + $ret .= '
'; + } + return $ret; + } + + function getOneRadio($name, $attributes, $default, $subtype){ + $attributes['id'] = $this->form_name . '_' . $name . '_' . $subtype; + $ret = "\nform_name}_{$name}\" value=\"{$this->form_fields[$name]['options'][$subtype]['value']}\"" . (($default) ? " checked " : ""); + $ret .= $this->getAttributes($attributes); + $ret .= ">"; + $attributes['for'] = $attributes['id']; + unset($attributes['id']); + $ret .= $this->getFormFieldCaption($this->form_fields[$name]['options'][$subtype]['name'], $attributes); + return $ret; + } + + function getFormFieldTextarea($name, $attributes, $default){ + $ret = "\n"; + return $ret; + } + + function getFormFieldDatepicker($name, $attributes, $default) + { + $date_values = explode("-", $default); //YYYY-MM-DD + $value = ''; + $ret = ''; + if(count($date_values)==3){ + $value = $date_values[2]. '.' . $date_values[1]. '.' .$date_values[0]; + } + $ret .= $this->getFormFieldText($name, array_merge(['size'=>11,'maxlength'=>11], (array)$attributes), $value); + $ret .=''; + return $ret; + } + + function getFormFieldTime($name, $attributes, $default) { + $date_values = explode(":", $default); //hh:mm + $ret = '
'; + unset($attributes['id']); + $ret .= $this->getFormFieldText($name . "_hours", array_merge(['size'=>2,'maxlength'=>2], (array)$attributes), $date_values[0]); + $ret .= "\n" . $this->form_fields[$name]['separator']; + $ret .= $this->getFormFieldText($name . "_minutes", array_merge(['size'=>2,'maxlength'=>2], (array)$attributes), $date_values[1]); + $ret .= '
'; + return $ret; + } + + function getFormFieldSelect($name, $attributes, $default){ + $ret = "\n"; + return $ret; + } + + function getFormFieldSelectBox($name, $attributes, $default){ + $box_attributes = $this->form_fields[$name]['box_attributes'] ? $this->form_fields[$name]['box_attributes'] : []; + $ret = "\n
getAttributes($box_attributes)." >"; + $ret .= "\n
"; + unset($attributes['id']); + if ($this->form_fields[$name]['multiple']) { + $element = 'checkbox'; + $element_name = $this->form_name . '_' . $name . '[]'; + } else { + $element = 'radio'; + $element_name = $this->form_name . '_' . $name; + } + if ($default === false){ + $default = $this->form_fields[$name]['default_value']; + } + if (is_array($this->form_fields[$name]['options'])){ + $options = $this->form_fields[$name]['options']; + } else if ($this->form_fields[$name]['options_callback']){ + $options = call_user_func($this->form_fields[$name]['options_callback'],$this,$name); + } + for ($i = 0; $i < count($options); ++$i) { + $options_name = (is_array($options[$i])) ? $options[$i]['name'] : $options[$i]; + $options_value = (is_array($options[$i])) ? $options[$i]['value'] : $options[$i]; + $options_attributes = (is_array($options[$i])) ? $options[$i]['attributes'] : []; + $selected = false; + if ((is_array($default) && in_array("" . $options_value, $default)) + || (!is_array($default) && ($default == "" . $options_value))){ + $selected = true; + } + if ($this->form_fields[$name]['max_length']){ + $options_name = my_substr($options_name,0, $this->form_fields[$name]['max_length']); + } + $id = $this->form_name . '_' . $name . '_' . $i; + $ret .= "\n
getAttributes($attributes); + $ret .= ">"; + $ret .= "\n
"; + } + $ret .= "\n
\n
"; + return $ret; + } + + function getFormFieldCombo($name, $attributes, $default , $subtype = false){ + $ret = '
'; + unset($attributes['id']); + $combo_text_name = $this->form_fields[$name]['text']; + $combo_select_name = $this->form_fields[$name]['select']; + $select_attributes = ['onChange' => "document.{$this->form_name}.{$this->form_name}_{$combo_text_name}.value=" + ."document.{$this->form_name}.{$this->form_name}_{$combo_select_name}.options[document.{$this->form_name}.{$this->form_name}_{$combo_select_name}.selectedIndex].text; "]; + if (is_array($attributes)){ + $select_attributes = array_merge((array)$select_attributes, (array)$attributes); + } + if (!$subtype){ + $ret .= "\n" . $this->getFormFieldSelect($combo_select_name, $select_attributes, $default); + $ret .= "\n" . $this->form_fields[$name]['separator']; + $ret .= $this->getFormFieldText($combo_text_name, $attributes, $default); + } else if ($subtype == "text"){ + $ret .= "\n" . $this->getFormFieldText($combo_text_name, $attributes, $default); + } else { + $ret .= $this->getFormFieldSelect($combo_select_name, $select_attributes, $default); + } + $ret .= "
"; + return $ret; + } + + function getFormButton($name, $attributes = []){ + if (!empty($this->form_buttons[$name]['attributes']) && is_array($this->form_buttons[$name]['attributes'])) { + $attributes = array_merge((array)$attributes, (array)$this->form_buttons[$name]['attributes']); + } + if (empty($this->form_buttons[$name]['is_picture'])) { + if (isset($this->form_buttons[$name]['info']) && !isset($attributes['title'])) { + $attributes['title'] = $this->form_buttons[$name]['info']; + } + $caption = $this->form_buttons[$name]['caption'] ? $this->form_buttons[$name]['caption'] : $this->form_buttons[$name]['type']; + if (!empty($this->form_buttons[$name]['type']) && in_array($this->form_buttons[$name]['type'], ['cancel', 'accept'])) { + $create = 'create' . $this->form_buttons[$name]['type']; + } else { + $create = 'create'; + } + $ret = Button::$create($caption, $this->form_name . "_" . $name, $attributes); + } else { + // Yes, this is kinda ugly + $ret = Assets::input($this->form_buttons[$name]['type'], + tooltip2($this->form_buttons[$name]['info']) + + (array)$attributes + + ['name' => $this->form_name . '_' . $name]); + } + return $ret; + } + + function getFormFieldCaption($name, $attributes = false){ + $_name = $name; + if (!isset($attributes['for'])) { + $attributes['for'] = $this->form_name . '_' . $name; + } + if (isset($this->form_fields[$name]['caption'])) { + $name = $this->form_fields[$name]['caption']; + } + $res = ''; + return $res; + } + + function getFormFieldInfo($name){ + return tooltipIcon($this->form_fields[$name]['info']); + } + + function getFormStart($action = false, $attributes = false){ + if (!$action){ + $action = URLHelper::getLink(); + } + $ret = "\n
form_name}\" " . $this->getAttributes($attributes) . ">"; + $ret .= CSRFProtection::tokenTag(); + return $ret; + } + + function getFormEnd(){ + $ret = ""; + foreach ($this->form_fields as $field_name => $field_content){ + if ($field_content['type'] == 'hidden'){ + $ret .= $this->getHiddenField($field_name); + } + } + $ret .= $this->getHiddenField(md5("is_sended"),1); + return $ret . "\n
"; + } + + function getFormFieldValue($name){ + if (isset($this->form_values[$name])){ + $value = $this->form_values[$name]; + } else { + $value = $this->form_fields[$name]['default_value'] ?? ''; + } + return $value; + } + + function getFormFieldsByName($only_editable = false){ + $ret = []; + foreach ($this->form_fields as $name => $detail){ + if( !($only_editable && ($detail['type'] == 'noform' || !empty($detail['disabled']))) ){ + $ret[] = $name; + } + } + return $ret; + } + + function getHiddenField($name, $value = false){ + if (!$value){ + $value = $this->getFormFieldValue($name); + } + return "\nform_name}_{$name}\" value=\"".htmlReady($value)."\">"; + } + + function doFormReset(){ + $this->form_values = null; + return true; + } + + function isChanged($name){ + return isset($this->value_changed[$name]); + } + + function getRawFieldValue($field_name) { + return self::_GetRawFieldValue($field_name, $this->form_name); + } + + function isSended() { + return self::_IsSended($this->form_name); + } + + function isClicked($button) { + return self::_IsClicked($button, $this->form_name); + } + + function getClickedKillButton(){ + foreach($this->form_buttons as $name => $value){ + if ($value['is_kill_button']){ + if ($this->isClicked($name)){ + return $name; + } + } + } + return false; + } + + function getAttributes($attributes){ + $ret = ""; + if (is_array($attributes)) { + foreach($attributes as $key => $value){ + $ret .= " ".$key."=\"".htmlReady($value)."\""; + } + } + return $ret; + } + + function getFormFieldRequired($name){ + if ($this->form_fields[$name]['required']) + return "\n" . '*'; + else return ""; + } + + +} + +// test & demo +/* +function getSomeOptions(&$caller, $name){ + $options[] = md5($name); + foreach($caller->form_fields as $key => $value){ + $options[]=$key; + } + return $options; +} + +page_open(array("sess" => "Seminar_Session")); +$_language = DEFAULT_LANGUAGE; +$_language_path = $INSTALLED_LANGUAGES[$_language]["path"]; + +$form_fields = array('text1' => array('type' => 'text', 'caption' => 'Testtextfeld1', 'info' => 'Hier Schwachsinn eingeben'), + 'text2' => array('type' => 'textarea','caption' => 'Testtextfeld2', 'info' => 'Hier Schwachsinn eingeben','default' => 'blablubb'), + 'select1' => array('type' => 'select', 'options' => array( array('name' =>_("UND"),'value' => 'AND'), + array('name' =>_("ODER"),'value' => 'OR'))), + 'select2' => array('type' => 'select','options_callback' => 'getSomeOptions'), + 'combo1_text' => array('type' => 'text'), + 'combo1_select' => array('type' => 'select', 'options' => array("",_("Eins"),_("Zwei"), _("Drei"))), + 'combo1' => array('type' => 'combo', 'text' => 'combo1_text', 'select' => 'combo1_select', 'separator' => '--'), + 'date1' => array('type' => 'date', 'separator' => '.', 'default' => 'YYYY-MM-DD'), + 'checkbox' => array('type' => 'checkbox', 'caption' => 'Tolle Checkbox ?', value => '1'), + 'radio_group' => array('type' => 'radio', 'separator' => " ", 'options' => array( array('name' =>_("UND"),'value' => 'AND'), + array('name' =>_("ODER"),'value' => 'OR'), + array('name' =>_("NICHT"),'value' => 'NOT'))) + ); + +$form_buttons = array('send' => array('type' => 'abschicken', 'info' => _("Dieses Formular abschicken")), + 'not_send' => array('type' => 'abbrechen', 'info' => _("Eingabe abbrechen"))); + +$test = new StudipForm($form_fields, $form_buttons); +echo "
"; +echo $test->getFormStart(); +echo $test->getFormFieldCaption("text1"); +echo " " . $test->getFormFieldInfo("text1") . " "; +echo $test->getFormField("text1"); +echo $test->getFormField("text2"); +echo $test->getFormFieldCaption("select1"); +echo " " . $test->getFormFieldInfo("select1") . " "; +echo $test->getFormField("select1"); +echo $test->getFormFieldCaption("select2"); +echo " " . $test->getFormFieldInfo("select2") . " "; +echo $test->getFormField("select2"); +echo $test->getFormField("date1", array('style' => 'vertical-align:middle')); +echo "
" . $test->getFormField("combo1",array('style' => 'vertical-align:middle')); +echo $test->getFormFieldCaption("checkbox", array('style' => 'vertical-align:middle')); +echo " " . $test->getFormField("checkbox",array('style' => 'vertical-align:middle')); +echo "
" . $test->getFormField("radio_group",array('style' => 'vertical-align:middle;font-size:10pt;')); +echo $test->getFormButton("send",array('style' => 'vertical-align:middle;')); +echo $test->getFormEnd(); +echo "
"; +echo "
";
+page_close();
+*/
+?>
diff --git a/lib/classes/StudipItem.interface.php b/lib/classes/StudipItem.interface.php
deleted file mode 100644
index b575bdc..0000000
--- a/lib/classes/StudipItem.interface.php
+++ /dev/null
@@ -1,72 +0,0 @@
-
- * @copyright   2018-2019
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category    Stud.IP
- * @since       4.5
- */
-
-
-/**
- * This interface provides methods which allow an unified access
- * to basic properties of Stud.IP objects.
- * It is meant to be an extension for SimpleORMap objects.
- */
-interface StudipItem
-{
-    /**
-     * Returns a human-readable name of the object.
-     *
-     * @param bool $long_format If set to true, a long format
-     *     that has the object type as a prefix (course, room etc.)
-     *     is returned. Otherwise only the name is returned.
-     *
-     * @returns string A human-readable string of the object's name.
-     */
-    public function getItemName($long_format = true);
-
-    /**
-     * Returns an URL that points to a page describing or displaying
-     * the object.
-     *
-     * @returns string|null Either the URL to a descriptive page for
-     *     the object or null, if the object does not have such an URL.
-     */
-    public function getItemURL();
-
-    /**
-     * Returns the URL to the avatar image or icon of the object,
-     * if applicable.
-     *
-     * @returns string|null Either the URL to the object's avatar
-     *     or icon or null, if the object does not have an avatar.
-     */
-    public function getItemAvatarURL();
-
-
-    /**
-     * Creates a StudipLink object that links to a page with information
-     * about the StudipItem object.
-     *
-     * @returns StudipLink A StudipLink object for the information page
-     *     of the StudipItem object.
-     */
-    public function getLink() : StudipLink;
-
-
-    /**
-     * @return string A string representation of the item.
-     *     For backward compatibility with existing plugins that have classes
-     *     derived of StudipItem, the return type is not specified.
-     */
-    public function __toString();
-}
diff --git a/lib/classes/StudipItem.php b/lib/classes/StudipItem.php
new file mode 100644
index 0000000..b575bdc
--- /dev/null
+++ b/lib/classes/StudipItem.php
@@ -0,0 +1,72 @@
+
+ * @copyright   2018-2019
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+ * @since       4.5
+ */
+
+
+/**
+ * This interface provides methods which allow an unified access
+ * to basic properties of Stud.IP objects.
+ * It is meant to be an extension for SimpleORMap objects.
+ */
+interface StudipItem
+{
+    /**
+     * Returns a human-readable name of the object.
+     *
+     * @param bool $long_format If set to true, a long format
+     *     that has the object type as a prefix (course, room etc.)
+     *     is returned. Otherwise only the name is returned.
+     *
+     * @returns string A human-readable string of the object's name.
+     */
+    public function getItemName($long_format = true);
+
+    /**
+     * Returns an URL that points to a page describing or displaying
+     * the object.
+     *
+     * @returns string|null Either the URL to a descriptive page for
+     *     the object or null, if the object does not have such an URL.
+     */
+    public function getItemURL();
+
+    /**
+     * Returns the URL to the avatar image or icon of the object,
+     * if applicable.
+     *
+     * @returns string|null Either the URL to the object's avatar
+     *     or icon or null, if the object does not have an avatar.
+     */
+    public function getItemAvatarURL();
+
+
+    /**
+     * Creates a StudipLink object that links to a page with information
+     * about the StudipItem object.
+     *
+     * @returns StudipLink A StudipLink object for the information page
+     *     of the StudipItem object.
+     */
+    public function getLink() : StudipLink;
+
+
+    /**
+     * @return string A string representation of the item.
+     *     For backward compatibility with existing plugins that have classes
+     *     derived of StudipItem, the return type is not specified.
+     */
+    public function __toString();
+}
diff --git a/lib/classes/StudipKing.class.php b/lib/classes/StudipKing.class.php
deleted file mode 100644
index ae5a14e..0000000
--- a/lib/classes/StudipKing.class.php
+++ /dev/null
@@ -1,173 +0,0 @@
-
- * Copyright (C) 2004 - Till Glöggler 
- * Copyright (C) 2009 - Marcus Lunzenauer 
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- */
-
-/**
- * This class awards honours (crowns) to users posting a lot in the bulletin
- * boards, writing a lot of wiki pages and so on.
- *
- * @author    mlunzena
- */
-
-class StudipKing {
-
-    /**
-     * How many of each type should be awarded?
-     */
-    const NUM_KINGS = 1;
-
-    /**
-     * key to use for caching
-     */
-    const CACHE_KEY = 'core/kings';
-
-    /**
-    * store kings also in memory
-    */
-    private static $kings;
-
-    /**
-     * Returns the awards of a user as an associative array consisting of
-     * "award type" => "amount of posts, wiki pages etc." pairs that belong to
-     * this user. If the 2nd parameter is set to true, the values are
-     * descriptional strings instead of the raw numbers.
-     *
-     * @param  string     a string containing the MD5ish ID of the user
-     * @param  bool       TRUE to return descriptional text, FALSE to return
-     *                    raw numbers, which is the default
-     *
-     * @return array      an associative array mapping the awards to an amount
-     */
-    static function is_king($user_id, $textual = FALSE)
-    {
-        $kings = self::get_kings();
-        $result = isset($kings[$user_id]) ? $kings[$user_id] : [];
-        if ($textual) {
-            foreach ($result as $type => $amount) {
-                $result[$type] = self::textual_representation($type, $amount);
-            }
-        }
-        return $result;
-    }
-
-    private static function get_kings()
-    {
-        if (self::$kings === null) {
-            $cache = \Studip\Cache\Factory::getCache();
-
-            # read cache (unserializing a cache miss - FALSE - does not matter)
-            $kings = unserialize($cache->read(self::CACHE_KEY));
-
-            # cache miss, retrieve from database
-            if ($kings === FALSE) {
-                $kings = self::get_kings_uncached();
-                # write to cache with an expiry time of 24 hours
-                $cache->write(self::CACHE_KEY, serialize($kings), 86400);
-            }
-            self::$kings = $kings;
-        }
-        return self::$kings;
-    }
-
-    private static function get_kings_uncached()
-    {
-        $types = words('files forum news voter votes wiki');
-        $kings = [];
-        foreach ($types as $type) {
-            $method = "{$type}_kings";
-            foreach (self::$method() as $user_id => $amount) {
-                if (!isset($kings[$user_id])) {
-                    $kings[$user_id] = [];
-                }
-                $kings[$user_id][$type] = $amount;
-            }
-        }
-        return $kings;
-    }
-
-    private static function select_kings($sql)
-    {
-        $result = [];
-        $stmt = DBManager::get()->query($sql . " ORDER BY num DESC LIMIT 0," . self::NUM_KINGS);
-        foreach ($stmt as $row) {
-            $result[$row["id"]] = $row["num"];
-        }
-        return $result;
-    }
-
-    private static function wiki_kings()
-    {
-        return self::select_kings("
-            SELECT user_id AS id, COUNT(*) AS num
-            FROM (SELECT user_id FROM wiki_pages UNION SELECT user_id FROM wiki_versions) AS `all_wiki_pages`
-            GROUP BY user_id");
-    }
-
-    private static function forum_kings()
-    {
-        $kings = [];
-
-        // sum up postings for all users from all ForumModules available
-        foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) {
-            $table = $plugin->getEntryTableInfo();
-            $query = "SELECT user_id AS id, COUNT(*) AS num FROM ". $table['table'] ." GROUP BY user_id";
-            $new_kings = self::select_kings($query);
-            foreach ($new_kings as $user_id => $num) {
-                if (!isset($kings[$user_id])) {
-                    $kings[$user_id] = $num;
-                } else {
-                    $kings[$user_id] += $num;
-                }
-            }
-        }
-
-        return $kings;
-    }
-
-    private static function files_kings()
-    {
-        return self::select_kings("SELECT user_id AS id, COUNT(*) AS num FROM file_refs GROUP BY user_id");
-    }
-
-    private static function votes_kings()
-    {
-        return self::select_kings("SELECT questionnaires.user_id AS id, COUNT(*) AS num
-            FROM questionnaires
-                LEFT JOIN questionnaire_questions ON (questionnaires.questionnaire_id = questionnaire_questions.questionnaire_id)
-                LEFT JOIN questionnaire_answers ON (questionnaire_questions.question_id = questionnaire_answers.question_id)
-            GROUP BY questionnaires.user_id");
-    }
-
-    private static function voter_kings()
-    {
-        return self::select_kings("SELECT user_id AS id, COUNT(*) AS num FROM questionnaire_answers GROUP BY user_id");
-    }
-
-    private static function news_kings()
-    {
-        return self::select_kings("SELECT user_id AS id, COUNT(*) AS num FROM news GROUP BY user_id");
-    }
-
-    private static function textual_representation($type, $amount)
-    {
-        $alt_text = [
-            'files'            => _('%d hochgeladene Dateien'),
-            'forum'            => _('%d Forums-Beiträge'),
-            'wiki'             => _('%d Wiki-Beiträge'),
-            'voter'            => _('%d abgegebene Stimmen'),
-            'votes'            => _('%d bekommene Stimmen'),
-            'news'             => _('%d eingestellte Ankündigungen')
-        ];
-        return sprintf($alt_text[$type], $amount);
-    }
-}
diff --git a/lib/classes/StudipKing.php b/lib/classes/StudipKing.php
new file mode 100644
index 0000000..ae5a14e
--- /dev/null
+++ b/lib/classes/StudipKing.php
@@ -0,0 +1,173 @@
+
+ * Copyright (C) 2004 - Till Glöggler 
+ * Copyright (C) 2009 - Marcus Lunzenauer 
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+/**
+ * This class awards honours (crowns) to users posting a lot in the bulletin
+ * boards, writing a lot of wiki pages and so on.
+ *
+ * @author    mlunzena
+ */
+
+class StudipKing {
+
+    /**
+     * How many of each type should be awarded?
+     */
+    const NUM_KINGS = 1;
+
+    /**
+     * key to use for caching
+     */
+    const CACHE_KEY = 'core/kings';
+
+    /**
+    * store kings also in memory
+    */
+    private static $kings;
+
+    /**
+     * Returns the awards of a user as an associative array consisting of
+     * "award type" => "amount of posts, wiki pages etc." pairs that belong to
+     * this user. If the 2nd parameter is set to true, the values are
+     * descriptional strings instead of the raw numbers.
+     *
+     * @param  string     a string containing the MD5ish ID of the user
+     * @param  bool       TRUE to return descriptional text, FALSE to return
+     *                    raw numbers, which is the default
+     *
+     * @return array      an associative array mapping the awards to an amount
+     */
+    static function is_king($user_id, $textual = FALSE)
+    {
+        $kings = self::get_kings();
+        $result = isset($kings[$user_id]) ? $kings[$user_id] : [];
+        if ($textual) {
+            foreach ($result as $type => $amount) {
+                $result[$type] = self::textual_representation($type, $amount);
+            }
+        }
+        return $result;
+    }
+
+    private static function get_kings()
+    {
+        if (self::$kings === null) {
+            $cache = \Studip\Cache\Factory::getCache();
+
+            # read cache (unserializing a cache miss - FALSE - does not matter)
+            $kings = unserialize($cache->read(self::CACHE_KEY));
+
+            # cache miss, retrieve from database
+            if ($kings === FALSE) {
+                $kings = self::get_kings_uncached();
+                # write to cache with an expiry time of 24 hours
+                $cache->write(self::CACHE_KEY, serialize($kings), 86400);
+            }
+            self::$kings = $kings;
+        }
+        return self::$kings;
+    }
+
+    private static function get_kings_uncached()
+    {
+        $types = words('files forum news voter votes wiki');
+        $kings = [];
+        foreach ($types as $type) {
+            $method = "{$type}_kings";
+            foreach (self::$method() as $user_id => $amount) {
+                if (!isset($kings[$user_id])) {
+                    $kings[$user_id] = [];
+                }
+                $kings[$user_id][$type] = $amount;
+            }
+        }
+        return $kings;
+    }
+
+    private static function select_kings($sql)
+    {
+        $result = [];
+        $stmt = DBManager::get()->query($sql . " ORDER BY num DESC LIMIT 0," . self::NUM_KINGS);
+        foreach ($stmt as $row) {
+            $result[$row["id"]] = $row["num"];
+        }
+        return $result;
+    }
+
+    private static function wiki_kings()
+    {
+        return self::select_kings("
+            SELECT user_id AS id, COUNT(*) AS num
+            FROM (SELECT user_id FROM wiki_pages UNION SELECT user_id FROM wiki_versions) AS `all_wiki_pages`
+            GROUP BY user_id");
+    }
+
+    private static function forum_kings()
+    {
+        $kings = [];
+
+        // sum up postings for all users from all ForumModules available
+        foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) {
+            $table = $plugin->getEntryTableInfo();
+            $query = "SELECT user_id AS id, COUNT(*) AS num FROM ". $table['table'] ." GROUP BY user_id";
+            $new_kings = self::select_kings($query);
+            foreach ($new_kings as $user_id => $num) {
+                if (!isset($kings[$user_id])) {
+                    $kings[$user_id] = $num;
+                } else {
+                    $kings[$user_id] += $num;
+                }
+            }
+        }
+
+        return $kings;
+    }
+
+    private static function files_kings()
+    {
+        return self::select_kings("SELECT user_id AS id, COUNT(*) AS num FROM file_refs GROUP BY user_id");
+    }
+
+    private static function votes_kings()
+    {
+        return self::select_kings("SELECT questionnaires.user_id AS id, COUNT(*) AS num
+            FROM questionnaires
+                LEFT JOIN questionnaire_questions ON (questionnaires.questionnaire_id = questionnaire_questions.questionnaire_id)
+                LEFT JOIN questionnaire_answers ON (questionnaire_questions.question_id = questionnaire_answers.question_id)
+            GROUP BY questionnaires.user_id");
+    }
+
+    private static function voter_kings()
+    {
+        return self::select_kings("SELECT user_id AS id, COUNT(*) AS num FROM questionnaire_answers GROUP BY user_id");
+    }
+
+    private static function news_kings()
+    {
+        return self::select_kings("SELECT user_id AS id, COUNT(*) AS num FROM news GROUP BY user_id");
+    }
+
+    private static function textual_representation($type, $amount)
+    {
+        $alt_text = [
+            'files'            => _('%d hochgeladene Dateien'),
+            'forum'            => _('%d Forums-Beiträge'),
+            'wiki'             => _('%d Wiki-Beiträge'),
+            'voter'            => _('%d abgegebene Stimmen'),
+            'votes'            => _('%d bekommene Stimmen'),
+            'news'             => _('%d eingestellte Ankündigungen')
+        ];
+        return sprintf($alt_text[$type], $amount);
+    }
+}
diff --git a/lib/classes/StudipLink.class.php b/lib/classes/StudipLink.class.php
deleted file mode 100644
index b4e80ba..0000000
--- a/lib/classes/StudipLink.class.php
+++ /dev/null
@@ -1,47 +0,0 @@
-link = $link;
-        $this->title = $title;
-        $this->icon = $icon;
-    }
-
-
-    public function __toString()
-    {
-        $template = '%2$s %3$s';
-        return sprintf($template, $this->link, $this->title, $this->icon);
-    }
-}
diff --git a/lib/classes/StudipLink.php b/lib/classes/StudipLink.php
new file mode 100644
index 0000000..b4e80ba
--- /dev/null
+++ b/lib/classes/StudipLink.php
@@ -0,0 +1,47 @@
+link = $link;
+        $this->title = $title;
+        $this->icon = $icon;
+    }
+
+
+    public function __toString()
+    {
+        $template = '%2$s %3$s';
+        return sprintf($template, $this->link, $this->title, $this->icon);
+    }
+}
diff --git a/lib/classes/StudipLock.class.php b/lib/classes/StudipLock.class.php
deleted file mode 100644
index 601db8b..0000000
--- a/lib/classes/StudipLock.class.php
+++ /dev/null
@@ -1,99 +0,0 @@
-
- * @copyright   2013 Stud.IP Core-Group
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category    Stud.IP
-*/
-class StudipLock
-{
-    /**
-     * name of active lock
-     * @var string
-     */
-    private static $current;
-
-    /**
-     * returns the name of the current active lock
-     * @return string name of active lock
-     */
-    public static function getCurrent()
-    {
-        return self::$current;
-    }
-
-    /**
-     * Tries to obtain a lock with a name given by the string $lockname,
-     * using a timeout of $timeout seconds. Returns 1 if the lock was obtained
-     * successfully, 0 if the attempt timed out
-     * (for example, because another client has previously locked the name),
-     * or NULL if an error occurred
-     * If a name has been locked by one client, any request by another client
-     * for a lock with the same name is blocked.
-     *
-     * @param string $lockname
-     * @param number $timeout in seconds
-     * @throws UnexpectedValueException if there is already an active lock
-     * @return integer 1 if the lock was obtained successfully, 0 if the attempt timed out
-     */
-    public static function get($lockname, $timeout = 10)
-    {
-        if (self::$current !== null) {
-            throw new UnexpectedValueException(sprintf(
-                'could not acquire new lock, %s still active',
-                self::$current
-            ));
-        }
-        $ok = DBManager::get()->fetchColumn("SELECT GET_LOCK(?,?)", [self::lockname($lockname), $timeout]);
-        if ($ok) {
-            self::$current = $lockname;
-        }
-        return $ok;
-    }
-
-    /**
-     * check if lock with given name is available
-     *
-     * @param string $lockname
-     * @return integer 1 if lock is available
-     */
-    public static function isFree($lockname)
-    {
-        return DBManager::get()->fetchColumn("SELECT IS_FREE_LOCK(?)", [self::lockname($lockname)]);
-    }
-
-    /**
-     * release the current lock
-     *
-     * @return integer 1 if the lock could be released
-     */
-    public static function release()
-    {
-        if (self::$current) {
-            return DBManager::get()->fetchColumn("SELECT RELEASE_LOCK(?)", [self::lockname(self::$current)]);
-        }
-        return 0;
-    }
-
-    /**
-     * prepends the name of current database to lockname
-     * because locks are server-wide
-     *
-     * @param string $lockname
-     * @return string
-     */
-    public static function lockname($lockname)
-    {
-        return $GLOBALS['DB_STUDIP_DATABASE'] . '_' . $lockname;
-    }
-}
diff --git a/lib/classes/StudipLock.php b/lib/classes/StudipLock.php
new file mode 100644
index 0000000..601db8b
--- /dev/null
+++ b/lib/classes/StudipLock.php
@@ -0,0 +1,99 @@
+
+ * @copyright   2013 Stud.IP Core-Group
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+*/
+class StudipLock
+{
+    /**
+     * name of active lock
+     * @var string
+     */
+    private static $current;
+
+    /**
+     * returns the name of the current active lock
+     * @return string name of active lock
+     */
+    public static function getCurrent()
+    {
+        return self::$current;
+    }
+
+    /**
+     * Tries to obtain a lock with a name given by the string $lockname,
+     * using a timeout of $timeout seconds. Returns 1 if the lock was obtained
+     * successfully, 0 if the attempt timed out
+     * (for example, because another client has previously locked the name),
+     * or NULL if an error occurred
+     * If a name has been locked by one client, any request by another client
+     * for a lock with the same name is blocked.
+     *
+     * @param string $lockname
+     * @param number $timeout in seconds
+     * @throws UnexpectedValueException if there is already an active lock
+     * @return integer 1 if the lock was obtained successfully, 0 if the attempt timed out
+     */
+    public static function get($lockname, $timeout = 10)
+    {
+        if (self::$current !== null) {
+            throw new UnexpectedValueException(sprintf(
+                'could not acquire new lock, %s still active',
+                self::$current
+            ));
+        }
+        $ok = DBManager::get()->fetchColumn("SELECT GET_LOCK(?,?)", [self::lockname($lockname), $timeout]);
+        if ($ok) {
+            self::$current = $lockname;
+        }
+        return $ok;
+    }
+
+    /**
+     * check if lock with given name is available
+     *
+     * @param string $lockname
+     * @return integer 1 if lock is available
+     */
+    public static function isFree($lockname)
+    {
+        return DBManager::get()->fetchColumn("SELECT IS_FREE_LOCK(?)", [self::lockname($lockname)]);
+    }
+
+    /**
+     * release the current lock
+     *
+     * @return integer 1 if the lock could be released
+     */
+    public static function release()
+    {
+        if (self::$current) {
+            return DBManager::get()->fetchColumn("SELECT RELEASE_LOCK(?)", [self::lockname(self::$current)]);
+        }
+        return 0;
+    }
+
+    /**
+     * prepends the name of current database to lockname
+     * because locks are server-wide
+     *
+     * @param string $lockname
+     * @return string
+     */
+    public static function lockname($lockname)
+    {
+        return $GLOBALS['DB_STUDIP_DATABASE'] . '_' . $lockname;
+    }
+}
diff --git a/lib/classes/StudipLog.class.php b/lib/classes/StudipLog.class.php
deleted file mode 100644
index 7a520c9..0000000
--- a/lib/classes/StudipLog.class.php
+++ /dev/null
@@ -1,380 +0,0 @@
-
- * @copyright   2013 Stud.IP Core-Group
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category    Stud.IP
- * @since       3.0
- */
-
-class StudipLog
-{
-    /**
-     * Magic log, intercepts all undefined static method calls
-     * called method name must be the name of a log action
-     *
-     * @param string $name
-     * @param array $arguments
-     * @return boolean True if event was written or false if logging is disabled
-     */
-    public static function __callStatic($name, $arguments)
-    {
-        $log_action_name = mb_strtoupper($name);
-        $log_action = LogAction::findByName($log_action_name);
-        if ($log_action) {
-            return call_user_func_array('StudipLog::log', array_merge([$log_action_name], $arguments));
-        }
-        throw new BadMethodCallException('Unknown method called: '
-                . $log_action_name);
-    }
-
-    /**
-    * Logs an event to the database after a certain action took place along with
-    * the ids of the range object the action possibly affected. You can provide
-    * additional info as well as debug information.
-    *
-    * @param String $action_name     Name of the action that took place
-    * @param mixed  $affected   Range id that was affected by the action, if any
-    * @param mixed  $coaffected Range id that was possibly affected as well
-    * @param mixed  $info       Information to add to the event
-    * @param mixed  $dbg_info   Debug information to add to the event
-    * @param mixed  $user_id    Provide null for the current user id
-    **/
-    public static function log(
-        $action_name,
-        $affected = null,
-        $coaffected = null,
-        $info = null,
-        $dbg_info = null,
-        $user_id = null
-    ) {
-        if (!Config::get()->LOG_ENABLE) {
-            return false;
-        }
-
-        // automagically set current user as agent
-        if (!$user_id) {
-            $user_id = $GLOBALS['user']->id;
-        }
-
-        $log_action = LogAction::findOneByName($action_name);
-        if (!$log_action) {
-            // Action doesn't exist -> LOG_ERROR
-            $debug = sprintf(
-                'StudipLog::log(%s,%s,%s,%s,%s) for user %s',
-                $action_name,
-                $affected,
-                $coaffected,
-                $info,
-                $dbg_info,
-                $user_id
-            );
-            self::log('LOG_ERROR', null, null, null, $debug);
-            return false;
-        }
-
-        if (!$log_action->isActive()) {
-            return false;
-        }
-
-        $log_event = new LogEvent();
-        $log_event->user_id = $user_id;
-        $log_event->action_id = $log_action->getId();
-        $log_event->affected_range_id = $affected;
-        $log_event->coaffected_range_id = $coaffected;
-        $log_event->info = $info;
-        $log_event->dbg_info = $dbg_info;
-        $log_event->store();
-        return true;
-    }
-
-    /**
-     * Registers a new log action in database.
-     * Use this function to register log actions for Stud.IP core objects.
-     *
-     * @param string $name The name of the action.
-     * @param string $description The action's description.
-     * @param string $info_template The template
-     * @param string $class Name of the core class.
-     */
-    public static function registerAction($name, $description, $info_template,
-            $class)
-    {
-        $action = new LogAction();
-        $action->name = $name;
-        $action->description = $description;
-        $action->info_template = $info_template;
-        $action->class = $class;
-        $action->type = 'core';
-        $action->store();
-    }
-
-    /**
-     * Registers a new log action in database.
-     * Use this function to register log actions for plugin classes.
-     *
-     * @param string $name The name of the action.
-     * @param string $description The action's description.
-     * @param string $info_template The template
-     * @param string $plugin_class_name Name of the plugin class.
-     */
-    public static function registerActionPlugin($name, $description,
-            $info_template, $plugin_class_name)
-    {
-        $action = new LogAction();
-        $action->name = $name;
-        $action->description = $description;
-        $action->info_template = $info_template;
-        $action->class = $plugin_class_name;
-        $action->type = 'plugin';
-        $action->store();
-    }
-
-    /**
-     * Registers a new log action in database.
-     * Use this function to register log actions for arbitrary objects.
-     *
-     * @param string $name The name of the action.
-     * @param string $description The action's description.
-     * @param string $info_template The template
-     * @param string $filename Path to the file with the class (relative
-     * to Stud.IP root).
-     * @param string $class Name of class to be logged.
-     */
-    public static function registerActionFile($name, $description,
-            $info_template, $filename, $class)
-    {
-        $path_file = $GLOBALS['STUDIP_BASE_PATH'] . '/' . $filename;
-        if (!file_exists($path_file)) {
-            $message = sprintf('Task class file "%s" does not exist.',
-                    $path_file);
-            throw new InvalidArgumentException($message);
-        }
-        $action = new LogAction();
-        $action->name = $name;
-        $action->description = $description;
-        $action->info_template = $info_template;
-        $action->filename = $path_file;
-        $action->class = $class;
-        $action->type = 'file';
-        $action->store();
-    }
-
-    /**
-     * Removes the action from database.
-     * Deletes all related log events also.
-     *
-     * @param string $name The name of the log action.
-     * @return mixed Number of deleted objects or false if action is unknown.
-     */
-    public static function unregisterAction($name)
-    {
-        $action = LogAction::findOneByName($name);
-        if ($action) {
-            return $action->delete();
-        }
-        return false;
-    }
-
-    /**
-     * Finds all seminars by given search string. Searches for the name of
-     * existing or already deleted seminars.
-     *
-     * @param string $needle The needle to search for.
-     * @return array
-     */
-    public static function searchSeminar($needle)
-    {
-        $result = [];
-
-        // search for active seminars
-        $courses = Course::findBySQL("VeranstaltungsNummer LIKE CONCAT('%', :needle, '%')
-                     OR seminare.Name LIKE CONCAT('%', :needle, '%') ORDER BY start_time DESC",
-                [':needle' => $needle]);
-
-        foreach ($courses as $course) {
-            $title = sprintf('%s %s (%s)',
-                             $course->VeranstaltungsNummer,
-                             my_substr($course->name, 0, 40),
-                             $course->start_semester->name);
-                $result[] = [$course->getId(), $title];
-        }
-
-        // search deleted seminars
-        // SemName and Number is part of info field, old id (still in DB) is in affected column
-        $log_action_ids_archived_seminar = SimpleORMapCollection::createFromArray(
-                LogAction::findBySQL(
-                    "name IN ('SEM_ARCHIVE', 'SEM_DELETE_FROM_ARCHIVE')"))
-                ->pluck('action_id');
-        $log_events_archived_seminar = LogEvent::findBySQL("info LIKE CONCAT('%', ?, '%')
-                AND action_id IN (?) ",
-                [$needle, $log_action_ids_archived_seminar]);
-        foreach ($log_events_archived_seminar as $log_event) {
-            $title = sprintf('%s (%s)', my_substr($log_event->info, 0, 40), _('gelöscht'));
-            $result[] = [$log_event->affected_range_id, $title];
-        }
-
-        return $result;
-    }
-
-    /**
-     * Finds all institutes by given search string. Searches for the name of
-     * existing or already deleted institutes.
-     *
-     * @param type $needle The needle to search for.
-     * @return array
-     */
-    public static function searchInstitute($needle)
-    {
-        $result = [];
-
-        $institutes = Institute::findBySQL(
-                "name LIKE CONCAT('%', ?, '%') ORDER BY name", [$needle]);
-        foreach ($institutes as $institute) {
-            $result[] = [$institute->getId(), my_substr($institute->name, 0, 28)];
-        }
-
-        // search for deleted institutes
-        // Name of deleted institute is part of info field,
-        // old id (still in DB) is in affected column
-        $log_action_delete_institute = LogAction::findOneByName('INST_DEL');
-        $log_events_delete_institute = LogEvent::findBySQL(
-                "action_id = ? AND info LIKE CONCAT('%', ?, '%')",
-                [$log_action_delete_institute->getId(), $needle]);
-        foreach ($log_events_delete_institute as $log_event) {
-            $title = sprintf('%s (%s)', $log_event->info, _('gelöscht'));
-            $result[] = [$log_event->affected_range_id, $title];
-        }
-
-        return $result;
-    }
-
-    /**
-     * Finds all users by given search string. Searches for the users id,
-     * part of the name or the username.
-     *
-     * @param type $needle The needle to search for.
-     * @return array
-     */
-    public static function searchUser($needle)
-    {
-        $users = User::findBySQL(
-            "Nachname LIKE CONCAT('%', :needle, '%')
-             OR Vorname LIKE CONCAT('%', :needle, '%')
-             OR CONCAT(Nachname, ', ', Vorname) LIKE CONCAT('%', :needle, '%')
-             OR CONCAT(Vorname, ' ', Nachname) LIKE CONCAT('%', :needle, '%')
-             OR username LIKE CONCAT('%', :needle, '%')
-             ORDER BY Nachname DESC, Vorname DESC",
-            [':needle' => $needle]
-        );
-
-        $result = [];
-        foreach ($users as $user) {
-            $name = sprintf(
-                '%s (%s)',
-                my_substr($user->getFullName(), 0, 20),
-                $user->username
-            );
-            $result[] = [$user->getId(), $name];
-        }
-
-        // search for deleted users
-        //
-        // The name of the user is part of info field,
-        // old id (still in DB) is in affected column.
-        //
-        // The log action "USER_DEL" was removed from the list of initially
-        // registered log actions in the past.
-        // Search for the user if it is still in database. If not, the search
-        // for deleted users is not possible.
-        $log_action_deleted_user = LogAction::findOneByName('USER_DEL');
-        if ($log_action_deleted_user) {
-            $log_events_deleted_user = LogEvent::findBySQL(
-                "action_id = ? AND info LIKE CONCAT('%', ?, '%')",
-                [$log_action_deleted_user->getId(), $needle]
-            );
-            foreach ($log_events_deleted_user as $log_event) {
-                $name = sprintf('%s (%s)', $log_event->info, _('gelöscht'));
-                $result[] = [$log_event->affected_range_id, $name];
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Finds all resources by given search string. The search string can be
-     * either a resource id or part of the name.
-     *
-     * @param string $needle The needle to search for.
-     * @return array
-     */
-    public static function searchResource($needle)
-    {
-        $result = [];
-
-        $query = "SELECT id, name FROM resources WHERE name LIKE CONCAT('%', ?, '%') ORDER by name";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$needle]);
-
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $result[] = [$row['id'], my_substr($row['name'], 0, 30)];
-        }
-
-        return $result;
-    }
-
-    /**
-     * Finds all objects related to the given action by search string.
-     * The search string can be either a part of the name or the id
-     * of the object.
-     *
-     * Calls the method Loggable::logSearch() to retrieve the result.
-     *
-     * @param string $needle
-     * @param type $action_id
-     * @return type
-     */
-    public static function searchObjectByAction($needle, $action_id)
-    {
-        $action = LogAction::find($action_id);
-
-        if ($action) {
-            switch ($action->type) {
-                case 'plugin':
-                    $plugin_manager = PluginManager::getInstance();
-                    $plugin_info = $plugin_manager->getPluginInfo($action->class);
-                    $class_name = $plugin_info['class'];
-                    $plugin = $plugin_manager->getPlugin($class_name);
-                    if ($plugin instanceof Loggable) {
-                        return $class_name::logSearch($needle, $action->name);
-                    }
-                    break;
-                case 'file':
-                    if (!file_exists($action->filename)) {
-                        require_once($action->filename);
-                        $class_name = $action->class;
-                        if ($class_name instanceof Loggable) {
-                            return $class_name::logSearch($needle, $action->name);
-                        }
-                    }
-                    break;
-                case 'core':
-                    $class_name = $action->class;
-                    $interfaces = class_implements($class_name);
-                    if (isset($interfaces['Loggable'])) {
-                        return $class_name::logSearch($needle, $action->name);
-                    }
-            }
-        }
-        return [];
-    }
-}
diff --git a/lib/classes/StudipLog.php b/lib/classes/StudipLog.php
new file mode 100644
index 0000000..7a520c9
--- /dev/null
+++ b/lib/classes/StudipLog.php
@@ -0,0 +1,380 @@
+
+ * @copyright   2013 Stud.IP Core-Group
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+ * @since       3.0
+ */
+
+class StudipLog
+{
+    /**
+     * Magic log, intercepts all undefined static method calls
+     * called method name must be the name of a log action
+     *
+     * @param string $name
+     * @param array $arguments
+     * @return boolean True if event was written or false if logging is disabled
+     */
+    public static function __callStatic($name, $arguments)
+    {
+        $log_action_name = mb_strtoupper($name);
+        $log_action = LogAction::findByName($log_action_name);
+        if ($log_action) {
+            return call_user_func_array('StudipLog::log', array_merge([$log_action_name], $arguments));
+        }
+        throw new BadMethodCallException('Unknown method called: '
+                . $log_action_name);
+    }
+
+    /**
+    * Logs an event to the database after a certain action took place along with
+    * the ids of the range object the action possibly affected. You can provide
+    * additional info as well as debug information.
+    *
+    * @param String $action_name     Name of the action that took place
+    * @param mixed  $affected   Range id that was affected by the action, if any
+    * @param mixed  $coaffected Range id that was possibly affected as well
+    * @param mixed  $info       Information to add to the event
+    * @param mixed  $dbg_info   Debug information to add to the event
+    * @param mixed  $user_id    Provide null for the current user id
+    **/
+    public static function log(
+        $action_name,
+        $affected = null,
+        $coaffected = null,
+        $info = null,
+        $dbg_info = null,
+        $user_id = null
+    ) {
+        if (!Config::get()->LOG_ENABLE) {
+            return false;
+        }
+
+        // automagically set current user as agent
+        if (!$user_id) {
+            $user_id = $GLOBALS['user']->id;
+        }
+
+        $log_action = LogAction::findOneByName($action_name);
+        if (!$log_action) {
+            // Action doesn't exist -> LOG_ERROR
+            $debug = sprintf(
+                'StudipLog::log(%s,%s,%s,%s,%s) for user %s',
+                $action_name,
+                $affected,
+                $coaffected,
+                $info,
+                $dbg_info,
+                $user_id
+            );
+            self::log('LOG_ERROR', null, null, null, $debug);
+            return false;
+        }
+
+        if (!$log_action->isActive()) {
+            return false;
+        }
+
+        $log_event = new LogEvent();
+        $log_event->user_id = $user_id;
+        $log_event->action_id = $log_action->getId();
+        $log_event->affected_range_id = $affected;
+        $log_event->coaffected_range_id = $coaffected;
+        $log_event->info = $info;
+        $log_event->dbg_info = $dbg_info;
+        $log_event->store();
+        return true;
+    }
+
+    /**
+     * Registers a new log action in database.
+     * Use this function to register log actions for Stud.IP core objects.
+     *
+     * @param string $name The name of the action.
+     * @param string $description The action's description.
+     * @param string $info_template The template
+     * @param string $class Name of the core class.
+     */
+    public static function registerAction($name, $description, $info_template,
+            $class)
+    {
+        $action = new LogAction();
+        $action->name = $name;
+        $action->description = $description;
+        $action->info_template = $info_template;
+        $action->class = $class;
+        $action->type = 'core';
+        $action->store();
+    }
+
+    /**
+     * Registers a new log action in database.
+     * Use this function to register log actions for plugin classes.
+     *
+     * @param string $name The name of the action.
+     * @param string $description The action's description.
+     * @param string $info_template The template
+     * @param string $plugin_class_name Name of the plugin class.
+     */
+    public static function registerActionPlugin($name, $description,
+            $info_template, $plugin_class_name)
+    {
+        $action = new LogAction();
+        $action->name = $name;
+        $action->description = $description;
+        $action->info_template = $info_template;
+        $action->class = $plugin_class_name;
+        $action->type = 'plugin';
+        $action->store();
+    }
+
+    /**
+     * Registers a new log action in database.
+     * Use this function to register log actions for arbitrary objects.
+     *
+     * @param string $name The name of the action.
+     * @param string $description The action's description.
+     * @param string $info_template The template
+     * @param string $filename Path to the file with the class (relative
+     * to Stud.IP root).
+     * @param string $class Name of class to be logged.
+     */
+    public static function registerActionFile($name, $description,
+            $info_template, $filename, $class)
+    {
+        $path_file = $GLOBALS['STUDIP_BASE_PATH'] . '/' . $filename;
+        if (!file_exists($path_file)) {
+            $message = sprintf('Task class file "%s" does not exist.',
+                    $path_file);
+            throw new InvalidArgumentException($message);
+        }
+        $action = new LogAction();
+        $action->name = $name;
+        $action->description = $description;
+        $action->info_template = $info_template;
+        $action->filename = $path_file;
+        $action->class = $class;
+        $action->type = 'file';
+        $action->store();
+    }
+
+    /**
+     * Removes the action from database.
+     * Deletes all related log events also.
+     *
+     * @param string $name The name of the log action.
+     * @return mixed Number of deleted objects or false if action is unknown.
+     */
+    public static function unregisterAction($name)
+    {
+        $action = LogAction::findOneByName($name);
+        if ($action) {
+            return $action->delete();
+        }
+        return false;
+    }
+
+    /**
+     * Finds all seminars by given search string. Searches for the name of
+     * existing or already deleted seminars.
+     *
+     * @param string $needle The needle to search for.
+     * @return array
+     */
+    public static function searchSeminar($needle)
+    {
+        $result = [];
+
+        // search for active seminars
+        $courses = Course::findBySQL("VeranstaltungsNummer LIKE CONCAT('%', :needle, '%')
+                     OR seminare.Name LIKE CONCAT('%', :needle, '%') ORDER BY start_time DESC",
+                [':needle' => $needle]);
+
+        foreach ($courses as $course) {
+            $title = sprintf('%s %s (%s)',
+                             $course->VeranstaltungsNummer,
+                             my_substr($course->name, 0, 40),
+                             $course->start_semester->name);
+                $result[] = [$course->getId(), $title];
+        }
+
+        // search deleted seminars
+        // SemName and Number is part of info field, old id (still in DB) is in affected column
+        $log_action_ids_archived_seminar = SimpleORMapCollection::createFromArray(
+                LogAction::findBySQL(
+                    "name IN ('SEM_ARCHIVE', 'SEM_DELETE_FROM_ARCHIVE')"))
+                ->pluck('action_id');
+        $log_events_archived_seminar = LogEvent::findBySQL("info LIKE CONCAT('%', ?, '%')
+                AND action_id IN (?) ",
+                [$needle, $log_action_ids_archived_seminar]);
+        foreach ($log_events_archived_seminar as $log_event) {
+            $title = sprintf('%s (%s)', my_substr($log_event->info, 0, 40), _('gelöscht'));
+            $result[] = [$log_event->affected_range_id, $title];
+        }
+
+        return $result;
+    }
+
+    /**
+     * Finds all institutes by given search string. Searches for the name of
+     * existing or already deleted institutes.
+     *
+     * @param type $needle The needle to search for.
+     * @return array
+     */
+    public static function searchInstitute($needle)
+    {
+        $result = [];
+
+        $institutes = Institute::findBySQL(
+                "name LIKE CONCAT('%', ?, '%') ORDER BY name", [$needle]);
+        foreach ($institutes as $institute) {
+            $result[] = [$institute->getId(), my_substr($institute->name, 0, 28)];
+        }
+
+        // search for deleted institutes
+        // Name of deleted institute is part of info field,
+        // old id (still in DB) is in affected column
+        $log_action_delete_institute = LogAction::findOneByName('INST_DEL');
+        $log_events_delete_institute = LogEvent::findBySQL(
+                "action_id = ? AND info LIKE CONCAT('%', ?, '%')",
+                [$log_action_delete_institute->getId(), $needle]);
+        foreach ($log_events_delete_institute as $log_event) {
+            $title = sprintf('%s (%s)', $log_event->info, _('gelöscht'));
+            $result[] = [$log_event->affected_range_id, $title];
+        }
+
+        return $result;
+    }
+
+    /**
+     * Finds all users by given search string. Searches for the users id,
+     * part of the name or the username.
+     *
+     * @param type $needle The needle to search for.
+     * @return array
+     */
+    public static function searchUser($needle)
+    {
+        $users = User::findBySQL(
+            "Nachname LIKE CONCAT('%', :needle, '%')
+             OR Vorname LIKE CONCAT('%', :needle, '%')
+             OR CONCAT(Nachname, ', ', Vorname) LIKE CONCAT('%', :needle, '%')
+             OR CONCAT(Vorname, ' ', Nachname) LIKE CONCAT('%', :needle, '%')
+             OR username LIKE CONCAT('%', :needle, '%')
+             ORDER BY Nachname DESC, Vorname DESC",
+            [':needle' => $needle]
+        );
+
+        $result = [];
+        foreach ($users as $user) {
+            $name = sprintf(
+                '%s (%s)',
+                my_substr($user->getFullName(), 0, 20),
+                $user->username
+            );
+            $result[] = [$user->getId(), $name];
+        }
+
+        // search for deleted users
+        //
+        // The name of the user is part of info field,
+        // old id (still in DB) is in affected column.
+        //
+        // The log action "USER_DEL" was removed from the list of initially
+        // registered log actions in the past.
+        // Search for the user if it is still in database. If not, the search
+        // for deleted users is not possible.
+        $log_action_deleted_user = LogAction::findOneByName('USER_DEL');
+        if ($log_action_deleted_user) {
+            $log_events_deleted_user = LogEvent::findBySQL(
+                "action_id = ? AND info LIKE CONCAT('%', ?, '%')",
+                [$log_action_deleted_user->getId(), $needle]
+            );
+            foreach ($log_events_deleted_user as $log_event) {
+                $name = sprintf('%s (%s)', $log_event->info, _('gelöscht'));
+                $result[] = [$log_event->affected_range_id, $name];
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Finds all resources by given search string. The search string can be
+     * either a resource id or part of the name.
+     *
+     * @param string $needle The needle to search for.
+     * @return array
+     */
+    public static function searchResource($needle)
+    {
+        $result = [];
+
+        $query = "SELECT id, name FROM resources WHERE name LIKE CONCAT('%', ?, '%') ORDER by name";
+        $statement = DBManager::get()->prepare($query);
+        $statement->execute([$needle]);
+
+        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
+            $result[] = [$row['id'], my_substr($row['name'], 0, 30)];
+        }
+
+        return $result;
+    }
+
+    /**
+     * Finds all objects related to the given action by search string.
+     * The search string can be either a part of the name or the id
+     * of the object.
+     *
+     * Calls the method Loggable::logSearch() to retrieve the result.
+     *
+     * @param string $needle
+     * @param type $action_id
+     * @return type
+     */
+    public static function searchObjectByAction($needle, $action_id)
+    {
+        $action = LogAction::find($action_id);
+
+        if ($action) {
+            switch ($action->type) {
+                case 'plugin':
+                    $plugin_manager = PluginManager::getInstance();
+                    $plugin_info = $plugin_manager->getPluginInfo($action->class);
+                    $class_name = $plugin_info['class'];
+                    $plugin = $plugin_manager->getPlugin($class_name);
+                    if ($plugin instanceof Loggable) {
+                        return $class_name::logSearch($needle, $action->name);
+                    }
+                    break;
+                case 'file':
+                    if (!file_exists($action->filename)) {
+                        require_once($action->filename);
+                        $class_name = $action->class;
+                        if ($class_name instanceof Loggable) {
+                            return $class_name::logSearch($needle, $action->name);
+                        }
+                    }
+                    break;
+                case 'core':
+                    $class_name = $action->class;
+                    $interfaces = class_implements($class_name);
+                    if (isset($interfaces['Loggable'])) {
+                        return $class_name::logSearch($needle, $action->name);
+                    }
+            }
+        }
+        return [];
+    }
+}
diff --git a/lib/classes/StudipLvgruppeSelection.class.php b/lib/classes/StudipLvgruppeSelection.class.php
deleted file mode 100644
index a5da9cb..0000000
--- a/lib/classes/StudipLvgruppeSelection.class.php
+++ /dev/null
@@ -1,359 +0,0 @@
-
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- */
-
-
-/**
- * Objects of this class represent the state of the LV-Gruppe selection form.
- */
-class StudipLvgruppeSelection {
-
-    private $selected;
-    private $showAll;
-    private $areas;
-    private $searchKey;
-    private $searchResult;
-
-    /**
-     * This constructor can be called with or without a course ID. If a course ID
-     * has been sent, the selected lvgruppen are populated by that course's already
-     * chosen lvgruppen. If no course ID is given, it is assumed that you are
-     * creating a new course at the moment.
-     *
-     * @param  string     optional; the ID of the course to prepopulate the form
-     *                    with
-     *
-     * @return void
-     */
-    public function __construct($course_id = null)
-    {
-        $this->selected = self::getRootItem();
-        $this->showAll = FALSE;
-
-        $this->areas = [];
-
-        $this->searchKey = '';
-        $this->clearSearchResult();
-
-        if (isset($course_id)) {
-            $this->populateAreasForCourse($course_id);
-        }
-    }
-
-    /**
-      * Returns the not really existing root of the tree.
-      *
-      * @return object     the root tree object
-      */
-     public static function getRootItem()
-     {
-        $root = new MvvTreeRoot();
-        return $root;
-     }
-
-    /**
-     * This method populates this instance with the already chosen LV-Gruppen.
-     *
-     * @param  string     the course's ID
-     *
-     * @return void
-     */
-    private function populateAreasForCourse($id)
-    {
-        $lvgruppen = Lvgruppe::findBySeminar($id);
-        $this->setLvgruppen($lvgruppen);
-        $this->sortAreas();
-    }
-
-
-    /**
-     * Sorts the internal representation of the areas by their paths according to
-     * the current locale.
-     *
-     * @return void
-     */
-    private function sortAreas()
-    {
-        // MVV: sort by name of LvGruppe
-        uasort($this->areas, function ($a, $b) {
-            return strcoll($a->getDisplayName(), $b->getDisplayName());
-        });
-    }
-
-
-    /**
-     * @return string     the current search term
-     */
-    public function getSearchKey()
-    {
-        return $this->searchKey;
-    }
-
-
-    /**
-     * @param  string     a search term
-     *
-     * @return object     this instance
-     */
-    public function setSearchKey($searchKey)
-    {
-        $this->searchKey = (string) $searchKey;
-
-        $this->clearSearchResult();
-        return $this;
-    }
-
-
-    /**
-     * @return bool       returns TRUE if the search key was set meaning that
-     *                    we are currently searching; returns FALSE otherwise
-     */
-    public function searched()
-    {
-        return $this->searchKey !== '';
-    }
-
-
-    /**
-     * Clears the current search result.
-     *
-     * @return object     this instance
-     */
-    public function clearSearchResult()
-    {
-        $this->searchResult = null;
-        return $this;
-    }
-
-
-    /**
-     * Returns an array of search results.
-     *
-     * @return array      an array of search results
-     */
-    public function getSearchResult()
-    {
-
-        # no search key -> return empty array
-        if ($this->searchKey === '') {
-            return [];
-        }
-
-        if (is_null($this->searchResult)) {
-            $lvgruppen = Lvgruppe::findBySearchTerm($this->searchKey);
-            foreach ($lvgruppen as $lvgruppe) {
-                $this->searchResult[$lvgruppe->id] = $lvgruppe;
-            }
-            usort($this->searchResult, [__CLASS__, 'sortSearchResult']);
-        }
-
-        return $this->searchResult;
-    }
-
-    public static function sortSearchResult($a, $b)
-    {
-        // sort by display name
-        return strcmp($a->getDisplayName(), $b->getDisplayName());
-    }
-
-
-    /**
-     * @return object     the currently selected lvgruppe
-     */
-    public function getSelected()
-    {
-        return $this->selected;
-    }
-
-
-    /**
-     * Sets the selected tree item.
-     *
-     * @param  mixed $selected Either the id of a tree item to select or the
-     * tree item object itself
-     * @return object this instance
-     */
-    public function setSelected($selected, $type = null)
-    {
-        if (!is_object($selected) && !is_null($type)) {
-            $reflection = new ReflectionClass($type);
-            if (!$reflection->implementsInterface('MvvTreeItem')) {
-                throw new InvalidArgumentException('Wrong type of tree element.');
-            }
-            if ($type != 'MvvTreeRoot') {
-                $this->selected = $type::find(explode('_', $selected));
-            }
-        } else {
-            $this->selected = $selected;
-        }
-        return $this;
-    }
-
-
-    /**
-     * @return bool       returns TRUE if the subtrees should be expanded
-     *                    completely or FALSE otherwise
-     */
-    public function getShowAll()
-    {
-        return $this->showAll;
-    }
-
-
-    /**
-     * @param  bool       the new state of the expansion of subtrees
-     *
-     * @return object     this instance
-     */
-    public function setShowAll($showAll)
-    {
-        $this->showAll = $showAll;
-     //   $this->selected = new MvvTreeRoot();
-        return $this;
-    }
-
-
-    /**
-     * Toggles the state of the expansion of subtrees.
-     *
-     * @return object     this instance
-     */
-    public function toggleShowAll()
-    {
-        $this->showAll = !$this->showAll;
-        return $this;
-    }
-
-
-    /**
-     * Returns all the IDs of the already selected LV-Gruppen.
-     *
-     * @return array      an array with IDs of the selected LV-Gruppen
-     */
-    public function getLvGruppenIDs()
-    {
-        return array_keys($this->areas);
-    }
-
-
-    /**
-     * Returns all the selected LV-Gruppen.
-     *
-     * @return array      an array of LV-Gruppen representing the selected
-     *                    LV-Gruppen
-     */
-    public function getAreas()
-    {
-        return $this->areas;
-    }
-
-
-    /**
-     * Sets the LV-Gruppen of this selection. One can provide either MD5ish ID
-     * strings or instances of Lvgruppe.
-     *
-     * @param  array      an array of either MD5ish ID strings or Lvgruppe
-     *
-     * @return object     the called instance itself
-     */
-    public function setLvgruppen($areas)
-    {
-        $this->areas = [];
-        foreach ($areas as $area) {
-            $this->add($area);
-        }
-        return $this;
-    }
-
-
-    /**
-     * Returns true if this LV-Gruppe is selected, false otherwise.
-     *
-     * @param  mixed      the id of a LV-Gruppe or the LV-Gruppe object itself
-     * @return bool       returns true if selected, false otherwise
-     */
-    public function includes($area)
-    {
-        $id = is_object($area) ? $area->getId() : $area;
-        return isset($this->areas[$id]);
-    }
-
-
-    /**
-     * @return integer    returns the number of the selected LV-Gruppen
-     */
-    public function size()
-    {
-        return count($this->areas);
-    }
-
-
-    /**
-     * This method adds an area to the selected LV-Gruppen.
-     *
-     * @param  mixed     the ID of the LV-Gruppe to add or a LV-Gruppe object
-     *
-     * @return object     this instance
-     */
-    public function add($area)
-    {
-        # convert to an object
-        if (!is_object($area)) {
-            $area = Lvgruppe::find($area);
-        }
-        $id = $area->getId();
-        if (!isset($this->areas[$id])) {
-            $this->areas[$id] = $area;
-        }
-        $this->sortAreas();
-        return $this;
-    }
-
-
-    /**
-     * This method removes given LV-Gruppe from the already selected LV-Gruppen.
-     *
-     * @param  mixed     the ID of the LV-Gruppe to add or a LV-Gruppe object
-     *
-     * @return object     this instance
-     */
-    public function remove($area)
-    {
-        if (is_object($area)) {
-            $area = $area->getId();
-        }
-        if (isset($this->areas[(string) $area])) {
-            unset($this->areas[$area]);
-        }
-        return $this;
-    }
-
-
-    /**
-     * Returns the trail -- the path from the root of the tree of MVV objects down
-     * to the currently selected LV-Gruppe.
-     *
-     * @return array an array of MVV objects
-     */
-    public function getTrail()
-    {
-        $area = $this->selected;
-        $trail = [implode('_', (array) $area->getId()) => $area];
-        while ($parent = $area->getTrailParent()) {
-            $trail[implode('_', (array) $parent->getId())] = $parent;
-            $area = $parent;
-        }
-        $trail['root'] = new MvvTreeRoot();
-        return array_reverse($trail, true);
-    }
-}
diff --git a/lib/classes/StudipLvgruppeSelection.php b/lib/classes/StudipLvgruppeSelection.php
new file mode 100644
index 0000000..a5da9cb
--- /dev/null
+++ b/lib/classes/StudipLvgruppeSelection.php
@@ -0,0 +1,359 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+
+/**
+ * Objects of this class represent the state of the LV-Gruppe selection form.
+ */
+class StudipLvgruppeSelection {
+
+    private $selected;
+    private $showAll;
+    private $areas;
+    private $searchKey;
+    private $searchResult;
+
+    /**
+     * This constructor can be called with or without a course ID. If a course ID
+     * has been sent, the selected lvgruppen are populated by that course's already
+     * chosen lvgruppen. If no course ID is given, it is assumed that you are
+     * creating a new course at the moment.
+     *
+     * @param  string     optional; the ID of the course to prepopulate the form
+     *                    with
+     *
+     * @return void
+     */
+    public function __construct($course_id = null)
+    {
+        $this->selected = self::getRootItem();
+        $this->showAll = FALSE;
+
+        $this->areas = [];
+
+        $this->searchKey = '';
+        $this->clearSearchResult();
+
+        if (isset($course_id)) {
+            $this->populateAreasForCourse($course_id);
+        }
+    }
+
+    /**
+      * Returns the not really existing root of the tree.
+      *
+      * @return object     the root tree object
+      */
+     public static function getRootItem()
+     {
+        $root = new MvvTreeRoot();
+        return $root;
+     }
+
+    /**
+     * This method populates this instance with the already chosen LV-Gruppen.
+     *
+     * @param  string     the course's ID
+     *
+     * @return void
+     */
+    private function populateAreasForCourse($id)
+    {
+        $lvgruppen = Lvgruppe::findBySeminar($id);
+        $this->setLvgruppen($lvgruppen);
+        $this->sortAreas();
+    }
+
+
+    /**
+     * Sorts the internal representation of the areas by their paths according to
+     * the current locale.
+     *
+     * @return void
+     */
+    private function sortAreas()
+    {
+        // MVV: sort by name of LvGruppe
+        uasort($this->areas, function ($a, $b) {
+            return strcoll($a->getDisplayName(), $b->getDisplayName());
+        });
+    }
+
+
+    /**
+     * @return string     the current search term
+     */
+    public function getSearchKey()
+    {
+        return $this->searchKey;
+    }
+
+
+    /**
+     * @param  string     a search term
+     *
+     * @return object     this instance
+     */
+    public function setSearchKey($searchKey)
+    {
+        $this->searchKey = (string) $searchKey;
+
+        $this->clearSearchResult();
+        return $this;
+    }
+
+
+    /**
+     * @return bool       returns TRUE if the search key was set meaning that
+     *                    we are currently searching; returns FALSE otherwise
+     */
+    public function searched()
+    {
+        return $this->searchKey !== '';
+    }
+
+
+    /**
+     * Clears the current search result.
+     *
+     * @return object     this instance
+     */
+    public function clearSearchResult()
+    {
+        $this->searchResult = null;
+        return $this;
+    }
+
+
+    /**
+     * Returns an array of search results.
+     *
+     * @return array      an array of search results
+     */
+    public function getSearchResult()
+    {
+
+        # no search key -> return empty array
+        if ($this->searchKey === '') {
+            return [];
+        }
+
+        if (is_null($this->searchResult)) {
+            $lvgruppen = Lvgruppe::findBySearchTerm($this->searchKey);
+            foreach ($lvgruppen as $lvgruppe) {
+                $this->searchResult[$lvgruppe->id] = $lvgruppe;
+            }
+            usort($this->searchResult, [__CLASS__, 'sortSearchResult']);
+        }
+
+        return $this->searchResult;
+    }
+
+    public static function sortSearchResult($a, $b)
+    {
+        // sort by display name
+        return strcmp($a->getDisplayName(), $b->getDisplayName());
+    }
+
+
+    /**
+     * @return object     the currently selected lvgruppe
+     */
+    public function getSelected()
+    {
+        return $this->selected;
+    }
+
+
+    /**
+     * Sets the selected tree item.
+     *
+     * @param  mixed $selected Either the id of a tree item to select or the
+     * tree item object itself
+     * @return object this instance
+     */
+    public function setSelected($selected, $type = null)
+    {
+        if (!is_object($selected) && !is_null($type)) {
+            $reflection = new ReflectionClass($type);
+            if (!$reflection->implementsInterface('MvvTreeItem')) {
+                throw new InvalidArgumentException('Wrong type of tree element.');
+            }
+            if ($type != 'MvvTreeRoot') {
+                $this->selected = $type::find(explode('_', $selected));
+            }
+        } else {
+            $this->selected = $selected;
+        }
+        return $this;
+    }
+
+
+    /**
+     * @return bool       returns TRUE if the subtrees should be expanded
+     *                    completely or FALSE otherwise
+     */
+    public function getShowAll()
+    {
+        return $this->showAll;
+    }
+
+
+    /**
+     * @param  bool       the new state of the expansion of subtrees
+     *
+     * @return object     this instance
+     */
+    public function setShowAll($showAll)
+    {
+        $this->showAll = $showAll;
+     //   $this->selected = new MvvTreeRoot();
+        return $this;
+    }
+
+
+    /**
+     * Toggles the state of the expansion of subtrees.
+     *
+     * @return object     this instance
+     */
+    public function toggleShowAll()
+    {
+        $this->showAll = !$this->showAll;
+        return $this;
+    }
+
+
+    /**
+     * Returns all the IDs of the already selected LV-Gruppen.
+     *
+     * @return array      an array with IDs of the selected LV-Gruppen
+     */
+    public function getLvGruppenIDs()
+    {
+        return array_keys($this->areas);
+    }
+
+
+    /**
+     * Returns all the selected LV-Gruppen.
+     *
+     * @return array      an array of LV-Gruppen representing the selected
+     *                    LV-Gruppen
+     */
+    public function getAreas()
+    {
+        return $this->areas;
+    }
+
+
+    /**
+     * Sets the LV-Gruppen of this selection. One can provide either MD5ish ID
+     * strings or instances of Lvgruppe.
+     *
+     * @param  array      an array of either MD5ish ID strings or Lvgruppe
+     *
+     * @return object     the called instance itself
+     */
+    public function setLvgruppen($areas)
+    {
+        $this->areas = [];
+        foreach ($areas as $area) {
+            $this->add($area);
+        }
+        return $this;
+    }
+
+
+    /**
+     * Returns true if this LV-Gruppe is selected, false otherwise.
+     *
+     * @param  mixed      the id of a LV-Gruppe or the LV-Gruppe object itself
+     * @return bool       returns true if selected, false otherwise
+     */
+    public function includes($area)
+    {
+        $id = is_object($area) ? $area->getId() : $area;
+        return isset($this->areas[$id]);
+    }
+
+
+    /**
+     * @return integer    returns the number of the selected LV-Gruppen
+     */
+    public function size()
+    {
+        return count($this->areas);
+    }
+
+
+    /**
+     * This method adds an area to the selected LV-Gruppen.
+     *
+     * @param  mixed     the ID of the LV-Gruppe to add or a LV-Gruppe object
+     *
+     * @return object     this instance
+     */
+    public function add($area)
+    {
+        # convert to an object
+        if (!is_object($area)) {
+            $area = Lvgruppe::find($area);
+        }
+        $id = $area->getId();
+        if (!isset($this->areas[$id])) {
+            $this->areas[$id] = $area;
+        }
+        $this->sortAreas();
+        return $this;
+    }
+
+
+    /**
+     * This method removes given LV-Gruppe from the already selected LV-Gruppen.
+     *
+     * @param  mixed     the ID of the LV-Gruppe to add or a LV-Gruppe object
+     *
+     * @return object     this instance
+     */
+    public function remove($area)
+    {
+        if (is_object($area)) {
+            $area = $area->getId();
+        }
+        if (isset($this->areas[(string) $area])) {
+            unset($this->areas[$area]);
+        }
+        return $this;
+    }
+
+
+    /**
+     * Returns the trail -- the path from the root of the tree of MVV objects down
+     * to the currently selected LV-Gruppe.
+     *
+     * @return array an array of MVV objects
+     */
+    public function getTrail()
+    {
+        $area = $this->selected;
+        $trail = [implode('_', (array) $area->getId()) => $area];
+        while ($parent = $area->getTrailParent()) {
+            $trail[implode('_', (array) $parent->getId())] = $parent;
+            $area = $parent;
+        }
+        $trail['root'] = new MvvTreeRoot();
+        return array_reverse($trail, true);
+    }
+}
diff --git a/lib/classes/StudipMail.class.php b/lib/classes/StudipMail.class.php
deleted file mode 100644
index 4974f66..0000000
--- a/lib/classes/StudipMail.class.php
+++ /dev/null
@@ -1,487 +0,0 @@
-, Suchi & Berg GmbH 
- * @version 1
- * @license GPL2 or any later version
- * @copyright 2009 authors
- */
-class StudipMail
-{
-    /**
-     * @var email_message_class
-     * @static
-     */
-    private static $transporter;
-
-    /**
-     * @var string
-     */
-    private $body_text;
-    /**
-     * @var string
-     */
-    private $body_html;
-    /**
-     * @var string
-     */
-    private $subject;
-    /**
-     * Array of all attachments, name ist key
-     * @var array
-     */
-    private $attachments = [];
-    /**
-     * Array of attachments that are related to the content
-     * @var array
-     */
-    private $related_attachments = [];
-    /**
-     * @var array
-     */
-    private $sender;
-    /**
-     * Array of all recipients, mail is key
-     * @var array
-     */
-    private $recipients = [];
-    /**
-     * @var array
-     */
-    private $reply_to;
-
-    /**
-     * Sets the default transporter used in StudipMail::send()
-     * @param email_message_class $transporter
-     * @return void
-     */
-    public static function setDefaultTransporter(email_message_class $transporter)
-    {
-        self::$transporter = $transporter;
-    }
-
-    /**
-     * gets the default transporter used in StudipMail::send()
-     *
-     * @return email_message_class
-     */
-    public static function getDefaultTransporter()
-    {
-        return self::$transporter;
-    }
-
-    /**
-     * Gets the configured abuse mail contact
-     *
-     * @return string
-     */
-    public static function getAbuseEmail()
-    {
-        return $GLOBALS['MAIL_ABUSE'] ?: "abuse@{$mail_localhost}";
-    }
-
-    /**
-     * convenience method for sending a qick, text based email message
-     *
-     * @param string $recipient
-     * @param string $subject
-     * @param string $text      Plain text version of the message (required).
-     * @param string $html      HTML version of the message (optional).
-     * @return bool
-     */
-    public static function sendMessage($recipient, $subject, $text, $html = null)
-    {
-        $mail = new StudipMail();
-        return $mail->setSubject($subject)
-                    ->addRecipient($recipient)
-                    ->setBodyText($text)
-                    ->setBodyHtml($html)
-                    ->send();
-    }
-
-    /**
-     * convenience method for sending a qick, text based email message
-     * to the configured abuse adress
-     *
-     * @param string $subject
-     * @param string $text
-     * @return bool
-     */
-    public static function sendAbuseMessage($subject, $text)
-    {
-        $mail = new StudipMail();
-        $abuse = self::getAbuseEmail();
-        return $mail->setSubject($subject)
-                    ->addRecipient($abuse)
-                    ->setBodyText($text)
-                    ->send();
-    }
-
-    /**
-     * sets some default values for sender and reply to from
-     * configuration settings.
-     *
-     */
-    public function __construct($data = null)
-    {
-        $mail_localhost = $GLOBALS['MAIL_LOCALHOST'] ?: $_SERVER['SERVER_NAME'];
-        $this->setSenderEmail($GLOBALS['MAIL_ENV_FROM'] ?: "wwwrun@{$mail_localhost}");
-        $this->setSenderName($GLOBALS['MAIL_FROM'] ?: 'Stud.IP - ' . Config::get()->UNI_NAME_CLEAN);
-
-        if ($data) {
-            $this->setData($data);
-        }
-    }
-
-    /**
-     * @param string $mail
-     * @return StudipMail provides fluent interface
-     */
-    public function setSenderEmail($mail)
-    {
-        $this->sender['mail'] = $mail;
-        return $this;
-    }
-
-    /**
-     * @return string
-     */
-    public function getSenderEmail()
-    {
-        return $this->sender['mail'];
-    }
-
-    /**
-     * @param string $name
-     * @return StudipMail provides fluent interface
-     */
-    public function setSenderName($name)
-    {
-        $this->sender['name'] = $name;
-        return $this;
-    }
-
-    /**
-     * @return unknown_type
-     */
-    public function getSenderName()
-    {
-        return $this->sender['name'];
-    }
-
-    /**
-     * @param $mail
-     * @return StudipMail provides fluent interface
-     */
-    public function setReplyToEmail($mail)
-    {
-        $this->reply_to['mail'] = $mail;
-        return $this;
-    }
-
-    /**
-     * @return unknown_type
-     */
-    public function getReplyToEmail()
-    {
-        return $this->reply_to['mail'] ?? '';
-    }
-
-    /**
-     * @param $name
-     * @return StudipMail provides fluent interface
-     */
-    public function setReplyToName($name)
-    {
-        $this->reply_to['name'] = $name;
-        return $this;
-    }
-
-    /**
-     * @return unknown_type
-     */
-    public function getReplyToName()
-    {
-        return $this->reply_to['name'] ?? '';
-    }
-
-    /**
-     * @param $subject
-     * @return StudipMail provides fluent interface
-     */
-    public function setSubject($subject)
-    {
-        $this->subject = $subject;
-        return $this;
-    }
-
-    /**
-     * @return unknown_type
-     */
-    public function getSubject()
-    {
-        return $this->subject;
-    }
-
-    /**
-     * @param $mail
-     * @param $name
-     * @param $type
-     * @return StudipMail provides fluent interface
-     */
-    public function addRecipient($mail, $name = '', $type = 'To')
-    {
-        $type = ucfirst($type);
-        $type = in_array($type, ['To', 'Cc', 'Bcc']) ? $type : 'To';
-        if (!isset($this->recipients[$mail]) || $this->recipients[$mail]['type'] !== 'To') {
-            $this->recipients[$mail] = compact('mail', 'name', 'type');
-        }
-        return $this;
-    }
-
-    /**
-     * @param $mail
-     * @return StudipMail provides fluent interface
-     */
-    public function removeRecipient($mail)
-    {
-        unset($this->recipients[$mail]);
-        return $this;
-    }
-
-    /**
-     * @return array
-     */
-    public function getRecipients()
-    {
-        return $this->recipients;
-    }
-
-    /**
-     * @param $mail
-     * @return unknown_type
-     */
-    public function isRecipient($mail)
-    {
-        return isset($this->recipients[$mail]);
-    }
-
-    /**
-     * @param $file_name
-     * @param $name
-     * @param $type
-     * @param $disposition
-     * @return StudipMail provides fluent interface
-     */
-    public function addFileAttachment($file_name, $name = '', $type = 'automatic/name', $disposition = 'attachment')
-    {
-        $name = $name ?: basename($file_name);
-        $this->attachments[$name] = compact('file_name', 'name', 'type', 'disposition');
-        return $this;
-    }
-
-    /**
-     * @param $data
-     * @param $name
-     * @param $type
-     * @param $disposition
-     * @return StudipMail provides fluent interface
-     */
-    public function addDataAttachment($data, $name, $type = 'automatic/name', $disposition = 'attachment')
-    {
-        $this->attachments[$name] = compact('data', 'name', 'type', 'disposition');
-        return $this;
-    }
-
-    /**
-     * @param FileRef $file_ref The FileRef object of a file that shall be added to a mail
-     * @return StudipMail provides fluent interface
-     */
-    public function addStudipAttachment(FileRef $file_ref)
-    {
-        if (!$file_ref->isNew()) {
-            $this->addFileAttachment(
-                $file_ref->file->getPath(),
-                $file_ref->name
-            );
-        }
-        return $this;
-    }
-
-    public function addRelatedAttachment(string $file_name, string $name, string $type, string $content_id): void
-    {
-        $this->related_attachments[$name] = [
-            'FileName' => $file_name,
-            'Name' => $name,
-            'Content-Type' => $type,
-            'Disposition' => 'inline',
-            'Content-ID' => $content_id
-        ];
-    }
-
-    /**
-     * @param $name
-     * @return StudipMail provides fluent interface
-     */
-    public function removeAttachment($name)
-    {
-        unset($this->attachments[$name]);
-        return $this;
-    }
-
-    /**
-     * @return array
-     */
-    public function getAttachments()
-    {
-        return $this->attachments;
-    }
-
-    /**
-     * @param $name
-     * @return unknown_type
-     */
-    public function isAttachment($name)
-    {
-        return isset($this->attachments[$name]);
-    }
-
-    /**
-     * @param $body
-     * @return StudipMail provides fluent interface
-     */
-    public function setBodyText($body)
-    {
-        $this->body_text = $body;
-        return $this;
-    }
-
-    /**
-     * @return unknown_type
-     */
-    public function getBodyText()
-    {
-        return $this->body_text;
-    }
-
-    /**
-     * @param $body
-     * @return StudipMail provides fluent interface
-     */
-    public function setBodyHtml($body)
-    {
-        $this->body_html = $body;
-        return $this;
-    }
-
-    /**
-     * @return unknown_type
-     */
-    public function getBodyHtml()
-    {
-        return $this->body_html;
-    }
-
-    /**
-     * quotes the given string if it contains any characters
-     * reserved for special interpretation in RFC 2822.
-     */
-    protected static function quoteString($string)
-    {
-        // list of reserved characters in RFC 2822
-        if (strcspn($string, '()<>[]:;@\\,.') < mb_strlen($string)) {
-            $string = '"' . addcslashes($string, "\r\"\\") . '"';
-        }
-        return $string;
-    }
-
-    /**
-     * send the mail using the given transporter object, or the
-     * set default transporter
-     *
-     * @param email_message_class $transporter
-     * @return bool
-     */
-    public function send(email_message_class $transporter = null)
-    {
-        if ($transporter === null) {
-            $transporter = self::$transporter;
-        }
-        if ($transporter === null) {
-            throw new Exception('no mail transport defined');
-        }
-        $transporter->ResetMessage();
-        $transporter->SetHeader('Return-Path', $this->getSenderEmail());
-        $transporter->SetEncodedEmailHeader('From', $this->getSenderEmail(), self::quoteString($this->getSenderName()));
-        if($this->getReplyToEmail()){
-            $transporter->SetEncodedEmailHeader('Reply-To', $this->getReplyToEmail(), self::quoteString($this->getReplyToName()));
-        }
-        foreach($this->getRecipients() as $recipient) {
-            $recipients_by_type[$recipient['type']][$recipient['mail']] = self::quoteString($recipient['name']);
-        }
-        foreach($recipients_by_type as $type => $recipients){
-            $transporter->SetMultipleEncodedEmailHeader($type, $recipients);
-        }
-        $transporter->SetEncodedHeader('Subject', $this->getSubject());
-        if($this->getBodyHtml()) {
-            $html_part = 0;
-            $transporter->CreateQuotedPrintableHTMLPart($this->getBodyHtml(), "", $html_part);
-            $text_part = '';
-            $text_message = $this->getBodyText();
-
-            if(!$text_message){
-                $text_message = _('Diese Nachricht ist im HTML-Format verfasst. Sie benötigen eine E-Mail-Anwendung, die das HTML-Format anzeigen kann.');
-            }
-            $transporter->CreateQuotedPrintableTextPart($transporter->WrapText($text_message), "", $text_part);
-
-            $part = [$text_part, $html_part];
-            if (count($this->related_attachments) > 0) {
-                $relparts = [$html_part];
-                $i = 99;
-                $multipart = 0;
-                foreach ($this->related_attachments as $one) {
-                    $transporter->CreateFilePart($one, $i);
-                    $relparts[] = $i;
-                }
-                $transporter->CreateRelatedMultipart($relparts, $multipart);
-                $part = [$text_part, $multipart];
-            }
-            $transporter->AddAlternativeMultipart($part);
-        } else {
-            $transporter->AddQuotedPrintableTextPart($this->getBodyText());
-        }
-        foreach($this->getAttachments() as $attachment){
-            $part = [
-                'FileName'     => $attachment['file_name'],
-                'Data'         => $attachment['data'],
-                'Name'         => $attachment['name'],
-                'Content-Type' => $attachment['type'],
-                'Disposition'  => $attachment['disposition'],
-            ];
-            $transporter->addFilePart($part);
-        }
-        $error = $transporter->Send();
-        if (mb_strlen($error) === 0) {
-            return true;
-        } else {
-            Log::error(get_class($transporter) . '::Send() - ' . $error);
-            return false;
-        }
-    }
-
-    public function toArray()
-    {
-        return get_object_vars($this);
-    }
-
-    public function setData($data)
-    {
-        foreach ($data as $name => $value) {
-            $this->$name = $value;
-        }
-    }
-}
diff --git a/lib/classes/StudipMail.php b/lib/classes/StudipMail.php
new file mode 100644
index 0000000..4974f66
--- /dev/null
+++ b/lib/classes/StudipMail.php
@@ -0,0 +1,487 @@
+, Suchi & Berg GmbH 
+ * @version 1
+ * @license GPL2 or any later version
+ * @copyright 2009 authors
+ */
+class StudipMail
+{
+    /**
+     * @var email_message_class
+     * @static
+     */
+    private static $transporter;
+
+    /**
+     * @var string
+     */
+    private $body_text;
+    /**
+     * @var string
+     */
+    private $body_html;
+    /**
+     * @var string
+     */
+    private $subject;
+    /**
+     * Array of all attachments, name ist key
+     * @var array
+     */
+    private $attachments = [];
+    /**
+     * Array of attachments that are related to the content
+     * @var array
+     */
+    private $related_attachments = [];
+    /**
+     * @var array
+     */
+    private $sender;
+    /**
+     * Array of all recipients, mail is key
+     * @var array
+     */
+    private $recipients = [];
+    /**
+     * @var array
+     */
+    private $reply_to;
+
+    /**
+     * Sets the default transporter used in StudipMail::send()
+     * @param email_message_class $transporter
+     * @return void
+     */
+    public static function setDefaultTransporter(email_message_class $transporter)
+    {
+        self::$transporter = $transporter;
+    }
+
+    /**
+     * gets the default transporter used in StudipMail::send()
+     *
+     * @return email_message_class
+     */
+    public static function getDefaultTransporter()
+    {
+        return self::$transporter;
+    }
+
+    /**
+     * Gets the configured abuse mail contact
+     *
+     * @return string
+     */
+    public static function getAbuseEmail()
+    {
+        return $GLOBALS['MAIL_ABUSE'] ?: "abuse@{$mail_localhost}";
+    }
+
+    /**
+     * convenience method for sending a qick, text based email message
+     *
+     * @param string $recipient
+     * @param string $subject
+     * @param string $text      Plain text version of the message (required).
+     * @param string $html      HTML version of the message (optional).
+     * @return bool
+     */
+    public static function sendMessage($recipient, $subject, $text, $html = null)
+    {
+        $mail = new StudipMail();
+        return $mail->setSubject($subject)
+                    ->addRecipient($recipient)
+                    ->setBodyText($text)
+                    ->setBodyHtml($html)
+                    ->send();
+    }
+
+    /**
+     * convenience method for sending a qick, text based email message
+     * to the configured abuse adress
+     *
+     * @param string $subject
+     * @param string $text
+     * @return bool
+     */
+    public static function sendAbuseMessage($subject, $text)
+    {
+        $mail = new StudipMail();
+        $abuse = self::getAbuseEmail();
+        return $mail->setSubject($subject)
+                    ->addRecipient($abuse)
+                    ->setBodyText($text)
+                    ->send();
+    }
+
+    /**
+     * sets some default values for sender and reply to from
+     * configuration settings.
+     *
+     */
+    public function __construct($data = null)
+    {
+        $mail_localhost = $GLOBALS['MAIL_LOCALHOST'] ?: $_SERVER['SERVER_NAME'];
+        $this->setSenderEmail($GLOBALS['MAIL_ENV_FROM'] ?: "wwwrun@{$mail_localhost}");
+        $this->setSenderName($GLOBALS['MAIL_FROM'] ?: 'Stud.IP - ' . Config::get()->UNI_NAME_CLEAN);
+
+        if ($data) {
+            $this->setData($data);
+        }
+    }
+
+    /**
+     * @param string $mail
+     * @return StudipMail provides fluent interface
+     */
+    public function setSenderEmail($mail)
+    {
+        $this->sender['mail'] = $mail;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getSenderEmail()
+    {
+        return $this->sender['mail'];
+    }
+
+    /**
+     * @param string $name
+     * @return StudipMail provides fluent interface
+     */
+    public function setSenderName($name)
+    {
+        $this->sender['name'] = $name;
+        return $this;
+    }
+
+    /**
+     * @return unknown_type
+     */
+    public function getSenderName()
+    {
+        return $this->sender['name'];
+    }
+
+    /**
+     * @param $mail
+     * @return StudipMail provides fluent interface
+     */
+    public function setReplyToEmail($mail)
+    {
+        $this->reply_to['mail'] = $mail;
+        return $this;
+    }
+
+    /**
+     * @return unknown_type
+     */
+    public function getReplyToEmail()
+    {
+        return $this->reply_to['mail'] ?? '';
+    }
+
+    /**
+     * @param $name
+     * @return StudipMail provides fluent interface
+     */
+    public function setReplyToName($name)
+    {
+        $this->reply_to['name'] = $name;
+        return $this;
+    }
+
+    /**
+     * @return unknown_type
+     */
+    public function getReplyToName()
+    {
+        return $this->reply_to['name'] ?? '';
+    }
+
+    /**
+     * @param $subject
+     * @return StudipMail provides fluent interface
+     */
+    public function setSubject($subject)
+    {
+        $this->subject = $subject;
+        return $this;
+    }
+
+    /**
+     * @return unknown_type
+     */
+    public function getSubject()
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @param $mail
+     * @param $name
+     * @param $type
+     * @return StudipMail provides fluent interface
+     */
+    public function addRecipient($mail, $name = '', $type = 'To')
+    {
+        $type = ucfirst($type);
+        $type = in_array($type, ['To', 'Cc', 'Bcc']) ? $type : 'To';
+        if (!isset($this->recipients[$mail]) || $this->recipients[$mail]['type'] !== 'To') {
+            $this->recipients[$mail] = compact('mail', 'name', 'type');
+        }
+        return $this;
+    }
+
+    /**
+     * @param $mail
+     * @return StudipMail provides fluent interface
+     */
+    public function removeRecipient($mail)
+    {
+        unset($this->recipients[$mail]);
+        return $this;
+    }
+
+    /**
+     * @return array
+     */
+    public function getRecipients()
+    {
+        return $this->recipients;
+    }
+
+    /**
+     * @param $mail
+     * @return unknown_type
+     */
+    public function isRecipient($mail)
+    {
+        return isset($this->recipients[$mail]);
+    }
+
+    /**
+     * @param $file_name
+     * @param $name
+     * @param $type
+     * @param $disposition
+     * @return StudipMail provides fluent interface
+     */
+    public function addFileAttachment($file_name, $name = '', $type = 'automatic/name', $disposition = 'attachment')
+    {
+        $name = $name ?: basename($file_name);
+        $this->attachments[$name] = compact('file_name', 'name', 'type', 'disposition');
+        return $this;
+    }
+
+    /**
+     * @param $data
+     * @param $name
+     * @param $type
+     * @param $disposition
+     * @return StudipMail provides fluent interface
+     */
+    public function addDataAttachment($data, $name, $type = 'automatic/name', $disposition = 'attachment')
+    {
+        $this->attachments[$name] = compact('data', 'name', 'type', 'disposition');
+        return $this;
+    }
+
+    /**
+     * @param FileRef $file_ref The FileRef object of a file that shall be added to a mail
+     * @return StudipMail provides fluent interface
+     */
+    public function addStudipAttachment(FileRef $file_ref)
+    {
+        if (!$file_ref->isNew()) {
+            $this->addFileAttachment(
+                $file_ref->file->getPath(),
+                $file_ref->name
+            );
+        }
+        return $this;
+    }
+
+    public function addRelatedAttachment(string $file_name, string $name, string $type, string $content_id): void
+    {
+        $this->related_attachments[$name] = [
+            'FileName' => $file_name,
+            'Name' => $name,
+            'Content-Type' => $type,
+            'Disposition' => 'inline',
+            'Content-ID' => $content_id
+        ];
+    }
+
+    /**
+     * @param $name
+     * @return StudipMail provides fluent interface
+     */
+    public function removeAttachment($name)
+    {
+        unset($this->attachments[$name]);
+        return $this;
+    }
+
+    /**
+     * @return array
+     */
+    public function getAttachments()
+    {
+        return $this->attachments;
+    }
+
+    /**
+     * @param $name
+     * @return unknown_type
+     */
+    public function isAttachment($name)
+    {
+        return isset($this->attachments[$name]);
+    }
+
+    /**
+     * @param $body
+     * @return StudipMail provides fluent interface
+     */
+    public function setBodyText($body)
+    {
+        $this->body_text = $body;
+        return $this;
+    }
+
+    /**
+     * @return unknown_type
+     */
+    public function getBodyText()
+    {
+        return $this->body_text;
+    }
+
+    /**
+     * @param $body
+     * @return StudipMail provides fluent interface
+     */
+    public function setBodyHtml($body)
+    {
+        $this->body_html = $body;
+        return $this;
+    }
+
+    /**
+     * @return unknown_type
+     */
+    public function getBodyHtml()
+    {
+        return $this->body_html;
+    }
+
+    /**
+     * quotes the given string if it contains any characters
+     * reserved for special interpretation in RFC 2822.
+     */
+    protected static function quoteString($string)
+    {
+        // list of reserved characters in RFC 2822
+        if (strcspn($string, '()<>[]:;@\\,.') < mb_strlen($string)) {
+            $string = '"' . addcslashes($string, "\r\"\\") . '"';
+        }
+        return $string;
+    }
+
+    /**
+     * send the mail using the given transporter object, or the
+     * set default transporter
+     *
+     * @param email_message_class $transporter
+     * @return bool
+     */
+    public function send(email_message_class $transporter = null)
+    {
+        if ($transporter === null) {
+            $transporter = self::$transporter;
+        }
+        if ($transporter === null) {
+            throw new Exception('no mail transport defined');
+        }
+        $transporter->ResetMessage();
+        $transporter->SetHeader('Return-Path', $this->getSenderEmail());
+        $transporter->SetEncodedEmailHeader('From', $this->getSenderEmail(), self::quoteString($this->getSenderName()));
+        if($this->getReplyToEmail()){
+            $transporter->SetEncodedEmailHeader('Reply-To', $this->getReplyToEmail(), self::quoteString($this->getReplyToName()));
+        }
+        foreach($this->getRecipients() as $recipient) {
+            $recipients_by_type[$recipient['type']][$recipient['mail']] = self::quoteString($recipient['name']);
+        }
+        foreach($recipients_by_type as $type => $recipients){
+            $transporter->SetMultipleEncodedEmailHeader($type, $recipients);
+        }
+        $transporter->SetEncodedHeader('Subject', $this->getSubject());
+        if($this->getBodyHtml()) {
+            $html_part = 0;
+            $transporter->CreateQuotedPrintableHTMLPart($this->getBodyHtml(), "", $html_part);
+            $text_part = '';
+            $text_message = $this->getBodyText();
+
+            if(!$text_message){
+                $text_message = _('Diese Nachricht ist im HTML-Format verfasst. Sie benötigen eine E-Mail-Anwendung, die das HTML-Format anzeigen kann.');
+            }
+            $transporter->CreateQuotedPrintableTextPart($transporter->WrapText($text_message), "", $text_part);
+
+            $part = [$text_part, $html_part];
+            if (count($this->related_attachments) > 0) {
+                $relparts = [$html_part];
+                $i = 99;
+                $multipart = 0;
+                foreach ($this->related_attachments as $one) {
+                    $transporter->CreateFilePart($one, $i);
+                    $relparts[] = $i;
+                }
+                $transporter->CreateRelatedMultipart($relparts, $multipart);
+                $part = [$text_part, $multipart];
+            }
+            $transporter->AddAlternativeMultipart($part);
+        } else {
+            $transporter->AddQuotedPrintableTextPart($this->getBodyText());
+        }
+        foreach($this->getAttachments() as $attachment){
+            $part = [
+                'FileName'     => $attachment['file_name'],
+                'Data'         => $attachment['data'],
+                'Name'         => $attachment['name'],
+                'Content-Type' => $attachment['type'],
+                'Disposition'  => $attachment['disposition'],
+            ];
+            $transporter->addFilePart($part);
+        }
+        $error = $transporter->Send();
+        if (mb_strlen($error) === 0) {
+            return true;
+        } else {
+            Log::error(get_class($transporter) . '::Send() - ' . $error);
+            return false;
+        }
+    }
+
+    public function toArray()
+    {
+        return get_object_vars($this);
+    }
+
+    public function setData($data)
+    {
+        foreach ($data as $name => $value) {
+            $this->$name = $value;
+        }
+    }
+}
diff --git a/lib/classes/StudipObject.class.php b/lib/classes/StudipObject.class.php
deleted file mode 100644
index 151fcdb..0000000
--- a/lib/classes/StudipObject.class.php
+++ /dev/null
@@ -1,199 +0,0 @@
-
-// +--------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +--------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +--------------------------------------------------------------------------+
-
-
-# Define all required constants ============================================= #
-/**
- * @const INSTANCEOF_STUDIPOBJECT Is instance of a studip object
- * @access public
- */
-define ("INSTANCEOF_STUDIPOBJECT", "StudipObject");
-# =========================================================================== #
-
-
-/**
- * StudipObject.class.php
- *
- * Class to provide basic properties of an StudipObject in Stud.IP
- *
- * @author      Alexander Willner 
- * @copyright   2003 Stud.IP-Project
- * @access      public
- * @package     studip_core
- * @modulegroup core
- */
-class StudipObject extends AuthorObject {
-
-# Define all required variables ============================================= #
-  /**
-   * The unique ID
-   * @access   private
-   * @var      integer $id
-   */
-  var $objectID;
-
-  /**
-   * The unique ID of the author
-   * @access   private
-   * @var      string  $authorID
-   */
-  var $authorID;
-
-  /**
-   * The unique range ID
-   * @access   private
-   * @var      string  $rangeID
-   */
-  var $rangeID;
-# =========================================================================== #
-
-
-
-# Define constructor and destructor ========================================= #
-   /**
-    * Constructor
-    * @access   public
-    * @param    string   $objectID   The ID of an existing object
-    */
-   function __construct($objectID = "") {
-
-     /* For good OOP: Call constructor ------------------------------------- */
-     parent::__construct();
-     $this->instanceof = INSTANCEOF_STUDIPOBJECT;
-     /* -------------------------------------------------------------------- */
-
-     /* Set default values ------------------------------------------------- */
-     $this->objectID = $objectID;
-     $this->authorID = "";
-     $this->rangeID  = "";
-     /* -------------------------------------------------------------------- */
-   }
-# =========================================================================== #
-
-
-# Define public functions =================================================== #
-   /**
-    * Creates a new ID
-    * @access  public
-    * @return  string  The new ID
-    */
-   public static function createNewID () {
-     srand ((double) microtime () * 1000000);
-     return md5 (uniqid (rand ()));
-   }
-
-   /**
-    * Gets the objectID
-    * @access  public
-    * @return  string  The objectID
-    */
-   function getObjectID () {
-     return $this->objectID;
-   }
-
-   /**
-    * Sets the objectID
-    * @access  public
-    * @param   String  $objectID  The object ID
-    */
-   function setObjectID ($objectID) {
-     if (empty ($objectID))
-       $this->throwError (1, _("Die ObjectID darf nicht leer sein."));
-     else
-       $this->objectID = $objectID;
-   }
-
-   /**
-    * Gets the authorID
-    * @access  public
-    * @return  string  The authorID
-    */
-   function getAuthorID () {
-     return $this->authorID;
-   }
-
-   /**
-    * Gets the authorname
-    * @access  public
-    * @return  string  The authorID
-    */
-   function getAuthor () {
-     return get_username ($this->authorID);
-   }
-
-   /**
-    * Gets the full name of the author
-    * @access  public
-    * @return  string  The authorID
-    */
-   function getFullName () {
-     return get_fullname ($this->authorID);
-   }
-
-   /**
-    * Sets the authorID
-    * @access  public
-    * @param   String  $authorID  The author ID
-    */
-   function setAuthorID ($authorID) {
-     if (empty ($authorID))
-       $this->throwError (1, _("Die AuthorID darf nicht leer sein."));
-     else
-       $this->authorID = $authorID;
-   }
-
-   /**
-    * Gets the rangeID
-    * @access  public
-    * @return  string  The rangeID
-    */
-   function getRangeID () {
-     return $this->rangeID;
-   }
-
-   /**
-    * Sets the rangeID
-    * @access  public
-    * @param   String  $rangeID  The range ID
-    */
-   function setRangeID ($rangeID) {
-       $this->rangeID = $rangeID;
-   }
-# =========================================================================== #
-
-
-# Define static functions =================================================== #
-
-# =========================================================================== #
-
-
-# Define private functions ================================================== #
-
-# =========================================================================== #
-
-}
-?>
diff --git a/lib/classes/StudipObject.php b/lib/classes/StudipObject.php
new file mode 100644
index 0000000..151fcdb
--- /dev/null
+++ b/lib/classes/StudipObject.php
@@ -0,0 +1,199 @@
+
+// +--------------------------------------------------------------------------+
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or any later version.
+// +--------------------------------------------------------------------------+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+// +--------------------------------------------------------------------------+
+
+
+# Define all required constants ============================================= #
+/**
+ * @const INSTANCEOF_STUDIPOBJECT Is instance of a studip object
+ * @access public
+ */
+define ("INSTANCEOF_STUDIPOBJECT", "StudipObject");
+# =========================================================================== #
+
+
+/**
+ * StudipObject.class.php
+ *
+ * Class to provide basic properties of an StudipObject in Stud.IP
+ *
+ * @author      Alexander Willner 
+ * @copyright   2003 Stud.IP-Project
+ * @access      public
+ * @package     studip_core
+ * @modulegroup core
+ */
+class StudipObject extends AuthorObject {
+
+# Define all required variables ============================================= #
+  /**
+   * The unique ID
+   * @access   private
+   * @var      integer $id
+   */
+  var $objectID;
+
+  /**
+   * The unique ID of the author
+   * @access   private
+   * @var      string  $authorID
+   */
+  var $authorID;
+
+  /**
+   * The unique range ID
+   * @access   private
+   * @var      string  $rangeID
+   */
+  var $rangeID;
+# =========================================================================== #
+
+
+
+# Define constructor and destructor ========================================= #
+   /**
+    * Constructor
+    * @access   public
+    * @param    string   $objectID   The ID of an existing object
+    */
+   function __construct($objectID = "") {
+
+     /* For good OOP: Call constructor ------------------------------------- */
+     parent::__construct();
+     $this->instanceof = INSTANCEOF_STUDIPOBJECT;
+     /* -------------------------------------------------------------------- */
+
+     /* Set default values ------------------------------------------------- */
+     $this->objectID = $objectID;
+     $this->authorID = "";
+     $this->rangeID  = "";
+     /* -------------------------------------------------------------------- */
+   }
+# =========================================================================== #
+
+
+# Define public functions =================================================== #
+   /**
+    * Creates a new ID
+    * @access  public
+    * @return  string  The new ID
+    */
+   public static function createNewID () {
+     srand ((double) microtime () * 1000000);
+     return md5 (uniqid (rand ()));
+   }
+
+   /**
+    * Gets the objectID
+    * @access  public
+    * @return  string  The objectID
+    */
+   function getObjectID () {
+     return $this->objectID;
+   }
+
+   /**
+    * Sets the objectID
+    * @access  public
+    * @param   String  $objectID  The object ID
+    */
+   function setObjectID ($objectID) {
+     if (empty ($objectID))
+       $this->throwError (1, _("Die ObjectID darf nicht leer sein."));
+     else
+       $this->objectID = $objectID;
+   }
+
+   /**
+    * Gets the authorID
+    * @access  public
+    * @return  string  The authorID
+    */
+   function getAuthorID () {
+     return $this->authorID;
+   }
+
+   /**
+    * Gets the authorname
+    * @access  public
+    * @return  string  The authorID
+    */
+   function getAuthor () {
+     return get_username ($this->authorID);
+   }
+
+   /**
+    * Gets the full name of the author
+    * @access  public
+    * @return  string  The authorID
+    */
+   function getFullName () {
+     return get_fullname ($this->authorID);
+   }
+
+   /**
+    * Sets the authorID
+    * @access  public
+    * @param   String  $authorID  The author ID
+    */
+   function setAuthorID ($authorID) {
+     if (empty ($authorID))
+       $this->throwError (1, _("Die AuthorID darf nicht leer sein."));
+     else
+       $this->authorID = $authorID;
+   }
+
+   /**
+    * Gets the rangeID
+    * @access  public
+    * @return  string  The rangeID
+    */
+   function getRangeID () {
+     return $this->rangeID;
+   }
+
+   /**
+    * Sets the rangeID
+    * @access  public
+    * @param   String  $rangeID  The range ID
+    */
+   function setRangeID ($rangeID) {
+       $this->rangeID = $rangeID;
+   }
+# =========================================================================== #
+
+
+# Define static functions =================================================== #
+
+# =========================================================================== #
+
+
+# Define private functions ================================================== #
+
+# =========================================================================== #
+
+}
+?>
diff --git a/lib/classes/StudipPDO.class.php b/lib/classes/StudipPDO.class.php
deleted file mode 100644
index 8a1422b..0000000
--- a/lib/classes/StudipPDO.class.php
+++ /dev/null
@@ -1,385 +0,0 @@
-query_count += 1;
-    }
-
-    /**
-     * Replaces all string literals in the statement with placeholders.
-     *
-     * @param string    SQL statement
-     * @return string   modified SQL statement
-     */
-    protected static function replaceStrings($statement)
-    {
-        $count = mb_substr_count($statement, '"') + mb_substr_count($statement, "'") + mb_substr_count($statement, '\\');
-
-        // use fast preg_replace() variant if possible
-        if ($count < 1000) {
-            $result = preg_replace('/"(""|\\\\.|[^\\\\"]+)*"|\'(\'\'|\\\\.|[^\\\\\']+)*\'/s', '?', $statement);
-        }
-
-        if (!isset($result)) {
-            // split string into parts at quotes and backslash
-            $parts = preg_split('/([\\\\"\'])/', $statement, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-            $result = '';
-            $quote_chr = null;
-
-            for ($part = current($parts); $part !== false; $part = next($parts)) {
-                // inside quotes, "" is ", '' is ' and \x is x
-                if ($quote_chr !== NULL) {
-                    if ($part === $quote_chr) {
-                        $part = next($parts);
-
-                        if ($part !== $quote_chr) {
-                            // backtrack and terminate string
-                            prev($parts);
-                            $result .= '?';
-                            $quote_chr = NULL;
-                        }
-                    } else if ($part === '\\') {
-                        // skip next part
-                        next($parts);
-                    }
-                } else if ($part === "'" || $part === '"') {
-                    $quote_chr = $part;
-                    $saved_pos = key($parts);
-                } else {
-                    $result .= $part;
-                }
-            }
-
-            if ($quote_chr !== NULL) {
-                // unterminated quote: copy to end of string
-                $result .= implode(array_slice($parts, $saved_pos));
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Quotes the given value in a form appropriate for the type.
-     * If no explicit type is given, the value's PHP type is used.
-     *
-     * @param mixed $string PHP value to quote
-     * @param ?int $type parameter type (e.g. PDO::PARAM_STR)
-     * @return string|false quoted SQL string
-     */
-    public function quote($string, $type = null): false|string
-    {
-        if (!isset($type)) {
-            if (is_null($string)) {
-                $type = PDO::PARAM_NULL;
-            } else if (is_bool($string)) {
-                $type = PDO::PARAM_BOOL;
-            } else if (is_int($string)) {
-                $type = PDO::PARAM_INT;
-            } else if (is_array($string)) {
-                $type = StudipPDO::PARAM_ARRAY;
-            } else {
-                $type = PDO::PARAM_STR;
-            }
-        }
-
-        switch ($type) {
-            case PDO::PARAM_NULL:
-                return 'NULL';
-            case PDO::PARAM_BOOL:
-                return $string ? '1' : '0';
-            case PDO::PARAM_INT:
-                return (int) $string;
-            case StudipPDO::PARAM_ARRAY:
-                return is_array($string) && count($string) ? join(',', array_map([$this, 'quote'], $string)) : 'NULL';
-            case StudipPDO::PARAM_COLUMN:
-                return preg_replace('/\\W/', '', $string);
-            default:
-                return parent::quote($string);
-        }
-    }
-
-    /**
-     * Executes an SQL statement and returns the number of affected rows.
-     *
-     * @param string $statement SQL statement
-     */
-    public function exec(string $statement): false|int
-    {
-        $this->verify($statement);
-        return parent::exec($statement);
-    }
-
-    /**
-     * Executes an SQL statement, returning a result set as a statement object.
-     *
-     * @param string    $statement  SQL statement
-     * @param int       $fetch_mode fetch mode (optional)
-     * @param mixed  ...$fetch_args fetch mode parameters (see PDOStatement::setFetchMode)
-     * @return object   PDOStatement object
-     */
-    #[ReturnTypeWillChange]
-    public function query($statement, $fetch_mode = NULL, ...$fetch_args)
-    {
-        $this->verify($statement);
-
-        if (isset($fetch_mode)) {
-            $stmt = parent::query($statement, $fetch_mode, ...$fetch_args);
-        } else {
-            $stmt = parent::query($statement);
-        }
-
-        $studip_stmt = new StudipPDOStatement($this, $statement, []);
-        $studip_stmt->setStatement($stmt);
-        return $studip_stmt;
-    }
-
-    /**
-     * Prepares a statement for execution and returns a statement object.
-     *
-     * @param string $statement SQL statement
-     * @param array  $driver_options
-     *
-     * @return StudipPDOStatement
-     */
-    #[ReturnTypeWillChange]
-    public function prepare($statement, $driver_options = [])
-    {
-        $this->verify($statement);
-        return new StudipPDOStatement($this, $statement, $driver_options);
-    }
-
-    /**
-     * This method is intended only for use by the StudipPDOStatement class.
-     *
-     * @param string    SQL statement
-     * @return object   PDOStatement object
-     */
-    public function prepareStatement($statement, $driver_options = [])
-    {
-        return parent::prepare($statement, $driver_options);
-    }
-
-    /**
-     * Executes sql statement with given parameters,
-     * returns number of affected rows, use only for INSERT,UPDATE etc
-     *
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @return integer number of affected rows
-     */
-    public function execute($statement, $input_parameters = null)
-    {
-        $st = $this->prepare($statement);
-        $ok = $st->execute($input_parameters);
-        if ($ok === true) {
-            return $st->rowCount();
-        }
-        return 0;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as sequential array, each row as associative array
-     * optionally apply given callable on each row, with current row and key as parameter
-     *
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @param callable $callable callable to be applied to each of the rows
-     * @return array result set as array of assoc arrays
-     */
-    public function fetchAll($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        if (is_callable($callable)) {
-            $data = [];
-            $st->setFetchMode(PDO::FETCH_ASSOC);
-            foreach ($st as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        } else {
-            $data = $st->fetchAll(PDO::FETCH_ASSOC);
-        }
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch only
-     * the values from first column as sequential array
-     * optionally apply given callable on each row, with current value and key as parameter
-     *
-     * @see StudipPDOStatement::fetchFirst()
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @param callable $callable callable to be applied to each of the rows
-     * @return array result set
-     */
-    public function fetchFirst($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchFirst();
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as associative array, first columns value is used as a key, the others are grouped
-     * optionally apply given callable on each grouped row, with current row and key as parameter
-     * if no callable is given, 'current' is used, to return the first entry of the grouped row
-     *
-     * @see StudipPDOStatement::fetchGrouped()
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @param callable $callable callable to be applied to each of the rows
-     * @return array result set
-     */
-    public function fetchGrouped($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchGrouped(PDO::FETCH_ASSOC, is_null($callable) ? 'current' : null);
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as associative array, first columns value is used as a key, the other one is grouped
-     * use only when selecting 2 columns
-     * optionally apply given callable on each grouped row, with current row and key as parameter
-     *
-     * @see StudipPDOStatement::fetchGroupedPairs()
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @param callable $callable callable to be applied to each of the rows
-     * @return array result set
-     */
-    public function fetchGroupedPairs($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchGroupedPairs();
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as associative array, first columns value is used as a key, the other one as the value
-     * use only when selecting 2 columns
-     * optionally apply given callable on each grouped row, with current row and key as parameter
-     *
-     * @see StudipPDOStatement::fetchGroupedPairs()
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @param callable $callable callable to be applied to each of the rows
-     * @return array result set
-     */
-    public function fetchPairs($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchPairs();
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch only the first row
-     * as associative array
-     *
-     * @see StudipPDOStatement::fetchOne()
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @return array first row of result set
-     */
-    public function fetchOne($statement, $input_parameters = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        return $st->fetchOne();
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch only the value of one column
-     * third param denotes the column, zero indexed
-     *
-     * @param string $statement SQL statement to execute
-     * @param array $input_parameters parameters for statement
-     * @param integer $column number of column to fetch
-     * @return string value of chosen column
-     */
-    public function fetchColumn($statement, $input_parameters = null, $column = 0)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        return $st->fetchColumn($column);
-    }
-
-    /**
-     * Determine if the connected database is a MariaDB database.
-     *
-     * @return bool
-     */
-    public function isMariaDB(): bool
-    {
-        return stripos($this->getAttribute(\PDO::ATTR_SERVER_VERSION), 'MariaDB') !== false;
-    }
-}
diff --git a/lib/classes/StudipPDO.php b/lib/classes/StudipPDO.php
new file mode 100644
index 0000000..8a1422b
--- /dev/null
+++ b/lib/classes/StudipPDO.php
@@ -0,0 +1,385 @@
+query_count += 1;
+    }
+
+    /**
+     * Replaces all string literals in the statement with placeholders.
+     *
+     * @param string    SQL statement
+     * @return string   modified SQL statement
+     */
+    protected static function replaceStrings($statement)
+    {
+        $count = mb_substr_count($statement, '"') + mb_substr_count($statement, "'") + mb_substr_count($statement, '\\');
+
+        // use fast preg_replace() variant if possible
+        if ($count < 1000) {
+            $result = preg_replace('/"(""|\\\\.|[^\\\\"]+)*"|\'(\'\'|\\\\.|[^\\\\\']+)*\'/s', '?', $statement);
+        }
+
+        if (!isset($result)) {
+            // split string into parts at quotes and backslash
+            $parts = preg_split('/([\\\\"\'])/', $statement, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+            $result = '';
+            $quote_chr = null;
+
+            for ($part = current($parts); $part !== false; $part = next($parts)) {
+                // inside quotes, "" is ", '' is ' and \x is x
+                if ($quote_chr !== NULL) {
+                    if ($part === $quote_chr) {
+                        $part = next($parts);
+
+                        if ($part !== $quote_chr) {
+                            // backtrack and terminate string
+                            prev($parts);
+                            $result .= '?';
+                            $quote_chr = NULL;
+                        }
+                    } else if ($part === '\\') {
+                        // skip next part
+                        next($parts);
+                    }
+                } else if ($part === "'" || $part === '"') {
+                    $quote_chr = $part;
+                    $saved_pos = key($parts);
+                } else {
+                    $result .= $part;
+                }
+            }
+
+            if ($quote_chr !== NULL) {
+                // unterminated quote: copy to end of string
+                $result .= implode(array_slice($parts, $saved_pos));
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Quotes the given value in a form appropriate for the type.
+     * If no explicit type is given, the value's PHP type is used.
+     *
+     * @param mixed $string PHP value to quote
+     * @param ?int $type parameter type (e.g. PDO::PARAM_STR)
+     * @return string|false quoted SQL string
+     */
+    public function quote($string, $type = null): false|string
+    {
+        if (!isset($type)) {
+            if (is_null($string)) {
+                $type = PDO::PARAM_NULL;
+            } else if (is_bool($string)) {
+                $type = PDO::PARAM_BOOL;
+            } else if (is_int($string)) {
+                $type = PDO::PARAM_INT;
+            } else if (is_array($string)) {
+                $type = StudipPDO::PARAM_ARRAY;
+            } else {
+                $type = PDO::PARAM_STR;
+            }
+        }
+
+        switch ($type) {
+            case PDO::PARAM_NULL:
+                return 'NULL';
+            case PDO::PARAM_BOOL:
+                return $string ? '1' : '0';
+            case PDO::PARAM_INT:
+                return (int) $string;
+            case StudipPDO::PARAM_ARRAY:
+                return is_array($string) && count($string) ? join(',', array_map([$this, 'quote'], $string)) : 'NULL';
+            case StudipPDO::PARAM_COLUMN:
+                return preg_replace('/\\W/', '', $string);
+            default:
+                return parent::quote($string);
+        }
+    }
+
+    /**
+     * Executes an SQL statement and returns the number of affected rows.
+     *
+     * @param string $statement SQL statement
+     */
+    public function exec(string $statement): false|int
+    {
+        $this->verify($statement);
+        return parent::exec($statement);
+    }
+
+    /**
+     * Executes an SQL statement, returning a result set as a statement object.
+     *
+     * @param string    $statement  SQL statement
+     * @param int       $fetch_mode fetch mode (optional)
+     * @param mixed  ...$fetch_args fetch mode parameters (see PDOStatement::setFetchMode)
+     * @return object   PDOStatement object
+     */
+    #[ReturnTypeWillChange]
+    public function query($statement, $fetch_mode = NULL, ...$fetch_args)
+    {
+        $this->verify($statement);
+
+        if (isset($fetch_mode)) {
+            $stmt = parent::query($statement, $fetch_mode, ...$fetch_args);
+        } else {
+            $stmt = parent::query($statement);
+        }
+
+        $studip_stmt = new StudipPDOStatement($this, $statement, []);
+        $studip_stmt->setStatement($stmt);
+        return $studip_stmt;
+    }
+
+    /**
+     * Prepares a statement for execution and returns a statement object.
+     *
+     * @param string $statement SQL statement
+     * @param array  $driver_options
+     *
+     * @return StudipPDOStatement
+     */
+    #[ReturnTypeWillChange]
+    public function prepare($statement, $driver_options = [])
+    {
+        $this->verify($statement);
+        return new StudipPDOStatement($this, $statement, $driver_options);
+    }
+
+    /**
+     * This method is intended only for use by the StudipPDOStatement class.
+     *
+     * @param string    SQL statement
+     * @return object   PDOStatement object
+     */
+    public function prepareStatement($statement, $driver_options = [])
+    {
+        return parent::prepare($statement, $driver_options);
+    }
+
+    /**
+     * Executes sql statement with given parameters,
+     * returns number of affected rows, use only for INSERT,UPDATE etc
+     *
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @return integer number of affected rows
+     */
+    public function execute($statement, $input_parameters = null)
+    {
+        $st = $this->prepare($statement);
+        $ok = $st->execute($input_parameters);
+        if ($ok === true) {
+            return $st->rowCount();
+        }
+        return 0;
+    }
+
+    /**
+     * Executes sql statement with given parameters, and fetch results
+     * as sequential array, each row as associative array
+     * optionally apply given callable on each row, with current row and key as parameter
+     *
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @param callable $callable callable to be applied to each of the rows
+     * @return array result set as array of assoc arrays
+     */
+    public function fetchAll($statement, $input_parameters = null, $callable = null)
+    {
+        $st = $this->prepare($statement);
+        $st->execute($input_parameters);
+        if (is_callable($callable)) {
+            $data = [];
+            $st->setFetchMode(PDO::FETCH_ASSOC);
+            foreach ($st as $key => $row) {
+                $data[$key] = call_user_func($callable, $row, $key);
+            }
+        } else {
+            $data = $st->fetchAll(PDO::FETCH_ASSOC);
+        }
+        return $data;
+    }
+
+    /**
+     * Executes sql statement with given parameters, and fetch only
+     * the values from first column as sequential array
+     * optionally apply given callable on each row, with current value and key as parameter
+     *
+     * @see StudipPDOStatement::fetchFirst()
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @param callable $callable callable to be applied to each of the rows
+     * @return array result set
+     */
+    public function fetchFirst($statement, $input_parameters = null, $callable = null)
+    {
+        $st = $this->prepare($statement);
+        $st->execute($input_parameters);
+        $data = $st->fetchFirst();
+        if (is_callable($callable)) {
+            foreach ($data as $key => $row) {
+                $data[$key] = call_user_func($callable, $row, $key);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * Executes sql statement with given parameters, and fetch results
+     * as associative array, first columns value is used as a key, the others are grouped
+     * optionally apply given callable on each grouped row, with current row and key as parameter
+     * if no callable is given, 'current' is used, to return the first entry of the grouped row
+     *
+     * @see StudipPDOStatement::fetchGrouped()
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @param callable $callable callable to be applied to each of the rows
+     * @return array result set
+     */
+    public function fetchGrouped($statement, $input_parameters = null, $callable = null)
+    {
+        $st = $this->prepare($statement);
+        $st->execute($input_parameters);
+        $data = $st->fetchGrouped(PDO::FETCH_ASSOC, is_null($callable) ? 'current' : null);
+        if (is_callable($callable)) {
+            foreach ($data as $key => $row) {
+                $data[$key] = call_user_func($callable, $row, $key);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * Executes sql statement with given parameters, and fetch results
+     * as associative array, first columns value is used as a key, the other one is grouped
+     * use only when selecting 2 columns
+     * optionally apply given callable on each grouped row, with current row and key as parameter
+     *
+     * @see StudipPDOStatement::fetchGroupedPairs()
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @param callable $callable callable to be applied to each of the rows
+     * @return array result set
+     */
+    public function fetchGroupedPairs($statement, $input_parameters = null, $callable = null)
+    {
+        $st = $this->prepare($statement);
+        $st->execute($input_parameters);
+        $data = $st->fetchGroupedPairs();
+        if (is_callable($callable)) {
+            foreach ($data as $key => $row) {
+                $data[$key] = call_user_func($callable, $row, $key);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * Executes sql statement with given parameters, and fetch results
+     * as associative array, first columns value is used as a key, the other one as the value
+     * use only when selecting 2 columns
+     * optionally apply given callable on each grouped row, with current row and key as parameter
+     *
+     * @see StudipPDOStatement::fetchGroupedPairs()
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @param callable $callable callable to be applied to each of the rows
+     * @return array result set
+     */
+    public function fetchPairs($statement, $input_parameters = null, $callable = null)
+    {
+        $st = $this->prepare($statement);
+        $st->execute($input_parameters);
+        $data = $st->fetchPairs();
+        if (is_callable($callable)) {
+            foreach ($data as $key => $row) {
+                $data[$key] = call_user_func($callable, $row, $key);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * Executes sql statement with given parameters, and fetch only the first row
+     * as associative array
+     *
+     * @see StudipPDOStatement::fetchOne()
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @return array first row of result set
+     */
+    public function fetchOne($statement, $input_parameters = null)
+    {
+        $st = $this->prepare($statement);
+        $st->execute($input_parameters);
+        return $st->fetchOne();
+    }
+
+    /**
+     * Executes sql statement with given parameters, and fetch only the value of one column
+     * third param denotes the column, zero indexed
+     *
+     * @param string $statement SQL statement to execute
+     * @param array $input_parameters parameters for statement
+     * @param integer $column number of column to fetch
+     * @return string value of chosen column
+     */
+    public function fetchColumn($statement, $input_parameters = null, $column = 0)
+    {
+        $st = $this->prepare($statement);
+        $st->execute($input_parameters);
+        return $st->fetchColumn($column);
+    }
+
+    /**
+     * Determine if the connected database is a MariaDB database.
+     *
+     * @return bool
+     */
+    public function isMariaDB(): bool
+    {
+        return stripos($this->getAttribute(\PDO::ATTR_SERVER_VERSION), 'MariaDB') !== false;
+    }
+}
diff --git a/lib/classes/StudipRangeTree.class.php b/lib/classes/StudipRangeTree.class.php
deleted file mode 100644
index cf88d23..0000000
--- a/lib/classes/StudipRangeTree.class.php
+++ /dev/null
@@ -1,222 +0,0 @@
-
-// Suchi & Berg GmbH 
-// +---------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +---------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +---------------------------------------------------------------------------+
-
-/**
-* class to handle the "range tree"
-*
-* This class provides an interface to the structure of the "range tree"
-*
-* @access   public
-* @author   André Noack 
-* @package
-*/
-class StudipRangeTree extends TreeAbstract
-{
-    var $sem_number;
-    var $sem_status;
-    var $sem_dates;
-    var $studip_objects = [];
-    var $visible_only;
-    var $entries_init_done = false;
-
-    /**
-    * constructor
-    *
-    * do not use directly, call TreeAbstract::GetInstance("StudipRangeTree")
-    * @access private
-    */
-    function __construct($args) {
-        DbView::addView('range_tree');
-
-        $this->root_name = Config::get()->UNI_NAME_CLEAN;
-        $this->studip_objects['inst'] = ['pk' => 'Institut_id', 'table' => 'Institute'];
-        $this->studip_objects['fak'] = ['pk' => 'Institut_id', 'table' => 'Institute'];
-        if (isset($args['sem_number']) ){
-            $this->sem_number = array_map('intval', $args['sem_number']);
-        }
-        if (isset($args['sem_status']) ){
-            $this->sem_status = array_map('intval', $args['sem_status']);
-        }
-        $this->visible_only = (int)($args['visible_only'] ?? 0);
-        parent::__construct(); //calling the baseclass constructor
-        $this->sem_dates = Semester::findAllVisible();
-    }
-
-    /**
-    * initializes the tree
-    *
-    * stores all rows from table range_tree in array $tree_data
-    * @access public
-    */
-    function init(){
-        parent::init();
-        $this->tree_data['root']['studip_object_id'] = 'root';
-        $db = $this->view->get_query("view:TREE_GET_DATA");
-        while ($db->next_record()){
-            $item_name = $db->f("name");
-            if ($db->f("studip_object")){
-                $item_name = $db->f("studip_object_name");
-            }
-            $this->tree_data[$db->f("item_id")] = ["studip_object" => $db->f("studip_object"),
-                                                    "studip_object_id" => $db->f("studip_object_id"),
-                                                    "fakultaets_id" => $db->f("fakultaets_id"),"entries" => 0];
-            $this->storeItem($db->f("item_id"), $db->f("parent_id"), $item_name, $db->f("priority"));
-        }
-    }
-
-    function initEntries(){
-        $this->view->params[0] = (isset($this->sem_status)) ? " AND d.status IN('" . join("','", $this->sem_status) . "')" : " ";
-        $this->view->params[0] .= $this->visible_only ? " AND visible=1 " : "";
-        $this->view->params[1] = (isset($this->sem_number)) ? " WHERE ((" . $this->view->sem_number_sql
-                                . ") IN (" . join(",",$this->sem_number) .") OR ((" . $this->view->sem_number_sql
-                                .") <= " . $this->sem_number[count($this->sem_number)-1]
-                                . "  AND ((" . $this->view->sem_number_end_sql . ") >= " . $this->sem_number[count($this->sem_number)-1]
-                                . " OR (" . $this->view->sem_number_end_sql  . ") = -1))) " : "";
-
-        $db = $this->view->get_query("view:TREE_GET_SEM_ENTRIES");
-        while ($db->next_record()){
-            $this->tree_data[$db->f("item_id")]['entries'] = $db->f('entries');
-        }
-        $this->entries_init_done = true;
-    }
-
-    /**
-    * Returns Stud.IP range_id of the next "real" object
-    *
-    * This function finds the next item wich is a real Stud.IP Object, either an "Einrichtung" or a "Fakultaet"
- * useful for the user rights management - * @access public - * @param string $item_id - * @return bool|array of primary keys from table "institute" - */ - function getAdminRange($item_id) { - if (empty($this->tree_data[$item_id])) { - return false; - } - - $found = false; - $ret = false; - $next_link = $item_id; - - while(($next_link = $this->getNextLink($next_link)) != 'root') { - if ($this->tree_data[$next_link]['studip_object'] == 'inst') { - $found[] = $next_link; - } - - if ($this->tree_data[$next_link]['studip_object'] == 'fak') { - if (is_array($found) && count($found)) { - foreach($found as $f) { - if ($this->tree_data[$f]['fakultaets_id'] == $this->tree_data[$next_link]['studip_object_id']) { - $ret[] = $this->tree_data[$f]['studip_object_id']; - } - } - - $ret[] = $this->tree_data[$next_link]['studip_object_id']; - } else { - $ret[] = $this->tree_data[$next_link]['studip_object_id']; - } - break; - } - $next_link = $this->tree_data[$next_link]['parent_id']; - } - - if (!$ret){ - $ret[] = $next_link; - } - - return $ret; - } - /** - * returns the next item_id upwards the tree which is a Stud.IP object - * - * help function for getAdminRange() - * - * @access private - * @param string $item_id - * @return string - */ - - function getNextLink($item_id){ - if (!$this->tree_data[$item_id]) - return false; - $ret_id = $item_id; - while (!$this->tree_data[$ret_id]['studip_object_id']){ - $ret_id = $this->tree_data[$ret_id]['parent_id']; - } - return $ret_id; - } - - function getSemIds($item_id,$ids_from_kids = false){ - if (!$this->tree_data[$item_id]) - return false; - if ($ids_from_kids){ - $this->view->params[0] = $this->getKidsKids($item_id); - } - $this->view->params[0][] = $item_id; - $this->view->params[1] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : " "; - $ret = false; - $rs = $this->view->get_query("view:RANGE_TREE_GET_SEMIDS"); - while($rs->next_record()){ - $ret[] = $rs->f(0); - } - return $ret; - } - - function getNumEntries($item_id, $num_entries_from_kids = false){ - if (!$this->tree_data[$item_id]) - return false; - if (!$this->entries_init_done) $this->initEntries(); - - return parent::getNumEntries($item_id, $num_entries_from_kids); - } - - function InsertItem($item_id, $parent_id, $item_name, $priority,$studip_object,$studip_object_id){ - $view = new DbView(); - $view->params = [$item_id,$parent_id,$item_name,$priority,$studip_object,$studip_object_id]; - $rs = $view->get_query("view:TREE_INS_ITEM"); - return $rs->affected_rows(); - } - - function UpdateItem($item_name,$studip_object,$studip_object_id,$item_id){ - $view = new DbView(); - $view->params = [$item_name,$studip_object,$studip_object_id,$item_id]; - $rs = $view->get_query("view:TREE_UPD_ITEM"); - return $rs->affected_rows(); - } - - function DeleteItems($items_to_delete){ - $view = new DbView(); - $view->params[0] = (is_array($items_to_delete)) ? $items_to_delete : [$items_to_delete]; - $view->auto_free_params = false; - $rs = $view->get_query("view:TREE_DEL_ITEM"); - $deleted['items'] = $rs->affected_rows(); - $rs = $view->get_query("view:CAT_DEL_RANGE"); - $deleted['categories'] = $rs->affected_rows(); - return $deleted; - } -} -?> diff --git a/lib/classes/StudipRangeTree.php b/lib/classes/StudipRangeTree.php new file mode 100644 index 0000000..cf88d23 --- /dev/null +++ b/lib/classes/StudipRangeTree.php @@ -0,0 +1,222 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* class to handle the "range tree" +* +* This class provides an interface to the structure of the "range tree" +* +* @access public +* @author André Noack +* @package +*/ +class StudipRangeTree extends TreeAbstract +{ + var $sem_number; + var $sem_status; + var $sem_dates; + var $studip_objects = []; + var $visible_only; + var $entries_init_done = false; + + /** + * constructor + * + * do not use directly, call TreeAbstract::GetInstance("StudipRangeTree") + * @access private + */ + function __construct($args) { + DbView::addView('range_tree'); + + $this->root_name = Config::get()->UNI_NAME_CLEAN; + $this->studip_objects['inst'] = ['pk' => 'Institut_id', 'table' => 'Institute']; + $this->studip_objects['fak'] = ['pk' => 'Institut_id', 'table' => 'Institute']; + if (isset($args['sem_number']) ){ + $this->sem_number = array_map('intval', $args['sem_number']); + } + if (isset($args['sem_status']) ){ + $this->sem_status = array_map('intval', $args['sem_status']); + } + $this->visible_only = (int)($args['visible_only'] ?? 0); + parent::__construct(); //calling the baseclass constructor + $this->sem_dates = Semester::findAllVisible(); + } + + /** + * initializes the tree + * + * stores all rows from table range_tree in array $tree_data + * @access public + */ + function init(){ + parent::init(); + $this->tree_data['root']['studip_object_id'] = 'root'; + $db = $this->view->get_query("view:TREE_GET_DATA"); + while ($db->next_record()){ + $item_name = $db->f("name"); + if ($db->f("studip_object")){ + $item_name = $db->f("studip_object_name"); + } + $this->tree_data[$db->f("item_id")] = ["studip_object" => $db->f("studip_object"), + "studip_object_id" => $db->f("studip_object_id"), + "fakultaets_id" => $db->f("fakultaets_id"),"entries" => 0]; + $this->storeItem($db->f("item_id"), $db->f("parent_id"), $item_name, $db->f("priority")); + } + } + + function initEntries(){ + $this->view->params[0] = (isset($this->sem_status)) ? " AND d.status IN('" . join("','", $this->sem_status) . "')" : " "; + $this->view->params[0] .= $this->visible_only ? " AND visible=1 " : ""; + $this->view->params[1] = (isset($this->sem_number)) ? " WHERE ((" . $this->view->sem_number_sql + . ") IN (" . join(",",$this->sem_number) .") OR ((" . $this->view->sem_number_sql + .") <= " . $this->sem_number[count($this->sem_number)-1] + . " AND ((" . $this->view->sem_number_end_sql . ") >= " . $this->sem_number[count($this->sem_number)-1] + . " OR (" . $this->view->sem_number_end_sql . ") = -1))) " : ""; + + $db = $this->view->get_query("view:TREE_GET_SEM_ENTRIES"); + while ($db->next_record()){ + $this->tree_data[$db->f("item_id")]['entries'] = $db->f('entries'); + } + $this->entries_init_done = true; + } + + /** + * Returns Stud.IP range_id of the next "real" object + * + * This function finds the next item wich is a real Stud.IP Object, either an "Einrichtung" or a "Fakultaet"
+ * useful for the user rights management + * @access public + * @param string $item_id + * @return bool|array of primary keys from table "institute" + */ + function getAdminRange($item_id) { + if (empty($this->tree_data[$item_id])) { + return false; + } + + $found = false; + $ret = false; + $next_link = $item_id; + + while(($next_link = $this->getNextLink($next_link)) != 'root') { + if ($this->tree_data[$next_link]['studip_object'] == 'inst') { + $found[] = $next_link; + } + + if ($this->tree_data[$next_link]['studip_object'] == 'fak') { + if (is_array($found) && count($found)) { + foreach($found as $f) { + if ($this->tree_data[$f]['fakultaets_id'] == $this->tree_data[$next_link]['studip_object_id']) { + $ret[] = $this->tree_data[$f]['studip_object_id']; + } + } + + $ret[] = $this->tree_data[$next_link]['studip_object_id']; + } else { + $ret[] = $this->tree_data[$next_link]['studip_object_id']; + } + break; + } + $next_link = $this->tree_data[$next_link]['parent_id']; + } + + if (!$ret){ + $ret[] = $next_link; + } + + return $ret; + } + /** + * returns the next item_id upwards the tree which is a Stud.IP object + * + * help function for getAdminRange() + * + * @access private + * @param string $item_id + * @return string + */ + + function getNextLink($item_id){ + if (!$this->tree_data[$item_id]) + return false; + $ret_id = $item_id; + while (!$this->tree_data[$ret_id]['studip_object_id']){ + $ret_id = $this->tree_data[$ret_id]['parent_id']; + } + return $ret_id; + } + + function getSemIds($item_id,$ids_from_kids = false){ + if (!$this->tree_data[$item_id]) + return false; + if ($ids_from_kids){ + $this->view->params[0] = $this->getKidsKids($item_id); + } + $this->view->params[0][] = $item_id; + $this->view->params[1] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : " "; + $ret = false; + $rs = $this->view->get_query("view:RANGE_TREE_GET_SEMIDS"); + while($rs->next_record()){ + $ret[] = $rs->f(0); + } + return $ret; + } + + function getNumEntries($item_id, $num_entries_from_kids = false){ + if (!$this->tree_data[$item_id]) + return false; + if (!$this->entries_init_done) $this->initEntries(); + + return parent::getNumEntries($item_id, $num_entries_from_kids); + } + + function InsertItem($item_id, $parent_id, $item_name, $priority,$studip_object,$studip_object_id){ + $view = new DbView(); + $view->params = [$item_id,$parent_id,$item_name,$priority,$studip_object,$studip_object_id]; + $rs = $view->get_query("view:TREE_INS_ITEM"); + return $rs->affected_rows(); + } + + function UpdateItem($item_name,$studip_object,$studip_object_id,$item_id){ + $view = new DbView(); + $view->params = [$item_name,$studip_object,$studip_object_id,$item_id]; + $rs = $view->get_query("view:TREE_UPD_ITEM"); + return $rs->affected_rows(); + } + + function DeleteItems($items_to_delete){ + $view = new DbView(); + $view->params[0] = (is_array($items_to_delete)) ? $items_to_delete : [$items_to_delete]; + $view->auto_free_params = false; + $rs = $view->get_query("view:TREE_DEL_ITEM"); + $deleted['items'] = $rs->affected_rows(); + $rs = $view->get_query("view:CAT_DEL_RANGE"); + $deleted['categories'] = $rs->affected_rows(); + return $deleted; + } +} +?> diff --git a/lib/classes/StudipRangeTreeView.class.php b/lib/classes/StudipRangeTreeView.class.php deleted file mode 100644 index 2532e4b..0000000 --- a/lib/classes/StudipRangeTreeView.class.php +++ /dev/null @@ -1,101 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** -* class to print out the "range tree" -* -* This class prints out a html representation of the whole or part of the tree -* -* @access public -* @author André Noack -* @package -*/ -class StudipRangeTreeView extends TreeView{ - - /** - * constructor - * - * @access public - */ - function __construct(){ - $this->root_content = $GLOBALS['UNI_INFO']; - parent::__construct("StudipRangeTree"); //calling the baseclass constructor - } - - function getItemContent($item_id){ - $content = "\n"; - if ($item_id == "root"){ - $content .= "\n"; - $content .= "\n"; - $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; - return $content; - } - $range_object = RangeTreeObject::GetInstance($item_id); - $name = ($range_object->item_data['type']) ? $range_object->item_data['type'] . ": " : ""; - $name .= $range_object->item_data['name']; - $content .= "\n" . htmlReady($name) ." "; - if (is_array($range_object->item_data_mapping)){ - $content .= "\n"; - foreach ($range_object->item_data_mapping as $key => $value){ - if ($range_object->item_data[$key]){ - $content .= "" . htmlReady($value) . ": "; - $content .= formatLinks($range_object->item_data[$key]) . "  "; - } - } - $content .= "" . - "item_data['studip_object_id'])."\"" . - tooltip(_("Seite dieser Einrichtung in Stud.IP aufrufen")) . ">" . - htmlReady($range_object->item_data['name']) . " " ._("in Stud.IP") .""; - - } elseif (!$range_object->item_data['studip_object']){ - $content .= "\n" . - _("Dieses Element ist keine Stud.IP-Einrichtung, es hat daher keine Grunddaten.") . ""; - } else { - $content .= "\n" . _("Keine Grunddaten vorhanden!") . ""; - } - $content .= "\n "; - $kategorien =& $range_object->getCategories(); - if ($kategorien->numRows){ - while($kategorien->nextRow()){ - $content .= "\n" . htmlReady($kategorien->getField("name")) . ""; - $content .= "\n" . formatReady($kategorien->getField("content")) . ""; - } - } else { - $content .= "\n" . _("Keine weiteren Daten vorhanden!") . ""; - } - $content .= ""; - return $content; - } -} -//test -//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); -//include 'lib/include/html_head.inc.php'; -//$test = new StudipRangeTreeView(); -//$test->showTree(); -//echo ""; -//page_close(); diff --git a/lib/classes/StudipRangeTreeView.php b/lib/classes/StudipRangeTreeView.php new file mode 100644 index 0000000..2532e4b --- /dev/null +++ b/lib/classes/StudipRangeTreeView.php @@ -0,0 +1,101 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* class to print out the "range tree" +* +* This class prints out a html representation of the whole or part of the tree +* +* @access public +* @author André Noack +* @package +*/ +class StudipRangeTreeView extends TreeView{ + + /** + * constructor + * + * @access public + */ + function __construct(){ + $this->root_content = $GLOBALS['UNI_INFO']; + parent::__construct("StudipRangeTree"); //calling the baseclass constructor + } + + function getItemContent($item_id){ + $content = "\n"; + if ($item_id == "root"){ + $content .= "\n"; + $content .= "\n"; + $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; + return $content; + } + $range_object = RangeTreeObject::GetInstance($item_id); + $name = ($range_object->item_data['type']) ? $range_object->item_data['type'] . ": " : ""; + $name .= $range_object->item_data['name']; + $content .= "\n" . htmlReady($name) ." "; + if (is_array($range_object->item_data_mapping)){ + $content .= "\n"; + foreach ($range_object->item_data_mapping as $key => $value){ + if ($range_object->item_data[$key]){ + $content .= "" . htmlReady($value) . ": "; + $content .= formatLinks($range_object->item_data[$key]) . "  "; + } + } + $content .= "" . + "item_data['studip_object_id'])."\"" . + tooltip(_("Seite dieser Einrichtung in Stud.IP aufrufen")) . ">" . + htmlReady($range_object->item_data['name']) . " " ._("in Stud.IP") .""; + + } elseif (!$range_object->item_data['studip_object']){ + $content .= "\n" . + _("Dieses Element ist keine Stud.IP-Einrichtung, es hat daher keine Grunddaten.") . ""; + } else { + $content .= "\n" . _("Keine Grunddaten vorhanden!") . ""; + } + $content .= "\n "; + $kategorien =& $range_object->getCategories(); + if ($kategorien->numRows){ + while($kategorien->nextRow()){ + $content .= "\n" . htmlReady($kategorien->getField("name")) . ""; + $content .= "\n" . formatReady($kategorien->getField("content")) . ""; + } + } else { + $content .= "\n" . _("Keine weiteren Daten vorhanden!") . ""; + } + $content .= ""; + return $content; + } +} +//test +//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); +//include 'lib/include/html_head.inc.php'; +//$test = new StudipRangeTreeView(); +//$test->showTree(); +//echo ""; +//page_close(); diff --git a/lib/classes/StudipRangeTreeViewAdmin.class.php b/lib/classes/StudipRangeTreeViewAdmin.class.php deleted file mode 100644 index 0620957..0000000 --- a/lib/classes/StudipRangeTreeViewAdmin.class.php +++ /dev/null @@ -1,837 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -use Studip\Button, Studip\LinkButton; - -/** -* class to print out the admin view of the "range tree" -* -* This class prints out a html representation of the whole or part of the tree,
-* it also contains all functions for administrative tasks on the tree -* -* @access public -* @author André Noack -* @package -*/ -class StudipRangeTreeViewAdmin extends TreeView{ - - - var $tree_status; - - var $mode; - - var $edit_item_id; - - var $move_item_id; - - var $search_result; - - var $msg; - - var $marked_item; - - var $edit_cat_snap; - - var $edit_cat_item_id; - - /** - * constructor - * - * calls the base class constructor, registers a session variable, calls the init function and the command parser - * @access public - */ - function __construct(){ - - $this->root_content = $GLOBALS['UNI_INFO']; - parent::__construct("StudipRangeTree"); //calling the baseclass constructor - $this->marked_item =& $_SESSION['_marked_item']; - - $this->initTreeStatus(); - $this->parseCommand(); - } - - function initTreeStatus() - { - $view = DbView::getView('range_tree'); - $user_id = $GLOBALS['auth']->auth['uid']; - $user_perm = $GLOBALS['auth']->auth['perm']; - $studip_object_status = null; - - if (is_array($this->open_items)){ - foreach ($this->open_items as $key => $value) { - if ($key != 'root') { - $tmp = $this->tree->getAdminRange($key); - - if(!empty($tmp)) { - foreach($tmp as $i) { - if($i) { - $studip_object_status[$i] = ($user_perm == "root") ? 1 : -1; - } - } - - } - } - } - } - - if (is_array($this->open_ranges)){ - foreach ($this->open_ranges as $key => $value) { - if ($key != 'root'){ - $tmp = $this->tree->getAdminRange($key); - - if(!empty($tmp)) { - foreach($tmp as $i) { - if($i) { - $studip_object_status[$i] = ($user_perm == "root") ? 1 : -1; - } - } - } - - if (isset($this->tree->tree_data[$key])) { - $tmp = $this->tree->getAdminRange($this->tree->tree_data[$key]['parent_id']); - } else { - $tmp = []; - } - - if(!empty($tmp)) { - foreach($tmp as $i) { - if ($i) { - $studip_object_status[$i] = ($user_perm == "root") ? 1 : -1; - } - } - } - - } - } - } - if (is_array($studip_object_status) && $user_perm != 'root'){ - $view->params = [array_keys($studip_object_status), $user_id]; - $rs = $view->get_query("view:TREE_INST_STATUS"); - while ($rs->next_record()){ - $studip_object_status[$rs->f("Institut_id")] = 1; - } - $view->params = [array_keys($studip_object_status), $user_id]; - $rs = $view->get_query("view:TREE_FAK_STATUS"); - while ($rs->next_record()){ - $studip_object_status[$rs->f("Fakultaets_id")] = 1; - if ($rs->f("Institut_id") && isset($studip_object_status[$rs->f("Institut_id")])){ - $studip_object_status[$rs->f("Institut_id")] = 1; - } - } - } - $studip_object_status['root'] = ($user_perm == "root") ? 1 : -1; - $this->tree_status = $studip_object_status; - } - - function parseCommand(){ - if (Request::quoted('mode')) - $this->mode = Request::quoted('mode'); - if (Request::option('cmd')){ - $exec_func = "execCommand" . Request::option('cmd'); - if (method_exists($this,$exec_func)){ - if ($this->$exec_func()){ - $this->tree->init(); - $this->initTreeStatus(); - } - } - } - if ($this->mode == "MoveItem") - $this->move_item_id = $this->marked_item; - } - - function execCommandOrderItem(){ - $direction = Request::quoted('direction'); - $item_id = Request::option('item_id'); - $items_to_order = $this->tree->getKids($this->tree->tree_data[$item_id]['parent_id']); - if (!$this->isParentAdmin($item_id) || !$items_to_order) - return false; - for ($i = 0; $i < count($items_to_order); ++$i){ - if ($item_id == $items_to_order[$i]) - break; - } - if ($direction == "up" && isset($items_to_order[$i-1])){ - $items_to_order[$i] = $items_to_order[$i-1]; - $items_to_order[$i-1] = $item_id; - } elseif (isset($items_to_order[$i+1])){ - $items_to_order[$i] = $items_to_order[$i+1]; - $items_to_order[$i+1] = $item_id; - } - $view = DbView::getView('range_tree'); - for ($i = 0; $i < count($items_to_order); ++$i){ - $view->params = [$i, $items_to_order[$i]]; - $rs = $view->get_query("view:TREE_UPD_PRIO"); - } - $this->mode = ""; - $this->msg[$item_id] = "msg§" . (($direction == "up") ? _("Element wurde um eine Position nach oben verschoben.") : _("Element wurde um eine Position nach unten verschoben.")); - return true; - } - - function execCommandNewItem(){ - $item_id = Request::option('item_id'); - if ($this->isItemAdmin($item_id)){ - $new_item_id = DbView::get_uniqid(); - $this->tree->storeItem($new_item_id,$item_id,_("Neues Element"), $this->tree->getNumKids($item_id) +1); - $this->anchor = $new_item_id; - $this->edit_item_id = $new_item_id; - $this->open_ranges[$item_id] = true; - $this->open_items[$new_item_id] = true; - if ($this->mode != "NewItem") - $this->msg[$new_item_id] = "info§" . _("Wählen Sie einen Namen für dieses Element, oder verlinken Sie es mit einer Einrichtung in Stud.IP"); - $this->mode = "NewItem"; - } - return false; - } - - function execCommandSearchStudip(){ - $item_id = Request::option('item_id'); - $parent_id = Request::quoted('parent_id'); - $search_str = Request::quoted('edit_search'); - $view = DbView::getView('range_tree'); - if(mb_strlen($search_str) > 1){ - $view->params[0] = $search_str; - $rs = $view->get_query("view:TREE_SEARCH_INST"); - while ($rs->next_record()){ - $this->search_result[$rs->f("Institut_id")]['name'] = $rs->f("Name"); - $this->search_result[$rs->f("Institut_id")]['studip_object'] = "inst"; - } - if ($parent_id == "root"){ - $view->params[0] = $search_str; - $rs = $view->get_query("view:TREE_SEARCH_FAK"); - while ($rs->next_record()){ - $this->search_result[$rs->f("Fakultaets_id")]['name'] = $rs->f("Name"); - $this->search_result[$rs->f("Fakultaets_id")]['studip_object'] = "fak"; - } - } - $search_msg = "info§" . sprintf(_("Ihre Suche ergab %s Treffer."),count($this->search_result)); - } else { - $search_msg = "error§" . _("Sie haben keinen Suchbegriff eingegeben."); - } - if ($this->mode == "NewItem"){ - Request::set('item_id', $parent_id); - $this->execCommandNewItem(); - } else { - $this->anchor = $item_id; - $this->edit_item_id = $item_id; - } - $this->msg[$this->edit_item_id] = $search_msg; - return false; - } - - function execCommandEditItem(){ - $item_id = Request::option('item_id'); - if ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)){ - $this->mode = "EditItem"; - $this->anchor = $item_id; - $this->edit_item_id = $item_id; - $this->msg[$item_id] = "info§" . _("Wählen Sie einen Namen für dieses Element, oder verlinken Sie es mit einer Einrichtung in Stud.IP"); - } - return false; - } - - function execCommandInsertItem(){ - $item_id = Request::option('item_id'); - $parent_id = Request::option('parent_id'); - $item_name = Request::quoted('edit_name'); - $tmp = explode(":",Request::quoted('edit_studip_object')); - if ($tmp[1] == "fak" || $tmp[1] == "inst"){ - $studip_object = $tmp[1]; - $studip_object_id = $tmp[0]; - } else { - $studip_object = ""; - $studip_object_id = ""; - } - if ($this->mode == "NewItem" && $item_id){ - if ($this->isItemAdmin($parent_id)){ - $priority = count($this->tree->getKids($parent_id)); - $affected_rows = $this->tree->InsertItem($item_id,$parent_id,$item_name,$priority,$studip_object,$studip_object_id); - if ($affected_rows){ - $this->mode = ""; - $this->anchor = $item_id; - $this->open_items[$item_id] = true; - $this->msg[$item_id] = "msg§" . _("Dieses Element wurde neu eingefügt."); - } - } - } - if ($this->mode == "EditItem"){ - if ($this->isParentAdmin($item_id)){ - $affected_rows = $this->tree->UpdateItem($item_name,$studip_object,$studip_object_id,$item_id); - if ($affected_rows){ - $this->msg[$item_id] = "msg§" . _("Element wurde geändert."); - } else { - $this->msg[$item_id] = "info§" . _("Keine Veränderungen vorgenommen."); - } - $this->mode = ""; - $this->anchor = $item_id; - $this->open_items[$item_id] = true; - } - } - return true; - } - - function execCommandInsertFak(){ - $studip_object_id = Request::quoted('insert_fak'); - $parent_id = 'root'; - if ($this->isItemAdmin($parent_id)){ - $item_id = DbView::get_uniqid(); - $priority = count($this->tree->getKids($parent_id)); - $affected_rows = $this->tree->InsertItem($item_id,$parent_id,'',$priority,'fak',$studip_object_id); - if($affected_rows){ - $view = DbView::getView('range_tree'); - $priority = 0; - $rs = $view->get_query("SELECT * FROM Institute WHERE fakultaets_id <> Institut_id AND fakultaets_id = '$studip_object_id' ORDER BY Name"); - while($rs->next_record()){ - $affected_rows += $this->tree->InsertItem(DbView::get_uniqid(),$item_id,addslashes($rs->f('name')),$priority++,'inst',$rs->f('Institut_id')); - } - $this->msg[$item_id] = "msg§" . sprintf(_("%s Elemente wurden eingefügt."), $affected_rows); - $this->mode = ""; - $this->anchor = $item_id; - $this->open_items[$item_id] = true; - $this->open_ranges[$item_id] = true; - } - } - return true; - } - - function execCommandAssertDeleteItem(){ - $item_id = Request::option('item_id'); - if ($this->isParentAdmin($item_id)){ - $this->mode = "AssertDeleteItem"; - $this->msg[$item_id] = "info§" ._("Sie beabsichtigen dieses Element, inklusive aller Unterelemente, zu löschen. ") - . sprintf(_("Es werden insgesamt %s Elemente gelöscht!"),count($this->tree->getKidsKids($item_id))+1) - . "
" . _("Wollen Sie diese Elemente wirklich löschen?") . "
" - . '
' - . LinkButton::createAccept(_("JA"), URLHelper::getURL($this->getSelf("cmd=DeleteItem&item_id=$item_id"))) - . LinkButton::createCancel(_("NEIN"), URLHelper::getURL($this->getSelf("cmd=Cancel&item_id=$item_id"))) - . "
"; - } - return false; - } - - function execCommandDeleteItem(){ - $item_id = Request::option('item_id'); - $deleted = 0; - $item_name = $this->tree->tree_data[$item_id]['name']; - if ($this->isParentAdmin($item_id) && $this->mode == "AssertDeleteItem"){ - $this->anchor = $this->tree->tree_data[$item_id]['parent_id']; - $items_to_delete = $this->tree->getKidsKids($item_id); - $items_to_delete[] = $item_id; - $deleted = $this->tree->DeleteItems($items_to_delete); - if ($deleted['items']){ - $this->msg[$this->anchor] = "msg§" . sprintf(_("Das Element %s und alle Unterelemente (insgesamt %s) wurden gelöscht. "),htmlReady($item_name),$deleted['items']); - } else { - $this->msg[$this->anchor] = "error§" . _("Fehler, es konnten keine Elemente gelöscht werden!"); - } - if ($deleted['categories']){ - $this->msg[$this->anchor] .= sprintf(_("
Es wurden %s Datenfelder gelöscht. "),$deleted['categories']); - } - $this->mode = ""; - $this->open_items[$this->anchor] = true; - } - return true; - } - - function execCommandMoveItem(){ - $item_id = Request::option('item_id'); - $this->anchor = $item_id; - $this->marked_item = $item_id; - $this->mode = "MoveItem"; - return false; - } - - function execCommandDoMoveItem(){ - $item_id = Request::option('item_id'); - $item_to_move = $this->marked_item; - if ($this->mode == "MoveItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) - && ($item_to_move != $item_id) && ($this->tree->tree_data[$item_to_move]['parent_id'] != $item_id) - && !$this->tree->isChildOf($item_to_move,$item_id)){ - $view = DbView::getView('range_tree'); - $view->params = [$item_id, count($this->tree->getKids($item_id)), $item_to_move]; - $rs = $view->get_query("view:TREE_MOVE_ITEM"); - if ($rs->affected_rows()){ - $this->msg[$item_to_move] = "msg§" . _("Element wurde verschoben."); - } else { - $this->msg[$item_to_move] = "error§" . _("Keine Verschiebung durchgeführt."); - } - } - $this->anchor = $item_to_move; - $this->open_ranges[$item_id] = true; - $this->open_items[$item_to_move] = true; - $this->mode = ""; - return true; - } - - function execCommandOrderCat(){ - $item_id = Request::option('item_id'); - $direction = Request::quoted('direction'); - $cat_id = Request::option('cat_id'); - $items_to_order = []; - if ($this->isItemAdmin($item_id)){ - $range_object = RangeTreeObject::GetInstance($item_id); - $categories =& $range_object->getCategories(); - while($categories->nextRow()){ - $items_to_order[] = $categories->getField("kategorie_id"); - } - for ($i = 0; $i < count($items_to_order); ++$i){ - if ($cat_id == $items_to_order[$i]) - break; - } - if ($direction == "up" && isset($items_to_order[$i-1])){ - $items_to_order[$i] = $items_to_order[$i-1]; - $items_to_order[$i-1] = $cat_id; - } elseif (isset($items_to_order[$i+1])){ - $items_to_order[$i] = $items_to_order[$i+1]; - $items_to_order[$i+1] = $cat_id; - } - $view = DbView::getView('range_tree'); - for ($i = 0; $i < count($items_to_order); ++$i){ - $view->params = [$i,$items_to_order[$i]]; - $rs = $view->get_query("view:CAT_UPD_PRIO"); - } - $this->msg[$item_id] = "msg§" . _("Datenfelder wurden neu geordnet"); - } - $this->anchor = $item_id; - return false; - } - - function execCommandNewCat(){ - $item_id = Request::option('item_id'); - if ($this->isItemAdmin($item_id)){ - $range_object = RangeTreeObject::GetInstance($item_id); - $this->edit_cat_snap =& $range_object->getCategories(); - $this->edit_cat_snap->result[$this->edit_cat_snap->numRows] = - ["kategorie_id" => "new_entry", "range_id" => $item_id, "name" => "Neues Datenfeld", "content" => "Neues Datenfeld", - "priority" => $this->edit_cat_snap->numRows]; - ++$this->edit_cat_snap->numRows; - $this->edit_cat_item_id = $item_id; - $this->mode = "NewCat"; - } - $this->anchor = $item_id; - return false; - } - - function execCommandUpdateCat(){ - $item_id = Request::option('item_id'); - $cat_name = Request::getArray('cat_name'); - $cat_content = Request::getArray('cat_content'); - $cat_prio = Request::getArray('cat_prio'); - $inserted = false; - $updated = 0; - if ($this->isItemAdmin($item_id)){ - $view = DbView::getView('range_tree'); - if (isset($cat_name['new_entry'])){ - $view->params = [DbView::get_uniqid(),$item_id,$cat_name['new_entry'],$cat_content['new_entry'],$cat_prio['new_entry']]; - $rs = $view->get_query("view:CAT_INS_ALL"); - if ($rs->affected_rows()){ - $inserted = true; - } - unset($cat_name['new_entry']); - } - foreach ($cat_name as $key => $value){ - $view->params = [$value,$cat_content[$key],$key]; - $rs = $view->get_query("view:CAT_UPD_CONTENT"); - if ($rs->affected_rows()){ - ++$updated; - } - } - if ($updated){ - $this->msg[$item_id] = "msg§" . sprintf(_("Es wurden %s Datenfelder aktualisiert."),$updated); - if ($inserted) { - $this->msg[$item_id] .= _("Ein neues Datenfeld wurde eingefügt."); - } - } elseif ($inserted){ - $this->msg[$item_id] = "msg§" . _("Ein neues Datenfeld wurde eingefügt."); - } else { - $this->msg[$item_id] = "info§" . _("Keine Veränderungen vorgenommen."); - } - } - $this->anchor = $item_id; - $this->mode = ""; - return false; - } - - function execCommandDeleteCat(){ - $item_id = Request::option('item_id'); - $cat_id = Request::option('cat_id'); - if ($this->isItemAdmin($item_id)){ - $view = DbView::getView('range_tree'); - $view->params[0] = $cat_id; - $rs = $view->get_query("view:CAT_DEL"); - if ($rs->affected_rows()){ - $this->msg[$item_id] = "msg§" . _("Ein Datenfeld wurde gelöscht."); - } - } - $this->mode = ""; - $this->anchor = $item_id; - return false; - } - - - function execCommandCancel(){ - $item_id = Request::option('item_id'); - $this->mode = ""; - $this->anchor = $item_id; - return false; - } - - function isItemAdmin($item_id){ - $admin_ranges = $this->tree->getAdminRange($item_id); - for ($i = 0; $i < count($admin_ranges); ++$i){ - if ( - isset($this->tree_status[$admin_ranges[$i]]) - && $this->tree_status[$admin_ranges[$i]] == 1 - ) { - return true; - } - } - return false; - } - - function isParentAdmin($item_id){ - $admin_ranges = $this->tree->getAdminRange($this->tree->tree_data[$item_id]['parent_id']); - if (!empty($admin_ranges)) { - for ($i = 0; $i < count($admin_ranges); ++$i) { - if ( - isset($this->tree_status[$admin_ranges[$i]]) - && $this->tree_status[$admin_ranges[$i]] == 1 - ) { - return true; - } - } - } - return false; - } - - function getItemContent($item_id){ - - if ($item_id == $this->edit_item_id ) - return $this->getEditItemContent(); - if ($item_id == $this->move_item_id){ - $this->msg[$item_id] = "info§" . sprintf(_("Dieses Element wurde zum Verschieben markiert. Bitte wählen Sie ein Einfügesymbol %s aus, um das Element zu verschieben."), Icon::create('arr_2right', 'sort', ['title' => "Einfügesymbol"])->asImg(16, ["alt" => "Einfügesymbol"])); - } - $content = "\n"; - $content .= $this->getItemMessage($item_id); - $content .= "\n
"; - - if ($this->isItemAdmin($item_id)){ - $content .= LinkButton::create(_("Neues Objekt"), - URLHelper::getURL($this->getSelf("cmd=NewItem&item_id=$item_id")), - ['title' => _("Innerhalb dieser Ebene ein neues Element einfügen")]); - } - - if ($this->isParentAdmin($item_id) && $item_id !=$this->start_item_id && $item_id != "root"){ - $content .= LinkButton::create(_("Bearbeiten"), - URLHelper::getURL($this->getSelf("cmd=EditItem&item_id=$item_id"))); - - $content .= LinkButton::create(_("Löschen"), - URLHelper::getURL($this->getSelf("cmd=AssertDeleteItem&item_id=$item_id"))); - - if ($this->move_item_id == $item_id && $this->mode == "MoveItem"){ - $content .= LinkButton::create(_("Abbrechen"), - URLHelper::getURL($this->getSelf("cmd=Cancel&item_id=$item_id"))); - } else { - $content .= LinkButton::create(_("Verschieben"), - URLHelper::getURL($this->getSelf("cmd=MoveItem&item_id=$item_id"))); - } - } - - $content .= "
"; - $content .= "\n"; - if ($item_id == "root"){ - if ($this->isItemAdmin($item_id)){ - $view = DbView::getView('range_tree'); - $rs = $view->get_query("SELECT i1.Name,i1.Institut_id,COUNT(i2.Institut_id) as num FROM Institute i1 LEFT JOIN Institute i2 ON i1.Institut_id = i2.fakultaets_id AND i2.fakultaets_id<>i2.Institut_id WHERE i1.fakultaets_id=i1.Institut_id GROUP BY i1.Institut_id ORDER BY Name"); - $content .= "\n"; - } - $content .= "\n"; - $content .= "\n"; - $content .= "\n
"; - $content .= "\n
getSelf("cmd=InsertFak")) . "\" method=\"post\" class=\"default\">" - . CSRFProtection::tokenTag() - . '
' - . Button::create(_("Fakultät einfügen")) - ."
"; - $content .= "
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; - return $content; - } - $range_object = RangeTreeObject::GetInstance($item_id); - $name = !empty($range_object->item_data['type']) ? $range_object->item_data['type'] . ": " : ""; - $name .= $range_object->item_data['name']; - $content .= "\n" . htmlReady($name) ." "; - if (is_array($range_object->item_data_mapping)){ - $content .= "\n"; - foreach ($range_object->item_data_mapping as $key => $value){ - if ($range_object->item_data[$key]){ - $content .= "" . htmlReady($value) . ": "; - $content .= formatLinks($range_object->item_data[$key]) . "  "; - } - } - $content .= " "; - } elseif (!$range_object->item_data['studip_object']){ - $content .= "\n" . - _("Dieses Element ist keine Stud.IP-Einrichtung, es hat daher keine Grunddaten."); - } else { - $content .= "\n" . _("Keine Grunddaten vorhanden!"); - } - $content .= "\n
" . _("Mitarbeiter:") . " " . $range_object->getNumStaff() . "
"; - if ($this->isItemAdmin($item_id) && $range_object->item_data['studip_object']){ - $content .= "\n
"; - - $content .= LinkButton::create(_("Grunddaten in Stud.IP bearbeiten"), "dispatch.php/institute/basicdata/index?admin_inst_id=" . $range_object->item_data['studip_object_id']); - $content .= "
"; - } - - $content .= " "; - - if ($this->mode == "NewCat" && ($this->edit_cat_item_id == $item_id)){ - $categories =& $this->edit_cat_snap; - } else { - $categories =& $range_object->getCategories(); - } - if (!$this->isItemAdmin($item_id)){ - if ($categories->numRows){ - while($categories->nextRow()){ - $content .= "\n" . formatReady($categories->getField("name")) . ""; - $content .= "\n" . formatReady($categories->getField("content")) . ""; - } - } else { - $content .= "\n" . _("Keine weiteren Daten vorhanden!") . ""; - } - } else { - $content .= "" . $this->getEditCatContent($item_id,$categories) . ""; - } - $content .= ""; - return $content; - } - - function getItemHead($item_id){ - $head = ""; - if ($this->mode == "MoveItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) - && ($this->move_item_id != $item_id) && ($this->tree->tree_data[$this->move_item_id]['parent_id'] != $item_id) - && !$this->tree->isChildOf($this->move_item_id,$item_id)){ - $head .= "getSelf("cmd=DoMoveItem&item_id=$item_id")) . "\">" - . Icon::create('arr_2right', 'sort', ['title' => "An dieser Stelle einfügen"])->asImg(16, ["alt" => "An dieser Stelle einfügen"])." "; - } - $head .= parent::getItemHead($item_id); - if ($item_id != $this->start_item_id && $this->isParentAdmin($item_id) && $item_id != $this->edit_item_id){ - $head .= ""; - if (!$this->tree->isFirstKid($item_id)){ - $head .= "getSelf("cmd=OrderItem&direction=up&item_id=$item_id")) . - "\">" . Icon::create('arr_2up', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach oben verschieben")]) . " ". - ""; - } - if (!$this->tree->isLastKid($item_id)){ - $head .= "getSelf("cmd=OrderItem&direction=down&item_id=$item_id")) . - "\">" . Icon::create('arr_2down', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach unten verschieben")]) . " ". - ""; - } - $head .= " "; - } - return $head; - } - - function getEditItemContent(){ - $content = ''; - ob_start(); - ?> -
-
edit_item_id}")) ?>"> - - - - - - getItemMessage($this->edit_item_id, 2) ?> -
- -
- - - - - -
- -
- - - - - - - -
- -
- - URLHelper::getURL($this->getSelf("cmd=SearchStudIP&item_id={$this->edit_item_id}")) - ]); ?> - - getSelf("cmd=Cancel&item_id=" . ($this->mode == "NewItem" ? $this->tree->tree_data[$this->edit_item_id]['parent_id'] : $this->edit_item_id)))) ?> -
-
-
- - -
"> - - - numRows) while ($cat_snap->nextRow()) : ?> -
- - - pos && $cat_snap->getField("kategorie_id") != "new_entry") : ?> - getField("kategorie_id"))) ?>"> - asImg(['class' => 'text-top', 'title' => _("Datenfeld nach oben")]) ?> - - - - pos != $cat_snap->numRows-1 && $cat_snap->getField("kategorie_id") != "new_entry") : ?> - getField("kategorie_id"))) ?>"> - asImg(['class' => 'text-top', 'title' => _("Datenfeld nach unten")]) ?> - - - - - - getField("kategorie_id"))) ?>"> - asImg(['class' => 'text-top', 'title' => _("Datenfeld löschen")]) ?> - - - - - - -
- - -
- numRows) : ?> - - - getSelf("cmd=NewCat&item_id=$item_id"))) ?> -
-
- - '; - - return $content; - } - - function getItemMessage($item_id, $colspan = 1) - { - $content = ""; - if (!empty($this->msg[$item_id])) { - $msg = explode("§", $this->msg[$item_id]); - $pics = [ - 'error' => Icon::create('decline', 'attention'), - 'info' => Icon::create('exclaim', 'inactive'), - 'msg' => Icon::create('accept', 'accept')]; - $content = "\n - - -
" . $pics[$msg[0]]->asImg(['class' => 'text-top']) . "" . $msg[1] . "
"; - } - return $content; - } - - function getSelf($param = ''){ - $url_params = "foo=" . DbView::get_uniqid(); - if ($this->mode) $url_params .= "&mode=" . $this->mode; - if ($param) $url_params .= '&' . $param; - return parent::getSelf($url_params); - } -} - -//test -//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); -//include 'lib/include/html_head.inc.php'; -//$test = new StudipRangeTreeViewAdmin(); -//$test->showTree(); -//echo ""; -//page_close(); -?> diff --git a/lib/classes/StudipRangeTreeViewAdmin.php b/lib/classes/StudipRangeTreeViewAdmin.php new file mode 100644 index 0000000..0620957 --- /dev/null +++ b/lib/classes/StudipRangeTreeViewAdmin.php @@ -0,0 +1,837 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +use Studip\Button, Studip\LinkButton; + +/** +* class to print out the admin view of the "range tree" +* +* This class prints out a html representation of the whole or part of the tree,
+* it also contains all functions for administrative tasks on the tree +* +* @access public +* @author André Noack +* @package +*/ +class StudipRangeTreeViewAdmin extends TreeView{ + + + var $tree_status; + + var $mode; + + var $edit_item_id; + + var $move_item_id; + + var $search_result; + + var $msg; + + var $marked_item; + + var $edit_cat_snap; + + var $edit_cat_item_id; + + /** + * constructor + * + * calls the base class constructor, registers a session variable, calls the init function and the command parser + * @access public + */ + function __construct(){ + + $this->root_content = $GLOBALS['UNI_INFO']; + parent::__construct("StudipRangeTree"); //calling the baseclass constructor + $this->marked_item =& $_SESSION['_marked_item']; + + $this->initTreeStatus(); + $this->parseCommand(); + } + + function initTreeStatus() + { + $view = DbView::getView('range_tree'); + $user_id = $GLOBALS['auth']->auth['uid']; + $user_perm = $GLOBALS['auth']->auth['perm']; + $studip_object_status = null; + + if (is_array($this->open_items)){ + foreach ($this->open_items as $key => $value) { + if ($key != 'root') { + $tmp = $this->tree->getAdminRange($key); + + if(!empty($tmp)) { + foreach($tmp as $i) { + if($i) { + $studip_object_status[$i] = ($user_perm == "root") ? 1 : -1; + } + } + + } + } + } + } + + if (is_array($this->open_ranges)){ + foreach ($this->open_ranges as $key => $value) { + if ($key != 'root'){ + $tmp = $this->tree->getAdminRange($key); + + if(!empty($tmp)) { + foreach($tmp as $i) { + if($i) { + $studip_object_status[$i] = ($user_perm == "root") ? 1 : -1; + } + } + } + + if (isset($this->tree->tree_data[$key])) { + $tmp = $this->tree->getAdminRange($this->tree->tree_data[$key]['parent_id']); + } else { + $tmp = []; + } + + if(!empty($tmp)) { + foreach($tmp as $i) { + if ($i) { + $studip_object_status[$i] = ($user_perm == "root") ? 1 : -1; + } + } + } + + } + } + } + if (is_array($studip_object_status) && $user_perm != 'root'){ + $view->params = [array_keys($studip_object_status), $user_id]; + $rs = $view->get_query("view:TREE_INST_STATUS"); + while ($rs->next_record()){ + $studip_object_status[$rs->f("Institut_id")] = 1; + } + $view->params = [array_keys($studip_object_status), $user_id]; + $rs = $view->get_query("view:TREE_FAK_STATUS"); + while ($rs->next_record()){ + $studip_object_status[$rs->f("Fakultaets_id")] = 1; + if ($rs->f("Institut_id") && isset($studip_object_status[$rs->f("Institut_id")])){ + $studip_object_status[$rs->f("Institut_id")] = 1; + } + } + } + $studip_object_status['root'] = ($user_perm == "root") ? 1 : -1; + $this->tree_status = $studip_object_status; + } + + function parseCommand(){ + if (Request::quoted('mode')) + $this->mode = Request::quoted('mode'); + if (Request::option('cmd')){ + $exec_func = "execCommand" . Request::option('cmd'); + if (method_exists($this,$exec_func)){ + if ($this->$exec_func()){ + $this->tree->init(); + $this->initTreeStatus(); + } + } + } + if ($this->mode == "MoveItem") + $this->move_item_id = $this->marked_item; + } + + function execCommandOrderItem(){ + $direction = Request::quoted('direction'); + $item_id = Request::option('item_id'); + $items_to_order = $this->tree->getKids($this->tree->tree_data[$item_id]['parent_id']); + if (!$this->isParentAdmin($item_id) || !$items_to_order) + return false; + for ($i = 0; $i < count($items_to_order); ++$i){ + if ($item_id == $items_to_order[$i]) + break; + } + if ($direction == "up" && isset($items_to_order[$i-1])){ + $items_to_order[$i] = $items_to_order[$i-1]; + $items_to_order[$i-1] = $item_id; + } elseif (isset($items_to_order[$i+1])){ + $items_to_order[$i] = $items_to_order[$i+1]; + $items_to_order[$i+1] = $item_id; + } + $view = DbView::getView('range_tree'); + for ($i = 0; $i < count($items_to_order); ++$i){ + $view->params = [$i, $items_to_order[$i]]; + $rs = $view->get_query("view:TREE_UPD_PRIO"); + } + $this->mode = ""; + $this->msg[$item_id] = "msg§" . (($direction == "up") ? _("Element wurde um eine Position nach oben verschoben.") : _("Element wurde um eine Position nach unten verschoben.")); + return true; + } + + function execCommandNewItem(){ + $item_id = Request::option('item_id'); + if ($this->isItemAdmin($item_id)){ + $new_item_id = DbView::get_uniqid(); + $this->tree->storeItem($new_item_id,$item_id,_("Neues Element"), $this->tree->getNumKids($item_id) +1); + $this->anchor = $new_item_id; + $this->edit_item_id = $new_item_id; + $this->open_ranges[$item_id] = true; + $this->open_items[$new_item_id] = true; + if ($this->mode != "NewItem") + $this->msg[$new_item_id] = "info§" . _("Wählen Sie einen Namen für dieses Element, oder verlinken Sie es mit einer Einrichtung in Stud.IP"); + $this->mode = "NewItem"; + } + return false; + } + + function execCommandSearchStudip(){ + $item_id = Request::option('item_id'); + $parent_id = Request::quoted('parent_id'); + $search_str = Request::quoted('edit_search'); + $view = DbView::getView('range_tree'); + if(mb_strlen($search_str) > 1){ + $view->params[0] = $search_str; + $rs = $view->get_query("view:TREE_SEARCH_INST"); + while ($rs->next_record()){ + $this->search_result[$rs->f("Institut_id")]['name'] = $rs->f("Name"); + $this->search_result[$rs->f("Institut_id")]['studip_object'] = "inst"; + } + if ($parent_id == "root"){ + $view->params[0] = $search_str; + $rs = $view->get_query("view:TREE_SEARCH_FAK"); + while ($rs->next_record()){ + $this->search_result[$rs->f("Fakultaets_id")]['name'] = $rs->f("Name"); + $this->search_result[$rs->f("Fakultaets_id")]['studip_object'] = "fak"; + } + } + $search_msg = "info§" . sprintf(_("Ihre Suche ergab %s Treffer."),count($this->search_result)); + } else { + $search_msg = "error§" . _("Sie haben keinen Suchbegriff eingegeben."); + } + if ($this->mode == "NewItem"){ + Request::set('item_id', $parent_id); + $this->execCommandNewItem(); + } else { + $this->anchor = $item_id; + $this->edit_item_id = $item_id; + } + $this->msg[$this->edit_item_id] = $search_msg; + return false; + } + + function execCommandEditItem(){ + $item_id = Request::option('item_id'); + if ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)){ + $this->mode = "EditItem"; + $this->anchor = $item_id; + $this->edit_item_id = $item_id; + $this->msg[$item_id] = "info§" . _("Wählen Sie einen Namen für dieses Element, oder verlinken Sie es mit einer Einrichtung in Stud.IP"); + } + return false; + } + + function execCommandInsertItem(){ + $item_id = Request::option('item_id'); + $parent_id = Request::option('parent_id'); + $item_name = Request::quoted('edit_name'); + $tmp = explode(":",Request::quoted('edit_studip_object')); + if ($tmp[1] == "fak" || $tmp[1] == "inst"){ + $studip_object = $tmp[1]; + $studip_object_id = $tmp[0]; + } else { + $studip_object = ""; + $studip_object_id = ""; + } + if ($this->mode == "NewItem" && $item_id){ + if ($this->isItemAdmin($parent_id)){ + $priority = count($this->tree->getKids($parent_id)); + $affected_rows = $this->tree->InsertItem($item_id,$parent_id,$item_name,$priority,$studip_object,$studip_object_id); + if ($affected_rows){ + $this->mode = ""; + $this->anchor = $item_id; + $this->open_items[$item_id] = true; + $this->msg[$item_id] = "msg§" . _("Dieses Element wurde neu eingefügt."); + } + } + } + if ($this->mode == "EditItem"){ + if ($this->isParentAdmin($item_id)){ + $affected_rows = $this->tree->UpdateItem($item_name,$studip_object,$studip_object_id,$item_id); + if ($affected_rows){ + $this->msg[$item_id] = "msg§" . _("Element wurde geändert."); + } else { + $this->msg[$item_id] = "info§" . _("Keine Veränderungen vorgenommen."); + } + $this->mode = ""; + $this->anchor = $item_id; + $this->open_items[$item_id] = true; + } + } + return true; + } + + function execCommandInsertFak(){ + $studip_object_id = Request::quoted('insert_fak'); + $parent_id = 'root'; + if ($this->isItemAdmin($parent_id)){ + $item_id = DbView::get_uniqid(); + $priority = count($this->tree->getKids($parent_id)); + $affected_rows = $this->tree->InsertItem($item_id,$parent_id,'',$priority,'fak',$studip_object_id); + if($affected_rows){ + $view = DbView::getView('range_tree'); + $priority = 0; + $rs = $view->get_query("SELECT * FROM Institute WHERE fakultaets_id <> Institut_id AND fakultaets_id = '$studip_object_id' ORDER BY Name"); + while($rs->next_record()){ + $affected_rows += $this->tree->InsertItem(DbView::get_uniqid(),$item_id,addslashes($rs->f('name')),$priority++,'inst',$rs->f('Institut_id')); + } + $this->msg[$item_id] = "msg§" . sprintf(_("%s Elemente wurden eingefügt."), $affected_rows); + $this->mode = ""; + $this->anchor = $item_id; + $this->open_items[$item_id] = true; + $this->open_ranges[$item_id] = true; + } + } + return true; + } + + function execCommandAssertDeleteItem(){ + $item_id = Request::option('item_id'); + if ($this->isParentAdmin($item_id)){ + $this->mode = "AssertDeleteItem"; + $this->msg[$item_id] = "info§" ._("Sie beabsichtigen dieses Element, inklusive aller Unterelemente, zu löschen. ") + . sprintf(_("Es werden insgesamt %s Elemente gelöscht!"),count($this->tree->getKidsKids($item_id))+1) + . "
" . _("Wollen Sie diese Elemente wirklich löschen?") . "
" + . '
' + . LinkButton::createAccept(_("JA"), URLHelper::getURL($this->getSelf("cmd=DeleteItem&item_id=$item_id"))) + . LinkButton::createCancel(_("NEIN"), URLHelper::getURL($this->getSelf("cmd=Cancel&item_id=$item_id"))) + . "
"; + } + return false; + } + + function execCommandDeleteItem(){ + $item_id = Request::option('item_id'); + $deleted = 0; + $item_name = $this->tree->tree_data[$item_id]['name']; + if ($this->isParentAdmin($item_id) && $this->mode == "AssertDeleteItem"){ + $this->anchor = $this->tree->tree_data[$item_id]['parent_id']; + $items_to_delete = $this->tree->getKidsKids($item_id); + $items_to_delete[] = $item_id; + $deleted = $this->tree->DeleteItems($items_to_delete); + if ($deleted['items']){ + $this->msg[$this->anchor] = "msg§" . sprintf(_("Das Element %s und alle Unterelemente (insgesamt %s) wurden gelöscht. "),htmlReady($item_name),$deleted['items']); + } else { + $this->msg[$this->anchor] = "error§" . _("Fehler, es konnten keine Elemente gelöscht werden!"); + } + if ($deleted['categories']){ + $this->msg[$this->anchor] .= sprintf(_("
Es wurden %s Datenfelder gelöscht. "),$deleted['categories']); + } + $this->mode = ""; + $this->open_items[$this->anchor] = true; + } + return true; + } + + function execCommandMoveItem(){ + $item_id = Request::option('item_id'); + $this->anchor = $item_id; + $this->marked_item = $item_id; + $this->mode = "MoveItem"; + return false; + } + + function execCommandDoMoveItem(){ + $item_id = Request::option('item_id'); + $item_to_move = $this->marked_item; + if ($this->mode == "MoveItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) + && ($item_to_move != $item_id) && ($this->tree->tree_data[$item_to_move]['parent_id'] != $item_id) + && !$this->tree->isChildOf($item_to_move,$item_id)){ + $view = DbView::getView('range_tree'); + $view->params = [$item_id, count($this->tree->getKids($item_id)), $item_to_move]; + $rs = $view->get_query("view:TREE_MOVE_ITEM"); + if ($rs->affected_rows()){ + $this->msg[$item_to_move] = "msg§" . _("Element wurde verschoben."); + } else { + $this->msg[$item_to_move] = "error§" . _("Keine Verschiebung durchgeführt."); + } + } + $this->anchor = $item_to_move; + $this->open_ranges[$item_id] = true; + $this->open_items[$item_to_move] = true; + $this->mode = ""; + return true; + } + + function execCommandOrderCat(){ + $item_id = Request::option('item_id'); + $direction = Request::quoted('direction'); + $cat_id = Request::option('cat_id'); + $items_to_order = []; + if ($this->isItemAdmin($item_id)){ + $range_object = RangeTreeObject::GetInstance($item_id); + $categories =& $range_object->getCategories(); + while($categories->nextRow()){ + $items_to_order[] = $categories->getField("kategorie_id"); + } + for ($i = 0; $i < count($items_to_order); ++$i){ + if ($cat_id == $items_to_order[$i]) + break; + } + if ($direction == "up" && isset($items_to_order[$i-1])){ + $items_to_order[$i] = $items_to_order[$i-1]; + $items_to_order[$i-1] = $cat_id; + } elseif (isset($items_to_order[$i+1])){ + $items_to_order[$i] = $items_to_order[$i+1]; + $items_to_order[$i+1] = $cat_id; + } + $view = DbView::getView('range_tree'); + for ($i = 0; $i < count($items_to_order); ++$i){ + $view->params = [$i,$items_to_order[$i]]; + $rs = $view->get_query("view:CAT_UPD_PRIO"); + } + $this->msg[$item_id] = "msg§" . _("Datenfelder wurden neu geordnet"); + } + $this->anchor = $item_id; + return false; + } + + function execCommandNewCat(){ + $item_id = Request::option('item_id'); + if ($this->isItemAdmin($item_id)){ + $range_object = RangeTreeObject::GetInstance($item_id); + $this->edit_cat_snap =& $range_object->getCategories(); + $this->edit_cat_snap->result[$this->edit_cat_snap->numRows] = + ["kategorie_id" => "new_entry", "range_id" => $item_id, "name" => "Neues Datenfeld", "content" => "Neues Datenfeld", + "priority" => $this->edit_cat_snap->numRows]; + ++$this->edit_cat_snap->numRows; + $this->edit_cat_item_id = $item_id; + $this->mode = "NewCat"; + } + $this->anchor = $item_id; + return false; + } + + function execCommandUpdateCat(){ + $item_id = Request::option('item_id'); + $cat_name = Request::getArray('cat_name'); + $cat_content = Request::getArray('cat_content'); + $cat_prio = Request::getArray('cat_prio'); + $inserted = false; + $updated = 0; + if ($this->isItemAdmin($item_id)){ + $view = DbView::getView('range_tree'); + if (isset($cat_name['new_entry'])){ + $view->params = [DbView::get_uniqid(),$item_id,$cat_name['new_entry'],$cat_content['new_entry'],$cat_prio['new_entry']]; + $rs = $view->get_query("view:CAT_INS_ALL"); + if ($rs->affected_rows()){ + $inserted = true; + } + unset($cat_name['new_entry']); + } + foreach ($cat_name as $key => $value){ + $view->params = [$value,$cat_content[$key],$key]; + $rs = $view->get_query("view:CAT_UPD_CONTENT"); + if ($rs->affected_rows()){ + ++$updated; + } + } + if ($updated){ + $this->msg[$item_id] = "msg§" . sprintf(_("Es wurden %s Datenfelder aktualisiert."),$updated); + if ($inserted) { + $this->msg[$item_id] .= _("Ein neues Datenfeld wurde eingefügt."); + } + } elseif ($inserted){ + $this->msg[$item_id] = "msg§" . _("Ein neues Datenfeld wurde eingefügt."); + } else { + $this->msg[$item_id] = "info§" . _("Keine Veränderungen vorgenommen."); + } + } + $this->anchor = $item_id; + $this->mode = ""; + return false; + } + + function execCommandDeleteCat(){ + $item_id = Request::option('item_id'); + $cat_id = Request::option('cat_id'); + if ($this->isItemAdmin($item_id)){ + $view = DbView::getView('range_tree'); + $view->params[0] = $cat_id; + $rs = $view->get_query("view:CAT_DEL"); + if ($rs->affected_rows()){ + $this->msg[$item_id] = "msg§" . _("Ein Datenfeld wurde gelöscht."); + } + } + $this->mode = ""; + $this->anchor = $item_id; + return false; + } + + + function execCommandCancel(){ + $item_id = Request::option('item_id'); + $this->mode = ""; + $this->anchor = $item_id; + return false; + } + + function isItemAdmin($item_id){ + $admin_ranges = $this->tree->getAdminRange($item_id); + for ($i = 0; $i < count($admin_ranges); ++$i){ + if ( + isset($this->tree_status[$admin_ranges[$i]]) + && $this->tree_status[$admin_ranges[$i]] == 1 + ) { + return true; + } + } + return false; + } + + function isParentAdmin($item_id){ + $admin_ranges = $this->tree->getAdminRange($this->tree->tree_data[$item_id]['parent_id']); + if (!empty($admin_ranges)) { + for ($i = 0; $i < count($admin_ranges); ++$i) { + if ( + isset($this->tree_status[$admin_ranges[$i]]) + && $this->tree_status[$admin_ranges[$i]] == 1 + ) { + return true; + } + } + } + return false; + } + + function getItemContent($item_id){ + + if ($item_id == $this->edit_item_id ) + return $this->getEditItemContent(); + if ($item_id == $this->move_item_id){ + $this->msg[$item_id] = "info§" . sprintf(_("Dieses Element wurde zum Verschieben markiert. Bitte wählen Sie ein Einfügesymbol %s aus, um das Element zu verschieben."), Icon::create('arr_2right', 'sort', ['title' => "Einfügesymbol"])->asImg(16, ["alt" => "Einfügesymbol"])); + } + $content = "\n"; + $content .= $this->getItemMessage($item_id); + $content .= "\n
"; + + if ($this->isItemAdmin($item_id)){ + $content .= LinkButton::create(_("Neues Objekt"), + URLHelper::getURL($this->getSelf("cmd=NewItem&item_id=$item_id")), + ['title' => _("Innerhalb dieser Ebene ein neues Element einfügen")]); + } + + if ($this->isParentAdmin($item_id) && $item_id !=$this->start_item_id && $item_id != "root"){ + $content .= LinkButton::create(_("Bearbeiten"), + URLHelper::getURL($this->getSelf("cmd=EditItem&item_id=$item_id"))); + + $content .= LinkButton::create(_("Löschen"), + URLHelper::getURL($this->getSelf("cmd=AssertDeleteItem&item_id=$item_id"))); + + if ($this->move_item_id == $item_id && $this->mode == "MoveItem"){ + $content .= LinkButton::create(_("Abbrechen"), + URLHelper::getURL($this->getSelf("cmd=Cancel&item_id=$item_id"))); + } else { + $content .= LinkButton::create(_("Verschieben"), + URLHelper::getURL($this->getSelf("cmd=MoveItem&item_id=$item_id"))); + } + } + + $content .= "
"; + $content .= "\n"; + if ($item_id == "root"){ + if ($this->isItemAdmin($item_id)){ + $view = DbView::getView('range_tree'); + $rs = $view->get_query("SELECT i1.Name,i1.Institut_id,COUNT(i2.Institut_id) as num FROM Institute i1 LEFT JOIN Institute i2 ON i1.Institut_id = i2.fakultaets_id AND i2.fakultaets_id<>i2.Institut_id WHERE i1.fakultaets_id=i1.Institut_id GROUP BY i1.Institut_id ORDER BY Name"); + $content .= "\n"; + } + $content .= "\n"; + $content .= "\n"; + $content .= "\n
"; + $content .= "\n
getSelf("cmd=InsertFak")) . "\" method=\"post\" class=\"default\">" + . CSRFProtection::tokenTag() + . '
' + . Button::create(_("Fakultät einfügen")) + ."
"; + $content .= "
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; + return $content; + } + $range_object = RangeTreeObject::GetInstance($item_id); + $name = !empty($range_object->item_data['type']) ? $range_object->item_data['type'] . ": " : ""; + $name .= $range_object->item_data['name']; + $content .= "\n" . htmlReady($name) ." "; + if (is_array($range_object->item_data_mapping)){ + $content .= "\n"; + foreach ($range_object->item_data_mapping as $key => $value){ + if ($range_object->item_data[$key]){ + $content .= "" . htmlReady($value) . ": "; + $content .= formatLinks($range_object->item_data[$key]) . "  "; + } + } + $content .= " "; + } elseif (!$range_object->item_data['studip_object']){ + $content .= "\n" . + _("Dieses Element ist keine Stud.IP-Einrichtung, es hat daher keine Grunddaten."); + } else { + $content .= "\n" . _("Keine Grunddaten vorhanden!"); + } + $content .= "\n
" . _("Mitarbeiter:") . " " . $range_object->getNumStaff() . "
"; + if ($this->isItemAdmin($item_id) && $range_object->item_data['studip_object']){ + $content .= "\n
"; + + $content .= LinkButton::create(_("Grunddaten in Stud.IP bearbeiten"), "dispatch.php/institute/basicdata/index?admin_inst_id=" . $range_object->item_data['studip_object_id']); + $content .= "
"; + } + + $content .= " "; + + if ($this->mode == "NewCat" && ($this->edit_cat_item_id == $item_id)){ + $categories =& $this->edit_cat_snap; + } else { + $categories =& $range_object->getCategories(); + } + if (!$this->isItemAdmin($item_id)){ + if ($categories->numRows){ + while($categories->nextRow()){ + $content .= "\n" . formatReady($categories->getField("name")) . ""; + $content .= "\n" . formatReady($categories->getField("content")) . ""; + } + } else { + $content .= "\n" . _("Keine weiteren Daten vorhanden!") . ""; + } + } else { + $content .= "" . $this->getEditCatContent($item_id,$categories) . ""; + } + $content .= ""; + return $content; + } + + function getItemHead($item_id){ + $head = ""; + if ($this->mode == "MoveItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) + && ($this->move_item_id != $item_id) && ($this->tree->tree_data[$this->move_item_id]['parent_id'] != $item_id) + && !$this->tree->isChildOf($this->move_item_id,$item_id)){ + $head .= "getSelf("cmd=DoMoveItem&item_id=$item_id")) . "\">" + . Icon::create('arr_2right', 'sort', ['title' => "An dieser Stelle einfügen"])->asImg(16, ["alt" => "An dieser Stelle einfügen"])." "; + } + $head .= parent::getItemHead($item_id); + if ($item_id != $this->start_item_id && $this->isParentAdmin($item_id) && $item_id != $this->edit_item_id){ + $head .= ""; + if (!$this->tree->isFirstKid($item_id)){ + $head .= "getSelf("cmd=OrderItem&direction=up&item_id=$item_id")) . + "\">" . Icon::create('arr_2up', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach oben verschieben")]) . " ". + ""; + } + if (!$this->tree->isLastKid($item_id)){ + $head .= "getSelf("cmd=OrderItem&direction=down&item_id=$item_id")) . + "\">" . Icon::create('arr_2down', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach unten verschieben")]) . " ". + ""; + } + $head .= " "; + } + return $head; + } + + function getEditItemContent(){ + $content = ''; + ob_start(); + ?> +
+
edit_item_id}")) ?>"> + + + + + + getItemMessage($this->edit_item_id, 2) ?> +
+ +
+ + + + + +
+ +
+ + + + + + + +
+ +
+ + URLHelper::getURL($this->getSelf("cmd=SearchStudIP&item_id={$this->edit_item_id}")) + ]); ?> + + getSelf("cmd=Cancel&item_id=" . ($this->mode == "NewItem" ? $this->tree->tree_data[$this->edit_item_id]['parent_id'] : $this->edit_item_id)))) ?> +
+
+
+ + +
"> + + + numRows) while ($cat_snap->nextRow()) : ?> +
+ + + pos && $cat_snap->getField("kategorie_id") != "new_entry") : ?> + getField("kategorie_id"))) ?>"> + asImg(['class' => 'text-top', 'title' => _("Datenfeld nach oben")]) ?> + + + + pos != $cat_snap->numRows-1 && $cat_snap->getField("kategorie_id") != "new_entry") : ?> + getField("kategorie_id"))) ?>"> + asImg(['class' => 'text-top', 'title' => _("Datenfeld nach unten")]) ?> + + + + + + getField("kategorie_id"))) ?>"> + asImg(['class' => 'text-top', 'title' => _("Datenfeld löschen")]) ?> + + + + + + +
+ + +
+ numRows) : ?> + + + getSelf("cmd=NewCat&item_id=$item_id"))) ?> +
+
+ + '; + + return $content; + } + + function getItemMessage($item_id, $colspan = 1) + { + $content = ""; + if (!empty($this->msg[$item_id])) { + $msg = explode("§", $this->msg[$item_id]); + $pics = [ + 'error' => Icon::create('decline', 'attention'), + 'info' => Icon::create('exclaim', 'inactive'), + 'msg' => Icon::create('accept', 'accept')]; + $content = "\n + + +
" . $pics[$msg[0]]->asImg(['class' => 'text-top']) . "" . $msg[1] . "
"; + } + return $content; + } + + function getSelf($param = ''){ + $url_params = "foo=" . DbView::get_uniqid(); + if ($this->mode) $url_params .= "&mode=" . $this->mode; + if ($param) $url_params .= '&' . $param; + return parent::getSelf($url_params); + } +} + +//test +//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); +//include 'lib/include/html_head.inc.php'; +//$test = new StudipRangeTreeViewAdmin(); +//$test->showTree(); +//echo ""; +//page_close(); +?> diff --git a/lib/classes/StudipSemRangeTreeViewSimple.class.php b/lib/classes/StudipSemRangeTreeViewSimple.class.php deleted file mode 100644 index eecdd70..0000000 --- a/lib/classes/StudipSemRangeTreeViewSimple.class.php +++ /dev/null @@ -1,247 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - - -/** -* class to print out the range tree -* -* This class prints out a html representation a part of the tree -* -* @access public -* @author André Noack -* @package -*/ -class StudipSemRangeTreeViewSimple { - - - var $tree; - var $show_entries; - var $start_item_id; - var $root_content; - - /** - * constructor - * - * @access public - */ - function __construct($start_item_id = "root", $sem_number = false, $sem_status = false, $visible_only = false){ - $this->start_item_id = ($start_item_id) ? $start_item_id : "root"; - $this->root_content = $GLOBALS['UNI_INFO']; - $args = null; - if ($sem_number !== false){ - $args['sem_number'] = $sem_number; - } - if ($sem_status !== false){ - $args['sem_status'] = $sem_status; - } - $args['visible_only'] = $visible_only; - $this->tree = TreeAbstract::GetInstance("StudipRangeTree",$args); - if (empty($this->tree->tree_data[$this->start_item_id])) { - $this->start_item_id = "root"; - } - } - - public function showSemRangeTree($start_id = null) - { - echo ' - - - - - - - - - - - -
-
- -
'. - '
'. - $this->getSemPath($start_id); - echo '
-
-
'. - formatReady($this->tree->getValue($this->start_item_id, 'name')). - '
-
' . - - formatReady($this->getTooltip($this->start_item_id)) . - '
-
'; - echo'
-
-
'; - if ($this->start_item_id != 'root') { - echo ' - ' . - Icon::create('arr_2left', 'clickable')->asImg(['class' => 'text-top', 'title' =>_('eine Ebene zurück')]) . - ''; - } else { - echo ' '; - } - echo ' -
'; - $this->showKids($this->start_item_id); - echo ' -
'; - $this->showContent($this->start_item_id); - echo ' -
'; - } - - function showKids($item_id){ - $num_kids = $this->tree->getNumKids($item_id); - $kids = $this->tree->getKids($item_id); - $kids_table = ' - - - -
- - -
    '; - } - } - if (!$num_kids){ - $kids_table .= "
  • "; - $kids_table .= _("Auf dieser Ebene existieren keine weiteren Unterebenen."); - $kids_table .= "
  • "; - } - $kids_table .= "
"; - echo $kids_table; - } - - function getTooltip($item_id){ - if ($item_id == "root"){ - $ret = ($this->root_content) ? $this->root_content : _("Keine weitere Info vorhanden"); - } else { - $info = ''; - $range_object = RangeTreeObject::GetInstance($item_id); - if (is_array($range_object->item_data_mapping)){ - foreach ($range_object->item_data_mapping as $key => $value){ - if ($range_object->item_data[$key]){ - $info .= $value . ": "; - $info .= $range_object->item_data[$key]. " "; - } - } - } - $ret = $info ?: _("Keine weitere Info vorhanden"); - } - return $ret; - } - - public function getInfoIcon($item_id) - { - if ($item_id === 'root') { - $info = $this->root_content; - } - $ret = $info ? tooltipicon(kill_format($info)) : ''; - return $ret; - } - - function showContent($item_id){ - echo "\n
"; - if ($item_id != "root"){ - - if ($num_entries = $this->tree->getNumEntries($item_id)){ - if ($this->show_entries != "level"){ - echo "getSelf("cmd=show_sem_range_tree&item_id=$item_id")) ."\">"; - } - printf(_("%s Einträge auf dieser Ebene. "),$num_entries); - if ($this->show_entries != "level"){ - echo ""; - } - } else { - echo _("Keine Einträge auf dieser Ebene vorhanden!"); - } - if ($this->tree->hasKids($item_id) && ($num_entries = $this->tree->getNumEntries($this->start_item_id,true))){ - echo "  /  "; - if ($this->show_entries != "sublevels"){ - echo "getSelf("cmd=show_sem_range_tree&item_id={$this->start_item_id}_withkids")) ."\">"; - } - printf(_("%s Einträge in allen Unterebenen vorhanden"), $num_entries); - if ($this->show_entries != "sublevels"){ - echo ""; - } - } - } - echo "\n
"; - } - - public function getSemPath($start_id = null) - { - $parents = $this->tree->getParents($this->start_item_id); - $ret = ''; - if ($parents) { - $add_item = false; - $start_id = $start_id === null ? 'root' : $start_id; - for($i = count($parents) - 1; $i >= 0; --$i) { - if ($add_item || $start_id == $parents[$i]) { - $ret .= ($add_item === TRUE ? ' / ' : '') - . '' - . htmlReady($this->tree->tree_data[$parents[$i]]['name']) - . ''; - $add_item = true; - } - } - } - if ($this->start_item_id == 'root') { - $ret = '' . $this->tree->root_name . ''; - } else { - $ret .= ' / '. htmlReady($this->tree->tree_data[$this->start_item_id]['name']). ''; - $ret .= ' /  '; - } - return $ret; - } - - - - function getSelf($param = '', $with_start_item = true){ - $url_params = (($with_start_item) ? 'start_item_id=' . $this->start_item_id . '&' : '') . $param ; - return '?' . $url_params; - } -} -?> diff --git a/lib/classes/StudipSemRangeTreeViewSimple.php b/lib/classes/StudipSemRangeTreeViewSimple.php new file mode 100644 index 0000000..eecdd70 --- /dev/null +++ b/lib/classes/StudipSemRangeTreeViewSimple.php @@ -0,0 +1,247 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + + +/** +* class to print out the range tree +* +* This class prints out a html representation a part of the tree +* +* @access public +* @author André Noack +* @package +*/ +class StudipSemRangeTreeViewSimple { + + + var $tree; + var $show_entries; + var $start_item_id; + var $root_content; + + /** + * constructor + * + * @access public + */ + function __construct($start_item_id = "root", $sem_number = false, $sem_status = false, $visible_only = false){ + $this->start_item_id = ($start_item_id) ? $start_item_id : "root"; + $this->root_content = $GLOBALS['UNI_INFO']; + $args = null; + if ($sem_number !== false){ + $args['sem_number'] = $sem_number; + } + if ($sem_status !== false){ + $args['sem_status'] = $sem_status; + } + $args['visible_only'] = $visible_only; + $this->tree = TreeAbstract::GetInstance("StudipRangeTree",$args); + if (empty($this->tree->tree_data[$this->start_item_id])) { + $this->start_item_id = "root"; + } + } + + public function showSemRangeTree($start_id = null) + { + echo ' + + + + + + + + + + + +
+
+ +
'. + '
'. + $this->getSemPath($start_id); + echo '
+
+
'. + formatReady($this->tree->getValue($this->start_item_id, 'name')). + '
+
' . + + formatReady($this->getTooltip($this->start_item_id)) . + '
+
'; + echo'
+
+
'; + if ($this->start_item_id != 'root') { + echo ' + ' . + Icon::create('arr_2left', 'clickable')->asImg(['class' => 'text-top', 'title' =>_('eine Ebene zurück')]) . + ''; + } else { + echo ' '; + } + echo ' +
'; + $this->showKids($this->start_item_id); + echo ' +
'; + $this->showContent($this->start_item_id); + echo ' +
'; + } + + function showKids($item_id){ + $num_kids = $this->tree->getNumKids($item_id); + $kids = $this->tree->getKids($item_id); + $kids_table = ' + + + +
+ + +
    '; + } + } + if (!$num_kids){ + $kids_table .= "
  • "; + $kids_table .= _("Auf dieser Ebene existieren keine weiteren Unterebenen."); + $kids_table .= "
  • "; + } + $kids_table .= "
"; + echo $kids_table; + } + + function getTooltip($item_id){ + if ($item_id == "root"){ + $ret = ($this->root_content) ? $this->root_content : _("Keine weitere Info vorhanden"); + } else { + $info = ''; + $range_object = RangeTreeObject::GetInstance($item_id); + if (is_array($range_object->item_data_mapping)){ + foreach ($range_object->item_data_mapping as $key => $value){ + if ($range_object->item_data[$key]){ + $info .= $value . ": "; + $info .= $range_object->item_data[$key]. " "; + } + } + } + $ret = $info ?: _("Keine weitere Info vorhanden"); + } + return $ret; + } + + public function getInfoIcon($item_id) + { + if ($item_id === 'root') { + $info = $this->root_content; + } + $ret = $info ? tooltipicon(kill_format($info)) : ''; + return $ret; + } + + function showContent($item_id){ + echo "\n
"; + if ($item_id != "root"){ + + if ($num_entries = $this->tree->getNumEntries($item_id)){ + if ($this->show_entries != "level"){ + echo "getSelf("cmd=show_sem_range_tree&item_id=$item_id")) ."\">"; + } + printf(_("%s Einträge auf dieser Ebene. "),$num_entries); + if ($this->show_entries != "level"){ + echo ""; + } + } else { + echo _("Keine Einträge auf dieser Ebene vorhanden!"); + } + if ($this->tree->hasKids($item_id) && ($num_entries = $this->tree->getNumEntries($this->start_item_id,true))){ + echo "  /  "; + if ($this->show_entries != "sublevels"){ + echo "getSelf("cmd=show_sem_range_tree&item_id={$this->start_item_id}_withkids")) ."\">"; + } + printf(_("%s Einträge in allen Unterebenen vorhanden"), $num_entries); + if ($this->show_entries != "sublevels"){ + echo ""; + } + } + } + echo "\n
"; + } + + public function getSemPath($start_id = null) + { + $parents = $this->tree->getParents($this->start_item_id); + $ret = ''; + if ($parents) { + $add_item = false; + $start_id = $start_id === null ? 'root' : $start_id; + for($i = count($parents) - 1; $i >= 0; --$i) { + if ($add_item || $start_id == $parents[$i]) { + $ret .= ($add_item === TRUE ? ' / ' : '') + . '' + . htmlReady($this->tree->tree_data[$parents[$i]]['name']) + . ''; + $add_item = true; + } + } + } + if ($this->start_item_id == 'root') { + $ret = '' . $this->tree->root_name . ''; + } else { + $ret .= ' / '. htmlReady($this->tree->tree_data[$this->start_item_id]['name']). ''; + $ret .= ' /  '; + } + return $ret; + } + + + + function getSelf($param = '', $with_start_item = true){ + $url_params = (($with_start_item) ? 'start_item_id=' . $this->start_item_id . '&' : '') . $param ; + return '?' . $url_params; + } +} +?> diff --git a/lib/classes/StudipSemSearch.class.php b/lib/classes/StudipSemSearch.class.php deleted file mode 100644 index 8932dd1..0000000 --- a/lib/classes/StudipSemSearch.class.php +++ /dev/null @@ -1,191 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** -* Class to build search formular and execute search -* -* -* -* @access public -* @author André Noack -* @package DBTools -**/ -class StudipSemSearch { - - var $form; - var $search_result; - var $form_name; - var $sem_tree; - var $range_tree; - var $search_done = false; - var $found_rows = false; - var $search_button_clicked = false; - var $new_search_button_clicked = false; - var $sem_change_button_clicked = false; - var $override_sem = false; - var $attributes_default = ['style' => 'width:100%;']; - var $search_scopes = []; - var $search_ranges = []; - var $search_sem_class = 'all'; - var $visible_only = false; - var $sem_dates; - - function __construct($form_name = "search_sem", $auto_search = true, $visible_only = false, $sem_class = 'all'){ - - $search_fields = ['title' => ['type' => 'text'], - 'sub_title' => ['type' => 'text'], - 'number' => ['type' => 'text'], - 'comment' => ['type' => 'text'], - 'lecturer' => ['type' => 'text'], - 'scope' => ['type' => 'text'], - 'quick_search' => ['type' => 'text'], - 'type' => ['type' => 'select', 'default_value' => 'all', 'max_length' => 35,'options_callback' => [$this, 'getSelectOptions']], - 'sem' => ['type' => 'select', 'default_value' => 'all','options_callback' => [$this, 'getSelectOptions']], - 'category' => ['type' => 'select', 'default_value' => 'all', 'max_length' => 50,'options_callback' => [$this, 'getSelectOptions']], - 'combination' => ['type' => 'select', 'default_value' => 'AND','options_callback' => [$this, 'getSelectOptions']], - 'scope_choose' => ['type' => 'select', 'default_value' => 'root', 'max_length' => 45,'options_callback' => [$this, 'getSelectOptions']], - 'range_choose' => ['type' => 'select', 'default_value' => 'root', 'max_length' => 45,'options_callback' => [$this, 'getSelectOptions']], - 'qs_choose' => ['type' => 'select', - 'default_value' => 'title_lecturer_number', - 'options_callback' => [$this, 'getSelectOptions'] - ] - ]; - $search_buttons = ['do_search' => ['caption' => _("Suchen"), 'info' => _("Suche starten")], - 'sem_change' => ['caption' => _('Auswählen'), 'info' => _("anderes Semester auswählen")], - 'new_search' => ['caption' => _('Neue Suche'), 'info' =>_("Neue Suche starten")]]; - //workaround: Qicksearch ändert den Namen des Eingabefeldes - if (Request::get("search_sem_quick_search_parameter")) { - Request::set('search_sem_quick_search', Request::get("search_sem_quick_search_parameter")); - } - $this->form = new StudipForm($search_fields, $search_buttons, $form_name , false); - $this->form_name = $form_name; - $this->sem_dates = Semester::findAllVisible(); - $this->visible_only = $visible_only; - $this->search_sem_class = $sem_class; - - if($this->form->isClicked('do_search') || ($this->form->isSended() && (!$this->form->isClicked('sem_change') || mb_strlen($this->form->getFormFieldValue('quick_search')) > 2))){ - $this->search_button_clicked = true; - if ($auto_search){ - $this->doSearch(); - $this->search_done = true; - } - } - - $this->new_search_button_clicked = $this->form->isClicked('new_search'); - $this->sem_change_button_clicked = $this->form->isClicked('do_search'); - - } - - function getSearchField($name,$attributes = false,$default = false){ - if (!$attributes){ - $attributes = $this->attributes_default; - } - return $this->form->getFormField($name,$attributes,$default); - } - - function getSelectOptions($caller, $name){ - $options = []; - if ($name == "combination"){ - $options = [['name' =>_("UND"),'value' => 'AND'],['name' => _("ODER"), 'value' => 'OR']]; - } elseif ($name == "sem"){ - $options = [['name' =>_("alle"),'value' => 'all']]; - for ($i = count($this->sem_dates) -1 ; $i >= 0; --$i){ - $options[] = ['name' => $this->sem_dates[$i]['name'], 'value' => $i]; - } - } elseif ($name == "type"){ - $options = [['name' =>_("alle"),'value' => 'all']]; - foreach($GLOBALS['SEM_TYPE'] as $type_key => $type_value){ - if($this->search_sem_class == 'all' || $type_value['class'] == $this->search_sem_class){ - $options[] = ['name' => $type_value['name'] . " (". $GLOBALS['SEM_CLASS'][$type_value['class']]['name'] .")", - 'value' => $type_key]; - } - } - } elseif ($name == "category"){ - $options = [['name' =>_("alle"),'value' => 'all']]; - foreach($GLOBALS['SEM_CLASS'] as $class_key => $class_value){ - $options[] = ['name' => $class_value['name'], - 'value' => $class_key]; - } - } elseif ($name == "scope_choose"){ - if(!is_object($this->sem_tree)){ - $this->sem_tree = TreeAbstract::GetInstance("StudipSemTree", false); - } - $options = [['name' => $this->sem_tree->root_name, 'value' => 'root']]; - for($i = 0; $i < count($this->search_scopes); ++$i){ - $options[] = ['name' => $this->sem_tree->tree_data[$this->search_scopes[$i]]['name'], 'value' => $this->search_scopes[$i]]; - } - } elseif ($name == "range_choose"){ - if(!is_object($this->range_tree)){ - $this->range_tree = TreeAbstract::GetInstance("StudipRangeTree", false); - } - $options = [['name' => $this->range_tree->root_name, 'value' => 'root']]; - for($i = 0; $i < count($this->search_ranges); ++$i){ - $options[] = ['name' => $this->range_tree->tree_data[$this->search_ranges[$i]]['name'], 'value' => $this->search_ranges[$i]]; - } - } elseif ($name == "qs_choose"){ - foreach(StudipSemSearchHelper::GetQuickSearchFields() as $key => $value){ - $options[] = ['name' => $value, 'value' => $key]; - } - } - return $options; - } - - function getFormStart($action = "", $attributes = []) - { - return $this->form->getFormStart($action, $attributes); - } - - function getFormEnd() - { - $ret = ''; - if ($this->search_sem_class != 'all'){ - $ret = $this->form->getHiddenField('category',$this->search_sem_class); - } - return $ret . $this->form->getFormEnd(); - } - - function getHiddenField($name, $value = false){ - return $this->form->getHiddenField($name, $value); - } - - function getSearchButton($attributes = false, $tooltip = false){ - return $this->form->getFormButton('do_search', $attributes); - } - function getNewSearchButton($attributes = false, $tooltip = false){ - return $this->form->getFormButton('new_search', $attributes); - } - function getSemChangeButton($attributes = false, $tooltip = false){ - return $this->form->getFormButton('sem_change', $attributes); - } - - function doSearch(){ - $search_helper = new StudipSemSearchHelper($this->form, $this->visible_only); - $this->found_rows = $search_helper->doSearch(); - $this->search_result = $search_helper->getSearchResultAsSnapshot(); - return $this->found_rows; - } -} -?> diff --git a/lib/classes/StudipSemSearch.php b/lib/classes/StudipSemSearch.php new file mode 100644 index 0000000..8932dd1 --- /dev/null +++ b/lib/classes/StudipSemSearch.php @@ -0,0 +1,191 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* Class to build search formular and execute search +* +* +* +* @access public +* @author André Noack +* @package DBTools +**/ +class StudipSemSearch { + + var $form; + var $search_result; + var $form_name; + var $sem_tree; + var $range_tree; + var $search_done = false; + var $found_rows = false; + var $search_button_clicked = false; + var $new_search_button_clicked = false; + var $sem_change_button_clicked = false; + var $override_sem = false; + var $attributes_default = ['style' => 'width:100%;']; + var $search_scopes = []; + var $search_ranges = []; + var $search_sem_class = 'all'; + var $visible_only = false; + var $sem_dates; + + function __construct($form_name = "search_sem", $auto_search = true, $visible_only = false, $sem_class = 'all'){ + + $search_fields = ['title' => ['type' => 'text'], + 'sub_title' => ['type' => 'text'], + 'number' => ['type' => 'text'], + 'comment' => ['type' => 'text'], + 'lecturer' => ['type' => 'text'], + 'scope' => ['type' => 'text'], + 'quick_search' => ['type' => 'text'], + 'type' => ['type' => 'select', 'default_value' => 'all', 'max_length' => 35,'options_callback' => [$this, 'getSelectOptions']], + 'sem' => ['type' => 'select', 'default_value' => 'all','options_callback' => [$this, 'getSelectOptions']], + 'category' => ['type' => 'select', 'default_value' => 'all', 'max_length' => 50,'options_callback' => [$this, 'getSelectOptions']], + 'combination' => ['type' => 'select', 'default_value' => 'AND','options_callback' => [$this, 'getSelectOptions']], + 'scope_choose' => ['type' => 'select', 'default_value' => 'root', 'max_length' => 45,'options_callback' => [$this, 'getSelectOptions']], + 'range_choose' => ['type' => 'select', 'default_value' => 'root', 'max_length' => 45,'options_callback' => [$this, 'getSelectOptions']], + 'qs_choose' => ['type' => 'select', + 'default_value' => 'title_lecturer_number', + 'options_callback' => [$this, 'getSelectOptions'] + ] + ]; + $search_buttons = ['do_search' => ['caption' => _("Suchen"), 'info' => _("Suche starten")], + 'sem_change' => ['caption' => _('Auswählen'), 'info' => _("anderes Semester auswählen")], + 'new_search' => ['caption' => _('Neue Suche'), 'info' =>_("Neue Suche starten")]]; + //workaround: Qicksearch ändert den Namen des Eingabefeldes + if (Request::get("search_sem_quick_search_parameter")) { + Request::set('search_sem_quick_search', Request::get("search_sem_quick_search_parameter")); + } + $this->form = new StudipForm($search_fields, $search_buttons, $form_name , false); + $this->form_name = $form_name; + $this->sem_dates = Semester::findAllVisible(); + $this->visible_only = $visible_only; + $this->search_sem_class = $sem_class; + + if($this->form->isClicked('do_search') || ($this->form->isSended() && (!$this->form->isClicked('sem_change') || mb_strlen($this->form->getFormFieldValue('quick_search')) > 2))){ + $this->search_button_clicked = true; + if ($auto_search){ + $this->doSearch(); + $this->search_done = true; + } + } + + $this->new_search_button_clicked = $this->form->isClicked('new_search'); + $this->sem_change_button_clicked = $this->form->isClicked('do_search'); + + } + + function getSearchField($name,$attributes = false,$default = false){ + if (!$attributes){ + $attributes = $this->attributes_default; + } + return $this->form->getFormField($name,$attributes,$default); + } + + function getSelectOptions($caller, $name){ + $options = []; + if ($name == "combination"){ + $options = [['name' =>_("UND"),'value' => 'AND'],['name' => _("ODER"), 'value' => 'OR']]; + } elseif ($name == "sem"){ + $options = [['name' =>_("alle"),'value' => 'all']]; + for ($i = count($this->sem_dates) -1 ; $i >= 0; --$i){ + $options[] = ['name' => $this->sem_dates[$i]['name'], 'value' => $i]; + } + } elseif ($name == "type"){ + $options = [['name' =>_("alle"),'value' => 'all']]; + foreach($GLOBALS['SEM_TYPE'] as $type_key => $type_value){ + if($this->search_sem_class == 'all' || $type_value['class'] == $this->search_sem_class){ + $options[] = ['name' => $type_value['name'] . " (". $GLOBALS['SEM_CLASS'][$type_value['class']]['name'] .")", + 'value' => $type_key]; + } + } + } elseif ($name == "category"){ + $options = [['name' =>_("alle"),'value' => 'all']]; + foreach($GLOBALS['SEM_CLASS'] as $class_key => $class_value){ + $options[] = ['name' => $class_value['name'], + 'value' => $class_key]; + } + } elseif ($name == "scope_choose"){ + if(!is_object($this->sem_tree)){ + $this->sem_tree = TreeAbstract::GetInstance("StudipSemTree", false); + } + $options = [['name' => $this->sem_tree->root_name, 'value' => 'root']]; + for($i = 0; $i < count($this->search_scopes); ++$i){ + $options[] = ['name' => $this->sem_tree->tree_data[$this->search_scopes[$i]]['name'], 'value' => $this->search_scopes[$i]]; + } + } elseif ($name == "range_choose"){ + if(!is_object($this->range_tree)){ + $this->range_tree = TreeAbstract::GetInstance("StudipRangeTree", false); + } + $options = [['name' => $this->range_tree->root_name, 'value' => 'root']]; + for($i = 0; $i < count($this->search_ranges); ++$i){ + $options[] = ['name' => $this->range_tree->tree_data[$this->search_ranges[$i]]['name'], 'value' => $this->search_ranges[$i]]; + } + } elseif ($name == "qs_choose"){ + foreach(StudipSemSearchHelper::GetQuickSearchFields() as $key => $value){ + $options[] = ['name' => $value, 'value' => $key]; + } + } + return $options; + } + + function getFormStart($action = "", $attributes = []) + { + return $this->form->getFormStart($action, $attributes); + } + + function getFormEnd() + { + $ret = ''; + if ($this->search_sem_class != 'all'){ + $ret = $this->form->getHiddenField('category',$this->search_sem_class); + } + return $ret . $this->form->getFormEnd(); + } + + function getHiddenField($name, $value = false){ + return $this->form->getHiddenField($name, $value); + } + + function getSearchButton($attributes = false, $tooltip = false){ + return $this->form->getFormButton('do_search', $attributes); + } + function getNewSearchButton($attributes = false, $tooltip = false){ + return $this->form->getFormButton('new_search', $attributes); + } + function getSemChangeButton($attributes = false, $tooltip = false){ + return $this->form->getFormButton('sem_change', $attributes); + } + + function doSearch(){ + $search_helper = new StudipSemSearchHelper($this->form, $this->visible_only); + $this->found_rows = $search_helper->doSearch(); + $this->search_result = $search_helper->getSearchResultAsSnapshot(); + return $this->found_rows; + } +} +?> diff --git a/lib/classes/StudipSemSearchHelper.class.php b/lib/classes/StudipSemSearchHelper.class.php deleted file mode 100644 index 29ee5d3..0000000 --- a/lib/classes/StudipSemSearchHelper.class.php +++ /dev/null @@ -1,239 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -class StudipSemSearchHelper { - - public static function GetQuickSearchFields(){ - return [ 'all' =>_("alles"), - 'title_lecturer_number' => _("Titel") . ', ' . _("Lehrende") . ', ' . _("Nummer"), - 'title' => _("Titel"), - 'sub_title' => _("Untertitel"), - 'lecturer' => _("Lehrende"), - 'number' => _("Nummer"), - 'comment' => _("Kommentar"), - 'scope' => _("Bereich")]; - } - - private $search_result; - private $found_rows = false; - private $params = []; - private $visible_only; - - function __construct($form = null, $visible_only = null){ - $params = []; - if($form instanceof StudipForm){ - foreach($form->getFormFieldsByName(true) as $name){ - $params[$name] = $form->getFormFieldValue($name); - } - } - $this->setParams($params, $visible_only); - } - - public function setParams($params, $visible_only = null) - { - if(isset($params['quick_search']) && isset($params['qs_choose'])){ - if($params['qs_choose'] == 'all'){ - foreach (self::GetQuickSearchFields() as $key => $value){ - $params[$key] = $this->trim($params['quick_search']); - } - $params['combination'] = 'OR'; - } elseif($params['qs_choose'] == 'title_lecturer_number') { - foreach (explode('_', 'title_lecturer_number') as $key){ - $params[$key] = $this->trim($params['quick_search']); - } - $params['combination'] = 'OR'; - } else { - $params[$params['qs_choose']] = $this->trim($params['quick_search']); - } - } - if(!isset($params['combination'])) $params['combination'] = 'AND'; - $this->params = $params; - $this->visible_only = $visible_only; - } - - public function doSearch() - { - if (count($this->params) === 0) { - return false; - } - $this->params = array_map('addslashes', $this->params); - $clause = ""; - $and_clause = ""; - $this->search_result = new DbSnapshot(); - $combination = $this->params['combination']; - - $view = DbView::getView('sem_tree'); - - if (isset($this->params['sem']) && $this->params['sem'] !== 'all'){ - $sem_number = (int)$this->params['sem']; - $clause = " HAVING (sem_number <= $sem_number AND (sem_number_end >= $sem_number OR sem_number_end = -1)) "; - } - - $sem_types = []; - if (isset($this->params['category']) && $this->params['category'] !== 'all') { - foreach ($GLOBALS['SEM_TYPE'] as $type_key => $type_value) { - if ($type_value['class'] == $this->params['category']) { - $sem_types[] = $type_key; - } - } - } - - if (isset($this->params['type']) && $this->params['type'] != 'all'){ - $sem_types = [$this->params['type']]; - } - if ($sem_types) { - $clause = " AND c.status IN('" . join("','",$sem_types) . "') " . $clause; - } - - $view->params = []; - - if ($this->params['scope_choose'] && $this->params['scope_choose'] != 'root'){ - $sem_tree = TreeAbstract::GetInstance("StudipSemTree", false); - $view->params[0] = $sem_types ?: $sem_tree->sem_status; - $view->params[1] = $this->visible_only ? "c.visible=1" : "1"; - - $view->params[2] = $sem_tree->getKidsKids($this->params['scope_choose']); - $view->params[2][] = $this->params['scope_choose']; - $view->params[3] = $clause; - $snap = new DbSnapshot($view->get_query("view:SEM_TREE_GET_SEMIDS")); - if ($snap->numRows){ - $clause = " AND c.seminar_id IN('" . join("','",$snap->getRows("seminar_id")) ."')" . $clause; - } else { - return 0; - } - unset($snap); - } - - if ($this->params['range_choose'] && $this->params['range_choose'] != 'root'){ - $range_object = RangeTreeObject::GetInstance($this->params['range_choose']); - $view->params[0] = $range_object->getAllObjectKids(); - $view->params[0][] = $range_object->item_data['studip_object_id']; - $view->params[1] = ($this->visible_only ? " AND c.visible=1 " : ""); - $view->params[2] = $clause; - $snap = new DbSnapshot($view->get_query("view:SEM_INST_GET_SEM")); - if ($snap->numRows){ - $clause = " AND c.seminar_id IN('" . join("','",$snap->getRows("Seminar_id")) ."')" . $clause; - } else { - return 0; - } - unset($snap); - } - - - if (isset($this->params['lecturer']) && mb_strlen($this->params['lecturer']) > 2){ - $view->params[0] = "%" . $this->trim($this->params['lecturer']) . "%"; - $view->params[1] = "%" . $this->trim($this->params['lecturer']) . "%"; - $view->params[2] = "%" . $this->trim($this->params['lecturer']) . "%"; - $view->params[3] = "%" . $this->trim($this->params['lecturer']) . "%"; - $view->params[4] = "%" . $this->trim($this->params['lecturer']) . "%"; - $result = $view->get_query("view:SEM_SEARCH_LECTURER"); - - $lecturers = []; - while ($result->next_record()) { - $lecturers[] = $result->f('user_id'); - } - - if (count($lecturers)) { - $view->params[0] = $this->visible_only ? "c.visible=1" : "1"; - $view->params[1] = $lecturers; - $view->params[2] = $clause; - $snap = new DbSnapshot($view->get_query("view:SEM_SEARCH_LECTURER_ID")); - $this->search_result = $snap; - $this->found_rows = $this->search_result->numRows; - } - } - - - if ($combination == "AND" && $this->search_result->numRows){ - $and_clause = " AND c.seminar_id IN('" . join("','",$this->search_result->getRows("seminar_id")) ."')"; - } - - if ((isset($this->params['title']) && mb_strlen($this->params['title']) > 2) || - (isset($this->params['sub_title']) && mb_strlen($this->params['sub_title']) > 2) || - (isset($this->params['number']) && mb_strlen($this->params['number']) > 2) || - (isset($this->params['comment']) && mb_strlen($this->params['comment']) > 2)){ - - $toFilter = explode(" ", $this->params['title']); - $search_for = "(Name LIKE '%" . implode("%' AND Name LIKE '%", $toFilter) . "%')"; - if (!array_key_exists(0, $view->params)) { - $view->params[0] = ''; - } - $view->params[0] .= ($this->params['title']) ? $search_for . " " : " "; - $view->params[0] .= ($this->params['title'] && !empty($this->params['sub_title'])) ? $combination : " "; - $view->params[0] .= (!empty($this->params['sub_title'])) ? " Untertitel LIKE '%" . $this->trim($this->params['sub_title']) . "%' " : " "; - $view->params[0] .= (($this->params['title'] || !empty($this->params['sub_title'])) && !empty($this->params['comment'])) ? $combination : " "; - $view->params[0] .= (!empty($this->params['comment'])) ? " Beschreibung LIKE '%" . $this->trim($this->params['comment']) . "%' " : " "; - $view->params[0] .= (($this->params['title'] || !empty($this->params['sub_title']) || empty($this->params['comment'])) && $this->params['number']) ? $combination : " "; - $view->params[0] .= ($this->params['number']) ? " VeranstaltungsNummer LIKE '%" . $this->trim($this->params['number']) . "%' " : " "; - $view->params[0] = ($this->visible_only ? " c.visible=1 AND " : "") . "(" . $view->params[0] .")"; - $view->params[1] = $and_clause . $clause; - $snap = new DbSnapshot($view->get_query("view:SEM_SEARCH_SEM")); - if ($this->found_rows === false){ - $this->search_result = $snap; - } else { - $this->search_result->mergeSnapshot($snap,"seminar_id",$combination); - } - $this->found_rows = $this->search_result->numRows; - } - - if ($combination == "AND" && $this->search_result->numRows){ - $and_clause = " AND c.seminar_id IN('" . join("','",$this->search_result->getRows("seminar_id")) ."')"; - } - - if (isset($this->params['scope']) && mb_strlen($this->params['scope']) > 2){ - $view->params[0] = $this->visible_only ? "c.visible=1" : "1"; - $view->params[1] = "%" . $this->trim($this->params['scope']) . "%"; - $view->params[2] = $and_clause . $clause; - $snap = new DbSnapshot($view->get_query("view:SEM_TREE_SEARCH_SEM")); - if ($this->found_rows === false){ - $this->search_result = $snap; - } else { - $this->search_result->mergeSnapshot($snap,"seminar_id",$combination); - } - $this->found_rows = $this->search_result->numRows; - } - return $this->found_rows; - } - - public function getSearchResultAsSnapshot(){ - return $this->search_result; - } - - public function getSearchResultAsArray(){ - if($this->search_result instanceof DBSnapshot && $this->search_result->numRows){ - return array_unique($this->search_result->getRows('seminar_id')); - } else { - return []; - } - } - - private function trim($what) - { - $what = trim($what); - $what = preg_replace("/^\x{00A0}+|\x{00A0}+$/Su", '', $what); - return $what; - } -} diff --git a/lib/classes/StudipSemSearchHelper.php b/lib/classes/StudipSemSearchHelper.php new file mode 100644 index 0000000..29ee5d3 --- /dev/null +++ b/lib/classes/StudipSemSearchHelper.php @@ -0,0 +1,239 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +class StudipSemSearchHelper { + + public static function GetQuickSearchFields(){ + return [ 'all' =>_("alles"), + 'title_lecturer_number' => _("Titel") . ', ' . _("Lehrende") . ', ' . _("Nummer"), + 'title' => _("Titel"), + 'sub_title' => _("Untertitel"), + 'lecturer' => _("Lehrende"), + 'number' => _("Nummer"), + 'comment' => _("Kommentar"), + 'scope' => _("Bereich")]; + } + + private $search_result; + private $found_rows = false; + private $params = []; + private $visible_only; + + function __construct($form = null, $visible_only = null){ + $params = []; + if($form instanceof StudipForm){ + foreach($form->getFormFieldsByName(true) as $name){ + $params[$name] = $form->getFormFieldValue($name); + } + } + $this->setParams($params, $visible_only); + } + + public function setParams($params, $visible_only = null) + { + if(isset($params['quick_search']) && isset($params['qs_choose'])){ + if($params['qs_choose'] == 'all'){ + foreach (self::GetQuickSearchFields() as $key => $value){ + $params[$key] = $this->trim($params['quick_search']); + } + $params['combination'] = 'OR'; + } elseif($params['qs_choose'] == 'title_lecturer_number') { + foreach (explode('_', 'title_lecturer_number') as $key){ + $params[$key] = $this->trim($params['quick_search']); + } + $params['combination'] = 'OR'; + } else { + $params[$params['qs_choose']] = $this->trim($params['quick_search']); + } + } + if(!isset($params['combination'])) $params['combination'] = 'AND'; + $this->params = $params; + $this->visible_only = $visible_only; + } + + public function doSearch() + { + if (count($this->params) === 0) { + return false; + } + $this->params = array_map('addslashes', $this->params); + $clause = ""; + $and_clause = ""; + $this->search_result = new DbSnapshot(); + $combination = $this->params['combination']; + + $view = DbView::getView('sem_tree'); + + if (isset($this->params['sem']) && $this->params['sem'] !== 'all'){ + $sem_number = (int)$this->params['sem']; + $clause = " HAVING (sem_number <= $sem_number AND (sem_number_end >= $sem_number OR sem_number_end = -1)) "; + } + + $sem_types = []; + if (isset($this->params['category']) && $this->params['category'] !== 'all') { + foreach ($GLOBALS['SEM_TYPE'] as $type_key => $type_value) { + if ($type_value['class'] == $this->params['category']) { + $sem_types[] = $type_key; + } + } + } + + if (isset($this->params['type']) && $this->params['type'] != 'all'){ + $sem_types = [$this->params['type']]; + } + if ($sem_types) { + $clause = " AND c.status IN('" . join("','",$sem_types) . "') " . $clause; + } + + $view->params = []; + + if ($this->params['scope_choose'] && $this->params['scope_choose'] != 'root'){ + $sem_tree = TreeAbstract::GetInstance("StudipSemTree", false); + $view->params[0] = $sem_types ?: $sem_tree->sem_status; + $view->params[1] = $this->visible_only ? "c.visible=1" : "1"; + + $view->params[2] = $sem_tree->getKidsKids($this->params['scope_choose']); + $view->params[2][] = $this->params['scope_choose']; + $view->params[3] = $clause; + $snap = new DbSnapshot($view->get_query("view:SEM_TREE_GET_SEMIDS")); + if ($snap->numRows){ + $clause = " AND c.seminar_id IN('" . join("','",$snap->getRows("seminar_id")) ."')" . $clause; + } else { + return 0; + } + unset($snap); + } + + if ($this->params['range_choose'] && $this->params['range_choose'] != 'root'){ + $range_object = RangeTreeObject::GetInstance($this->params['range_choose']); + $view->params[0] = $range_object->getAllObjectKids(); + $view->params[0][] = $range_object->item_data['studip_object_id']; + $view->params[1] = ($this->visible_only ? " AND c.visible=1 " : ""); + $view->params[2] = $clause; + $snap = new DbSnapshot($view->get_query("view:SEM_INST_GET_SEM")); + if ($snap->numRows){ + $clause = " AND c.seminar_id IN('" . join("','",$snap->getRows("Seminar_id")) ."')" . $clause; + } else { + return 0; + } + unset($snap); + } + + + if (isset($this->params['lecturer']) && mb_strlen($this->params['lecturer']) > 2){ + $view->params[0] = "%" . $this->trim($this->params['lecturer']) . "%"; + $view->params[1] = "%" . $this->trim($this->params['lecturer']) . "%"; + $view->params[2] = "%" . $this->trim($this->params['lecturer']) . "%"; + $view->params[3] = "%" . $this->trim($this->params['lecturer']) . "%"; + $view->params[4] = "%" . $this->trim($this->params['lecturer']) . "%"; + $result = $view->get_query("view:SEM_SEARCH_LECTURER"); + + $lecturers = []; + while ($result->next_record()) { + $lecturers[] = $result->f('user_id'); + } + + if (count($lecturers)) { + $view->params[0] = $this->visible_only ? "c.visible=1" : "1"; + $view->params[1] = $lecturers; + $view->params[2] = $clause; + $snap = new DbSnapshot($view->get_query("view:SEM_SEARCH_LECTURER_ID")); + $this->search_result = $snap; + $this->found_rows = $this->search_result->numRows; + } + } + + + if ($combination == "AND" && $this->search_result->numRows){ + $and_clause = " AND c.seminar_id IN('" . join("','",$this->search_result->getRows("seminar_id")) ."')"; + } + + if ((isset($this->params['title']) && mb_strlen($this->params['title']) > 2) || + (isset($this->params['sub_title']) && mb_strlen($this->params['sub_title']) > 2) || + (isset($this->params['number']) && mb_strlen($this->params['number']) > 2) || + (isset($this->params['comment']) && mb_strlen($this->params['comment']) > 2)){ + + $toFilter = explode(" ", $this->params['title']); + $search_for = "(Name LIKE '%" . implode("%' AND Name LIKE '%", $toFilter) . "%')"; + if (!array_key_exists(0, $view->params)) { + $view->params[0] = ''; + } + $view->params[0] .= ($this->params['title']) ? $search_for . " " : " "; + $view->params[0] .= ($this->params['title'] && !empty($this->params['sub_title'])) ? $combination : " "; + $view->params[0] .= (!empty($this->params['sub_title'])) ? " Untertitel LIKE '%" . $this->trim($this->params['sub_title']) . "%' " : " "; + $view->params[0] .= (($this->params['title'] || !empty($this->params['sub_title'])) && !empty($this->params['comment'])) ? $combination : " "; + $view->params[0] .= (!empty($this->params['comment'])) ? " Beschreibung LIKE '%" . $this->trim($this->params['comment']) . "%' " : " "; + $view->params[0] .= (($this->params['title'] || !empty($this->params['sub_title']) || empty($this->params['comment'])) && $this->params['number']) ? $combination : " "; + $view->params[0] .= ($this->params['number']) ? " VeranstaltungsNummer LIKE '%" . $this->trim($this->params['number']) . "%' " : " "; + $view->params[0] = ($this->visible_only ? " c.visible=1 AND " : "") . "(" . $view->params[0] .")"; + $view->params[1] = $and_clause . $clause; + $snap = new DbSnapshot($view->get_query("view:SEM_SEARCH_SEM")); + if ($this->found_rows === false){ + $this->search_result = $snap; + } else { + $this->search_result->mergeSnapshot($snap,"seminar_id",$combination); + } + $this->found_rows = $this->search_result->numRows; + } + + if ($combination == "AND" && $this->search_result->numRows){ + $and_clause = " AND c.seminar_id IN('" . join("','",$this->search_result->getRows("seminar_id")) ."')"; + } + + if (isset($this->params['scope']) && mb_strlen($this->params['scope']) > 2){ + $view->params[0] = $this->visible_only ? "c.visible=1" : "1"; + $view->params[1] = "%" . $this->trim($this->params['scope']) . "%"; + $view->params[2] = $and_clause . $clause; + $snap = new DbSnapshot($view->get_query("view:SEM_TREE_SEARCH_SEM")); + if ($this->found_rows === false){ + $this->search_result = $snap; + } else { + $this->search_result->mergeSnapshot($snap,"seminar_id",$combination); + } + $this->found_rows = $this->search_result->numRows; + } + return $this->found_rows; + } + + public function getSearchResultAsSnapshot(){ + return $this->search_result; + } + + public function getSearchResultAsArray(){ + if($this->search_result instanceof DBSnapshot && $this->search_result->numRows){ + return array_unique($this->search_result->getRows('seminar_id')); + } else { + return []; + } + } + + private function trim($what) + { + $what = trim($what); + $what = preg_replace("/^\x{00A0}+|\x{00A0}+$/Su", '', $what); + return $what; + } +} diff --git a/lib/classes/StudipSemTree.class.php b/lib/classes/StudipSemTree.class.php deleted file mode 100644 index 70743ad..0000000 --- a/lib/classes/StudipSemTree.class.php +++ /dev/null @@ -1,312 +0,0 @@ - - * @license GPL2 or any later version - * @copyright 2003 André Noack , - * Suchi & Berg GmbH - */ -class StudipSemTree extends TreeAbstract -{ - public $sem_dates = []; - public $sem_number = null; - public $visible_only = false; - public $sem_status = []; - protected $entries_init_done = false; - - /** - * constructor - * - * do not use directly, call TreeAbstract::GetInstance("StudipRangeTree") - * @access private - */ - public function __construct($args) - { - DbView::addView('sem_tree'); - - $this->root_name = Config::get()->UNI_NAME_CLEAN; - if (isset($args['visible_only'])) { - $this->visible_only = (int) $args['visible_only']; - } - if (isset($args['sem_number']) ){ - $this->sem_number = array_map('intval', $args['sem_number']); - } - if (!empty($args['sem_status'])) { - $this->sem_status = array_map('intval', $args['sem_status']); - } else { - foreach ($GLOBALS['SEM_CLASS'] as $key => $value){ - if ($value['bereiche']){ - foreach ($GLOBALS['SEM_TYPE'] as $type_key => $type_value) { - if($type_value['class'] == $key) - $this->sem_status[] = $type_key; - } - } - } - } - - if (!count($this->sem_status)){ - $this->sem_status[] = -1; - } - - parent::__construct(); //calling the baseclass constructor - if (isset($args['build_index']) ){ - $this->buildIndex(); - } - - $this->sem_dates = Semester::findAllVisible(); - } - - /** - * initializes the tree - * - * stores all rows from table sem_tree in array $tree_data - * @access public - */ - public function init() - { - parent::init(); - - $db = $this->view->get_query("view:SEM_TREE_GET_DATA_NO_ENTRIES"); - - while ($db->next_record()){ - $this->tree_data[$db->f("sem_tree_id")] = ['type' => $db->f('type'), "info" => $db->f("info"), "entries" => 0]; - $name = $db->f("name"); - $this->storeItem($db->f("sem_tree_id"), $db->f("parent_id"), $name, $db->f("priority")); - } - } - - public function initEntries() - { - $this->view->params[0] = $this->sem_status; - $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; - $this->view->params[1] .= (isset($this->sem_number)) ? " AND ((" . $this->view->sem_number_sql - . ") IN (" . join(",",$this->sem_number) .") OR ((" . $this->view->sem_number_sql - .") <= " . $this->sem_number[count($this->sem_number)-1] - . " AND ((" . $this->view->sem_number_end_sql . ") >= " . $this->sem_number[count($this->sem_number)-1] - . " OR (" . $this->view->sem_number_end_sql . ") = -1))) " : ""; - - $db = $this->view->get_query("view:SEM_TREE_GET_ENTRIES"); - while ($db->next_record()){ - $this->tree_data[$db->f("sem_tree_id")]['entries'] = $db->f('entries'); - } - $this->entries_init_done = true; - } - - public function isModuleItem($item_id) - { - return isset($GLOBALS['SEM_TREE_TYPES'][$this->getValue($item_id, 'type')]['is_module']); - } - - public function isHiddenItem($item_id) - { - return !empty($GLOBALS['SEM_TREE_TYPES'][$this->getValue($item_id, 'type')]['hidden']); - } - - public function getSemIds($item_id,$ids_from_kids = false) - { - if (empty($this->tree_data[$item_id])) { - return false; - } - $this->view->params[0] = $this->sem_status; - $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; - if ($ids_from_kids && $item_id != 'root'){ - $this->view->params[2] = $this->getKidsKids($item_id); - } - $this->view->params[2][] = $item_id; - $this->view->params[3] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : ""; - $ret = false; - if ($item_id == 'root' && $ids_from_kids) { - unset($this->view->params[2]); - $this->view->params = array_values($this->view->params); - $rs = $this->view->get_query("view:SEM_TREE_GET_SEMIDS_ROOT"); - } else { - $rs = $this->view->get_query("view:SEM_TREE_GET_SEMIDS"); - } - while($rs->next_record()){ - $ret[] = $rs->f(0); - } - return $ret; - } - - public function getSemData($item_id,$sem_data_from_kids = false) - { - if (!$this->tree_data[$item_id]) - return false; - $this->view->params[0] = $this->sem_status; - $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; - if ($sem_data_from_kids && $item_id != 'root'){ - $this->view->params[2] = $this->getKidsKids($item_id); - } - $this->view->params[2][] = $item_id; - $this->view->params[3] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : ""; - if ($item_id == 'root' && $sem_data_from_kids) { - unset($this->view->params[2]); - $this->view->params = array_values($this->view->params); - $rs = $this->view->get_query("view:SEM_TREE_GET_SEMDATA_ROOT"); - } else { - $rs = $this->view->get_query("view:SEM_TREE_GET_SEMDATA"); - } - return new DbSnapshot($rs); - } - - public function getLonelySemData($item_id) - { - if (!$institut_id = $this->tree_data[$item_id]['studip_object_id']) - return false; - $this->view->params[0] = $this->sem_status; - $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; - $this->view->params[2] = $institut_id; - $this->view->params[3] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : ""; - return new DbSnapshot($this->view->get_query("view:SEM_TREE_GET_LONELY_SEM_DATA")); - } - - public function getNumEntries($item_id, $num_entries_from_kids = false) - { - if (empty($this->tree_data[$item_id])) { - return false; - } - - if (empty($this->entries_init_done)) { - $this->initEntries(); - } - - return parent::getNumEntries($item_id, $num_entries_from_kids); - /* - if (!$num_entries_from_kids){ - return $this->tree_data[$item_id]["entries"]; - } else { - $item_list = $this->getKidsKids($item_id); - $item_list[] = $item_id; - $ret = 0; - $num_items = count($item_list); - for ($i = 0; $i < $num_items; ++$i){ - $ret += $this->tree_data[$item_list[$i]]["entries"]; - } - return $ret; - } - */ - } - - public function getAdminRange($item_id) - { - if (!$this->tree_data[$item_id]) - return false; - if ($item_id == "root") - return "root"; - $ret_id = $item_id; - while ($ret_id != "root"){ - $ret_id = $this->tree_data[$ret_id]['parent_id']; - } - return $ret_id; - } - - public function InsertItem($item_id, $parent_id, $item_name, $item_info, $priority, $studip_object_id, $type) - { - $view = new DbView(); - $view->params = [$item_id,$parent_id,$item_name,$priority,$item_info,$studip_object_id, $type]; - $rs = $view->get_query("view:SEM_TREE_INS_ITEM"); - // Logging - StudipLog::log("STUDYAREA_ADD",$item_id); - NotificationCenter::postNotification("StudyAreaDidCreate", $item_id, $GLOBALS['user']->id); - - return $rs->affected_rows(); - } - - public function UpdateItem($item_id, $item_name, $item_info, $type) - { - $view = new DbView(); - $view->params = [$item_name,$item_info,$type,$item_id]; - $rs = $view->get_query("view:SEM_TREE_UPD_ITEM"); - NotificationCenter::postNotification("StudyAreaDidUpdate", $item_id, $GLOBALS['user']->id); - - return $rs->affected_rows(); - } - - public function DeleteItems($items_to_delete) - { - $view = new DbView(); - $view->params[0] = (is_array($items_to_delete)) ? $items_to_delete : [$items_to_delete]; - $view->auto_free_params = false; - $rs = $view->get_query("view:SEM_TREE_DEL_ITEM"); - $deleted['items'] = $rs->affected_rows(); - $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_RANGE"); - $deleted['entries'] = $rs->affected_rows(); - // Logging - foreach ($items_to_delete as $item_id) { - StudipLog::log("STUDYAREA_DELETE",$item_id); - NotificationCenter::postNotification("StudyAreaDidDelete", $item_id, $GLOBALS['user']->id); - } - return $deleted; - } - - public function DeleteSemEntries($item_ids = null, $sem_entries = null) - { - $view = new DbView(); - if ($item_ids && $sem_entries) { - $sem_tree_ids = $view->params[0] = (is_array($item_ids)) ? $item_ids : [$item_ids]; - $seminar_ids = $view->params[1] = (is_array($sem_entries)) ? $sem_entries : [$sem_entries]; - $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_SEM_RANGE"); - $ret = $rs->affected_rows(); - // Logging - foreach ($sem_tree_ids as $range) { - foreach ($seminar_ids as $sem) { - StudipLog::log("SEM_DELETE_STUDYAREA",$sem,$range); - } - } - if($ret){ - foreach ($sem_tree_ids as $sem_tree_id){ - $studyarea = StudipStudyArea::find($sem_tree_id); - if($studyarea->isModule()) { - foreach ($seminar_ids as $seminar_id) { - NotificationCenter::postNotification('CourseRemovedFromModule', $studyarea, ['module_id' => $sem_tree_id, 'course_id' => $seminar_id]); - } - } - } - } - } elseif ($item_ids){ - $view->params[0] = (is_array($item_ids)) ? $item_ids : [$item_ids]; - // Logging - foreach ($view->params[0] as $range) { - StudipLog::log("SEM_DELETE_STUDYAREA","all",$range); - } - $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_RANGE"); - $ret = $rs->affected_rows(); - } elseif ($sem_entries){ - $view->params[0] = (is_array($sem_entries)) ? $sem_entries : [$sem_entries]; - // Logging - foreach ($view->params[0] as $sem) { - StudipLog::log("SEM_DELETE_STUDYAREA",$sem,"all"); - } - $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_SEMID_RANGE"); - $ret = $rs->affected_rows(); - } else { - $ret = false; - } - - return $ret; - } - - public function InsertSemEntry($sem_tree_id, $seminar_id) - { - $view = new DbView(); - $view->params[0] = $seminar_id; - $view->params[1] = $sem_tree_id; - $rs = $view->get_query("view:SEMINAR_SEM_TREE_INS_ITEM"); - if($ret = $rs->affected_rows()){ - // Logging - StudipLog::log("SEM_ADD_STUDYAREA",$seminar_id,$sem_tree_id); - $studyarea = StudipStudyArea::find($sem_tree_id); - if($studyarea->isModule()){ - NotificationCenter::postNotification('CourseAddedToModule', $studyarea, ['module_id' => $sem_tree_id, 'course_id' => $seminar_id]); - } - } - return $ret; - } -} diff --git a/lib/classes/StudipSemTree.php b/lib/classes/StudipSemTree.php new file mode 100644 index 0000000..70743ad --- /dev/null +++ b/lib/classes/StudipSemTree.php @@ -0,0 +1,312 @@ + + * @license GPL2 or any later version + * @copyright 2003 André Noack , + * Suchi & Berg GmbH + */ +class StudipSemTree extends TreeAbstract +{ + public $sem_dates = []; + public $sem_number = null; + public $visible_only = false; + public $sem_status = []; + protected $entries_init_done = false; + + /** + * constructor + * + * do not use directly, call TreeAbstract::GetInstance("StudipRangeTree") + * @access private + */ + public function __construct($args) + { + DbView::addView('sem_tree'); + + $this->root_name = Config::get()->UNI_NAME_CLEAN; + if (isset($args['visible_only'])) { + $this->visible_only = (int) $args['visible_only']; + } + if (isset($args['sem_number']) ){ + $this->sem_number = array_map('intval', $args['sem_number']); + } + if (!empty($args['sem_status'])) { + $this->sem_status = array_map('intval', $args['sem_status']); + } else { + foreach ($GLOBALS['SEM_CLASS'] as $key => $value){ + if ($value['bereiche']){ + foreach ($GLOBALS['SEM_TYPE'] as $type_key => $type_value) { + if($type_value['class'] == $key) + $this->sem_status[] = $type_key; + } + } + } + } + + if (!count($this->sem_status)){ + $this->sem_status[] = -1; + } + + parent::__construct(); //calling the baseclass constructor + if (isset($args['build_index']) ){ + $this->buildIndex(); + } + + $this->sem_dates = Semester::findAllVisible(); + } + + /** + * initializes the tree + * + * stores all rows from table sem_tree in array $tree_data + * @access public + */ + public function init() + { + parent::init(); + + $db = $this->view->get_query("view:SEM_TREE_GET_DATA_NO_ENTRIES"); + + while ($db->next_record()){ + $this->tree_data[$db->f("sem_tree_id")] = ['type' => $db->f('type'), "info" => $db->f("info"), "entries" => 0]; + $name = $db->f("name"); + $this->storeItem($db->f("sem_tree_id"), $db->f("parent_id"), $name, $db->f("priority")); + } + } + + public function initEntries() + { + $this->view->params[0] = $this->sem_status; + $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; + $this->view->params[1] .= (isset($this->sem_number)) ? " AND ((" . $this->view->sem_number_sql + . ") IN (" . join(",",$this->sem_number) .") OR ((" . $this->view->sem_number_sql + .") <= " . $this->sem_number[count($this->sem_number)-1] + . " AND ((" . $this->view->sem_number_end_sql . ") >= " . $this->sem_number[count($this->sem_number)-1] + . " OR (" . $this->view->sem_number_end_sql . ") = -1))) " : ""; + + $db = $this->view->get_query("view:SEM_TREE_GET_ENTRIES"); + while ($db->next_record()){ + $this->tree_data[$db->f("sem_tree_id")]['entries'] = $db->f('entries'); + } + $this->entries_init_done = true; + } + + public function isModuleItem($item_id) + { + return isset($GLOBALS['SEM_TREE_TYPES'][$this->getValue($item_id, 'type')]['is_module']); + } + + public function isHiddenItem($item_id) + { + return !empty($GLOBALS['SEM_TREE_TYPES'][$this->getValue($item_id, 'type')]['hidden']); + } + + public function getSemIds($item_id,$ids_from_kids = false) + { + if (empty($this->tree_data[$item_id])) { + return false; + } + $this->view->params[0] = $this->sem_status; + $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; + if ($ids_from_kids && $item_id != 'root'){ + $this->view->params[2] = $this->getKidsKids($item_id); + } + $this->view->params[2][] = $item_id; + $this->view->params[3] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : ""; + $ret = false; + if ($item_id == 'root' && $ids_from_kids) { + unset($this->view->params[2]); + $this->view->params = array_values($this->view->params); + $rs = $this->view->get_query("view:SEM_TREE_GET_SEMIDS_ROOT"); + } else { + $rs = $this->view->get_query("view:SEM_TREE_GET_SEMIDS"); + } + while($rs->next_record()){ + $ret[] = $rs->f(0); + } + return $ret; + } + + public function getSemData($item_id,$sem_data_from_kids = false) + { + if (!$this->tree_data[$item_id]) + return false; + $this->view->params[0] = $this->sem_status; + $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; + if ($sem_data_from_kids && $item_id != 'root'){ + $this->view->params[2] = $this->getKidsKids($item_id); + } + $this->view->params[2][] = $item_id; + $this->view->params[3] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : ""; + if ($item_id == 'root' && $sem_data_from_kids) { + unset($this->view->params[2]); + $this->view->params = array_values($this->view->params); + $rs = $this->view->get_query("view:SEM_TREE_GET_SEMDATA_ROOT"); + } else { + $rs = $this->view->get_query("view:SEM_TREE_GET_SEMDATA"); + } + return new DbSnapshot($rs); + } + + public function getLonelySemData($item_id) + { + if (!$institut_id = $this->tree_data[$item_id]['studip_object_id']) + return false; + $this->view->params[0] = $this->sem_status; + $this->view->params[1] = $this->visible_only ? "visible=1" : "1"; + $this->view->params[2] = $institut_id; + $this->view->params[3] = (isset($this->sem_number)) ? " HAVING sem_number IN (" . join(",",$this->sem_number) .") OR (sem_number <= " . $this->sem_number[count($this->sem_number)-1] . " AND (sem_number_end >= " . $this->sem_number[count($this->sem_number)-1] . " OR sem_number_end = -1)) " : ""; + return new DbSnapshot($this->view->get_query("view:SEM_TREE_GET_LONELY_SEM_DATA")); + } + + public function getNumEntries($item_id, $num_entries_from_kids = false) + { + if (empty($this->tree_data[$item_id])) { + return false; + } + + if (empty($this->entries_init_done)) { + $this->initEntries(); + } + + return parent::getNumEntries($item_id, $num_entries_from_kids); + /* + if (!$num_entries_from_kids){ + return $this->tree_data[$item_id]["entries"]; + } else { + $item_list = $this->getKidsKids($item_id); + $item_list[] = $item_id; + $ret = 0; + $num_items = count($item_list); + for ($i = 0; $i < $num_items; ++$i){ + $ret += $this->tree_data[$item_list[$i]]["entries"]; + } + return $ret; + } + */ + } + + public function getAdminRange($item_id) + { + if (!$this->tree_data[$item_id]) + return false; + if ($item_id == "root") + return "root"; + $ret_id = $item_id; + while ($ret_id != "root"){ + $ret_id = $this->tree_data[$ret_id]['parent_id']; + } + return $ret_id; + } + + public function InsertItem($item_id, $parent_id, $item_name, $item_info, $priority, $studip_object_id, $type) + { + $view = new DbView(); + $view->params = [$item_id,$parent_id,$item_name,$priority,$item_info,$studip_object_id, $type]; + $rs = $view->get_query("view:SEM_TREE_INS_ITEM"); + // Logging + StudipLog::log("STUDYAREA_ADD",$item_id); + NotificationCenter::postNotification("StudyAreaDidCreate", $item_id, $GLOBALS['user']->id); + + return $rs->affected_rows(); + } + + public function UpdateItem($item_id, $item_name, $item_info, $type) + { + $view = new DbView(); + $view->params = [$item_name,$item_info,$type,$item_id]; + $rs = $view->get_query("view:SEM_TREE_UPD_ITEM"); + NotificationCenter::postNotification("StudyAreaDidUpdate", $item_id, $GLOBALS['user']->id); + + return $rs->affected_rows(); + } + + public function DeleteItems($items_to_delete) + { + $view = new DbView(); + $view->params[0] = (is_array($items_to_delete)) ? $items_to_delete : [$items_to_delete]; + $view->auto_free_params = false; + $rs = $view->get_query("view:SEM_TREE_DEL_ITEM"); + $deleted['items'] = $rs->affected_rows(); + $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_RANGE"); + $deleted['entries'] = $rs->affected_rows(); + // Logging + foreach ($items_to_delete as $item_id) { + StudipLog::log("STUDYAREA_DELETE",$item_id); + NotificationCenter::postNotification("StudyAreaDidDelete", $item_id, $GLOBALS['user']->id); + } + return $deleted; + } + + public function DeleteSemEntries($item_ids = null, $sem_entries = null) + { + $view = new DbView(); + if ($item_ids && $sem_entries) { + $sem_tree_ids = $view->params[0] = (is_array($item_ids)) ? $item_ids : [$item_ids]; + $seminar_ids = $view->params[1] = (is_array($sem_entries)) ? $sem_entries : [$sem_entries]; + $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_SEM_RANGE"); + $ret = $rs->affected_rows(); + // Logging + foreach ($sem_tree_ids as $range) { + foreach ($seminar_ids as $sem) { + StudipLog::log("SEM_DELETE_STUDYAREA",$sem,$range); + } + } + if($ret){ + foreach ($sem_tree_ids as $sem_tree_id){ + $studyarea = StudipStudyArea::find($sem_tree_id); + if($studyarea->isModule()) { + foreach ($seminar_ids as $seminar_id) { + NotificationCenter::postNotification('CourseRemovedFromModule', $studyarea, ['module_id' => $sem_tree_id, 'course_id' => $seminar_id]); + } + } + } + } + } elseif ($item_ids){ + $view->params[0] = (is_array($item_ids)) ? $item_ids : [$item_ids]; + // Logging + foreach ($view->params[0] as $range) { + StudipLog::log("SEM_DELETE_STUDYAREA","all",$range); + } + $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_RANGE"); + $ret = $rs->affected_rows(); + } elseif ($sem_entries){ + $view->params[0] = (is_array($sem_entries)) ? $sem_entries : [$sem_entries]; + // Logging + foreach ($view->params[0] as $sem) { + StudipLog::log("SEM_DELETE_STUDYAREA",$sem,"all"); + } + $rs = $view->get_query("view:SEMINAR_SEM_TREE_DEL_SEMID_RANGE"); + $ret = $rs->affected_rows(); + } else { + $ret = false; + } + + return $ret; + } + + public function InsertSemEntry($sem_tree_id, $seminar_id) + { + $view = new DbView(); + $view->params[0] = $seminar_id; + $view->params[1] = $sem_tree_id; + $rs = $view->get_query("view:SEMINAR_SEM_TREE_INS_ITEM"); + if($ret = $rs->affected_rows()){ + // Logging + StudipLog::log("SEM_ADD_STUDYAREA",$seminar_id,$sem_tree_id); + $studyarea = StudipStudyArea::find($sem_tree_id); + if($studyarea->isModule()){ + NotificationCenter::postNotification('CourseAddedToModule', $studyarea, ['module_id' => $sem_tree_id, 'course_id' => $seminar_id]); + } + } + return $ret; + } +} diff --git a/lib/classes/StudipSemTreeSearch.class.php b/lib/classes/StudipSemTreeSearch.class.php deleted file mode 100644 index e79a34f..0000000 --- a/lib/classes/StudipSemTreeSearch.class.php +++ /dev/null @@ -1,241 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** -* Class to build search formular and execute search -* -* -* -* @access public -* @author André Noack -* @package DBTools -**/ -class StudipSemTreeSearch { - - var $view; - var $num_search_result = false; - var $num_inserted; - var $num_deleted; - var $form_name; - var $tree; - var $seminar_id; - var $institut_id = []; - var $sem_tree_ranges = []; - var $sem_tree_ids = []; - var $selected = []; - var $search_result = []; - var $search_done = false; - - function __construct($seminar_id,$form_name = "search_sem_tree", $auto_search = true){ - $this->view = DbView::getView('sem_tree'); - $this->form_name = $form_name; - $this->tree = TreeAbstract::GetInstance("StudipSemTree", false); - $this->seminar_id = $seminar_id; - $this->view->params[0] = $seminar_id; - $rs = $this->view->get_query("view:SEM_GET_INST"); - while($rs->next_record()){ - $this->institut_id[] = $rs->f(0); - } - $this->init(); - if($auto_search){ - $this->doSearch(); - } - } - - function init(){ - $this->sem_tree_ranges = []; - $this->sem_tree_ids = []; - $this->selected = []; - $this->view->params[0] = $this->seminar_id; - $rs = $this->view->get_query("view:SEMINAR_SEM_TREE_GET_IDS"); - while($rs->next_record()){ - if (!$this->tree->hasKids($rs->f("sem_tree_id"))){ - $this->sem_tree_ranges[$rs->f("parent_id")][] = $rs->f("sem_tree_id"); - $this->sem_tree_ids[] = $rs->f("sem_tree_id"); - $this->selected[$rs->f("sem_tree_id")] = true; - } - } - } - /* fuzzy !!! - function getExpectedRanges(){ - $this->view->params[0] = $this->institut_id; - $this->view->params[1] = $this->sem_tree_ids; - $rs = $this->view->get_query("view:SEMINAR_SEM_TREE_GET_EXP_IDS"); - while ($rs->next_record()){ - if (!$this->tree->hasKids($rs->f("sem_tree_id"))){ - $this->sem_tree_ranges[$rs->f("parent_id")][] = $rs->f("sem_tree_id"); - $this->sem_tree_ids[] = $rs->f("sem_tree_id"); - } - } - } - */ - - //not fuzzy - function getExpectedRanges(){ - $this->view->params[0] = $this->institut_id; - $rs = $this->view->get_query("view:SEM_TREE_GET_FAK"); - while($rs->next_record()){ - $the_kids = $this->tree->getKidsKids($rs->f("sem_tree_id")); - $the_kids[] = $rs->f("sem_tree_id"); - for ($i = 0; $i < count($the_kids); ++$i){ - if (!$this->tree->hasKids($the_kids[$i]) && !in_array($the_kids[$i],$this->sem_tree_ids)){ - $this->sem_tree_ranges[$this->tree->tree_data[$the_kids[$i]]['parent_id']][] = $the_kids[$i]; - $this->sem_tree_ids[] = $the_kids[$i]; - } - } - } - } - - function prepRangePath($path, $cols) { - $parts=explode(">",$path); - $paths=[]; - $currpath=""; - foreach ($parts as $part) { - if (mb_strlen($part)>$cols) { - $p=my_substr($part, 0, $cols); - } else { - $p = $part; - } - if (mb_strlen($currpath)+mb_strlen($p)+3 > $cols) { - $paths[]=htmlReady($currpath); - $currpath=" >> " . $p; - } else { - if (count($paths)==0 && mb_strlen($currpath)==0) { - $currpath.=$p; - } else { - $currpath.=" > ".$p; - } - } - } - $paths[]=htmlReady($currpath); - return $paths; - } - - function getChooserField($attributes = [], $cols = 70, $field_name = 'chooser'){ - if ($this->institut_id){ - $this->getExpectedRanges(); - } - $element_name = "{$this->form_name}_{$field_name}[]"; - $ret = "\n
$value){ - $ret .= " " . $key . "=\"" . htmlReady($value) . "\""; - } - $ret .= ">"; - foreach ($this->sem_tree_ranges as $range_id => $sem_tree_id){ - $paths=$this->prepRangePath($this->getPath($range_id), $cols); - foreach ($paths as $p) { - $ret .= "\n
" . $p ."
"; - } - $ret .= "\n
" . str_repeat("¯",$cols) . "
"; - for ($i = 0; $i < count($sem_tree_id); ++$i){ - $id = $this->form_name . '_' . $field_name . '_' . $sem_tree_id[$i]; - $ret .= "\n
"; - $ret .= "\n"; - $ret .= "\n
"; - } - } - $ret .= "
"; - return $ret; - } - - function getPath($item_id,$delimeter = ">"){ - return $this->tree->getShortPath($item_id); - } - - - function getSearchField($attributes = []){ - $ret = "\nform_name}_search_field\""; - foreach ($attributes as $key => $value){ - $ret .= " " . $key . "=\"" . htmlReady($value) ."\""; - } - $ret .= ">"; - return $ret; - } - - function getSearchButton($attributes = []) - { - $ret = Icon::create('search', 'clickable', ['title' => _('Suche nach Studienbereichen starten')]) - ->asInput(["type" => "image", "class" => "middle", "name" => $this->form_name . "_do_search"]); - - return $ret; - } - - function getFormStart($action = ""){ - if (!$action){ - $action = URLHelper::getLink(); - } - $ret = "\n
form_name}\">"; - $ret .= CSRFProtection::tokenTag(); - return $ret; - } - - function getFormEnd(){ - - return "\nform_name}_send\" value=\"1\">\n
"; - } - - function doSearch(){ - if (Request::submitted($this->form_name . "_do_search") || Request::submitted($this->form_name . "_send")){ - if(mb_strlen($_REQUEST[$this->form_name . "_search_field"]) > 2){ - $this->view->params[0] = "%" . Request::quoted($this->form_name . "_search_field") . "%"; - $this->view->params[1] = $this->sem_tree_ids; - $rs = $this->view->get_query("view:SEM_TREE_SEARCH_ITEM"); - while($rs->next_record()){ - $this->sem_tree_ranges[$rs->f("parent_id")][] = $rs->f("sem_tree_id"); - $this->sem_tree_ids[] = $rs->f("sem_tree_id"); - $this->search_result[$rs->f("sem_tree_id")] = true; - } - $this->num_search_result = $rs->num_rows(); - } - $this->search_done = true; - } - return; - } - - public function insertSelectedRanges($selected = null) - { - if (!$selected){ - $selected = array_filter(Request::quotedArray("{$this->form_name}_chooser")); - } - - if (is_array($selected)){ - $count_intersect = count(array_intersect($selected,array_keys($this->selected))); - if (count($this->selected) != $count_intersect || count($selected) != $count_intersect){ - $count_del = (count($this->selected)) ? $this->tree->DeleteSemEntries(array_keys($this->selected),$this->seminar_id) : 0; - for ($i = 0; $i < count($selected); ++$i){ - $new_selected[$selected[$i]] = true; - $count_ins += $this->tree->InsertSemEntry($selected[$i], $this->seminar_id); - } - $this->num_inserted = $count_ins - $count_intersect; - $this->num_deleted = $count_del - $count_intersect; - $this->selected = $new_selected; - } - } - } -} diff --git a/lib/classes/StudipSemTreeSearch.php b/lib/classes/StudipSemTreeSearch.php new file mode 100644 index 0000000..e79a34f --- /dev/null +++ b/lib/classes/StudipSemTreeSearch.php @@ -0,0 +1,241 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* Class to build search formular and execute search +* +* +* +* @access public +* @author André Noack +* @package DBTools +**/ +class StudipSemTreeSearch { + + var $view; + var $num_search_result = false; + var $num_inserted; + var $num_deleted; + var $form_name; + var $tree; + var $seminar_id; + var $institut_id = []; + var $sem_tree_ranges = []; + var $sem_tree_ids = []; + var $selected = []; + var $search_result = []; + var $search_done = false; + + function __construct($seminar_id,$form_name = "search_sem_tree", $auto_search = true){ + $this->view = DbView::getView('sem_tree'); + $this->form_name = $form_name; + $this->tree = TreeAbstract::GetInstance("StudipSemTree", false); + $this->seminar_id = $seminar_id; + $this->view->params[0] = $seminar_id; + $rs = $this->view->get_query("view:SEM_GET_INST"); + while($rs->next_record()){ + $this->institut_id[] = $rs->f(0); + } + $this->init(); + if($auto_search){ + $this->doSearch(); + } + } + + function init(){ + $this->sem_tree_ranges = []; + $this->sem_tree_ids = []; + $this->selected = []; + $this->view->params[0] = $this->seminar_id; + $rs = $this->view->get_query("view:SEMINAR_SEM_TREE_GET_IDS"); + while($rs->next_record()){ + if (!$this->tree->hasKids($rs->f("sem_tree_id"))){ + $this->sem_tree_ranges[$rs->f("parent_id")][] = $rs->f("sem_tree_id"); + $this->sem_tree_ids[] = $rs->f("sem_tree_id"); + $this->selected[$rs->f("sem_tree_id")] = true; + } + } + } + /* fuzzy !!! + function getExpectedRanges(){ + $this->view->params[0] = $this->institut_id; + $this->view->params[1] = $this->sem_tree_ids; + $rs = $this->view->get_query("view:SEMINAR_SEM_TREE_GET_EXP_IDS"); + while ($rs->next_record()){ + if (!$this->tree->hasKids($rs->f("sem_tree_id"))){ + $this->sem_tree_ranges[$rs->f("parent_id")][] = $rs->f("sem_tree_id"); + $this->sem_tree_ids[] = $rs->f("sem_tree_id"); + } + } + } + */ + + //not fuzzy + function getExpectedRanges(){ + $this->view->params[0] = $this->institut_id; + $rs = $this->view->get_query("view:SEM_TREE_GET_FAK"); + while($rs->next_record()){ + $the_kids = $this->tree->getKidsKids($rs->f("sem_tree_id")); + $the_kids[] = $rs->f("sem_tree_id"); + for ($i = 0; $i < count($the_kids); ++$i){ + if (!$this->tree->hasKids($the_kids[$i]) && !in_array($the_kids[$i],$this->sem_tree_ids)){ + $this->sem_tree_ranges[$this->tree->tree_data[$the_kids[$i]]['parent_id']][] = $the_kids[$i]; + $this->sem_tree_ids[] = $the_kids[$i]; + } + } + } + } + + function prepRangePath($path, $cols) { + $parts=explode(">",$path); + $paths=[]; + $currpath=""; + foreach ($parts as $part) { + if (mb_strlen($part)>$cols) { + $p=my_substr($part, 0, $cols); + } else { + $p = $part; + } + if (mb_strlen($currpath)+mb_strlen($p)+3 > $cols) { + $paths[]=htmlReady($currpath); + $currpath=" >> " . $p; + } else { + if (count($paths)==0 && mb_strlen($currpath)==0) { + $currpath.=$p; + } else { + $currpath.=" > ".$p; + } + } + } + $paths[]=htmlReady($currpath); + return $paths; + } + + function getChooserField($attributes = [], $cols = 70, $field_name = 'chooser'){ + if ($this->institut_id){ + $this->getExpectedRanges(); + } + $element_name = "{$this->form_name}_{$field_name}[]"; + $ret = "\n
$value){ + $ret .= " " . $key . "=\"" . htmlReady($value) . "\""; + } + $ret .= ">"; + foreach ($this->sem_tree_ranges as $range_id => $sem_tree_id){ + $paths=$this->prepRangePath($this->getPath($range_id), $cols); + foreach ($paths as $p) { + $ret .= "\n
" . $p ."
"; + } + $ret .= "\n
" . str_repeat("¯",$cols) . "
"; + for ($i = 0; $i < count($sem_tree_id); ++$i){ + $id = $this->form_name . '_' . $field_name . '_' . $sem_tree_id[$i]; + $ret .= "\n
"; + $ret .= "\n"; + $ret .= "\n
"; + } + } + $ret .= "
"; + return $ret; + } + + function getPath($item_id,$delimeter = ">"){ + return $this->tree->getShortPath($item_id); + } + + + function getSearchField($attributes = []){ + $ret = "\nform_name}_search_field\""; + foreach ($attributes as $key => $value){ + $ret .= " " . $key . "=\"" . htmlReady($value) ."\""; + } + $ret .= ">"; + return $ret; + } + + function getSearchButton($attributes = []) + { + $ret = Icon::create('search', 'clickable', ['title' => _('Suche nach Studienbereichen starten')]) + ->asInput(["type" => "image", "class" => "middle", "name" => $this->form_name . "_do_search"]); + + return $ret; + } + + function getFormStart($action = ""){ + if (!$action){ + $action = URLHelper::getLink(); + } + $ret = "\n
form_name}\">"; + $ret .= CSRFProtection::tokenTag(); + return $ret; + } + + function getFormEnd(){ + + return "\nform_name}_send\" value=\"1\">\n
"; + } + + function doSearch(){ + if (Request::submitted($this->form_name . "_do_search") || Request::submitted($this->form_name . "_send")){ + if(mb_strlen($_REQUEST[$this->form_name . "_search_field"]) > 2){ + $this->view->params[0] = "%" . Request::quoted($this->form_name . "_search_field") . "%"; + $this->view->params[1] = $this->sem_tree_ids; + $rs = $this->view->get_query("view:SEM_TREE_SEARCH_ITEM"); + while($rs->next_record()){ + $this->sem_tree_ranges[$rs->f("parent_id")][] = $rs->f("sem_tree_id"); + $this->sem_tree_ids[] = $rs->f("sem_tree_id"); + $this->search_result[$rs->f("sem_tree_id")] = true; + } + $this->num_search_result = $rs->num_rows(); + } + $this->search_done = true; + } + return; + } + + public function insertSelectedRanges($selected = null) + { + if (!$selected){ + $selected = array_filter(Request::quotedArray("{$this->form_name}_chooser")); + } + + if (is_array($selected)){ + $count_intersect = count(array_intersect($selected,array_keys($this->selected))); + if (count($this->selected) != $count_intersect || count($selected) != $count_intersect){ + $count_del = (count($this->selected)) ? $this->tree->DeleteSemEntries(array_keys($this->selected),$this->seminar_id) : 0; + for ($i = 0; $i < count($selected); ++$i){ + $new_selected[$selected[$i]] = true; + $count_ins += $this->tree->InsertSemEntry($selected[$i], $this->seminar_id); + } + $this->num_inserted = $count_ins - $count_intersect; + $this->num_deleted = $count_del - $count_intersect; + $this->selected = $new_selected; + } + } + } +} diff --git a/lib/classes/StudipSemTreeView.class.php b/lib/classes/StudipSemTreeView.class.php deleted file mode 100644 index 3e984cc..0000000 --- a/lib/classes/StudipSemTreeView.class.php +++ /dev/null @@ -1,215 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - - -/** -* class to print out the seminar tree -* -* This class prints out a html representation of the whole or part of the tree -* -* @access public -* @author André Noack -* @package -*/ -class StudipSemTreeView extends TreeView { - - /** - * constructor - * - * @access public - */ - function __construct($start_item_id = "root", $sem_number = null){ - $this->start_item_id = ($start_item_id) ? $start_item_id : "root"; - $this->root_content = $GLOBALS['UNI_INFO']; - $args = null; - if ($sem_number){ - $args = ['sem_number' => $sem_number]; - } - parent::__construct("StudipSemTree", $args); //calling the baseclass constructor - } - - /** - * manages the session variables used for the open/close thing - * - * @access private - */ - function handleOpenRanges(){ - global $_REQUEST; - - $this->open_ranges[$this->start_item_id] = true; - if (Request::option('close_item') || Request::option('open_item')){ - $toggle_item = (Request::option('close_item')) ? Request::option('close_item') : Request::option('open_item'); - if (!$this->open_items[$toggle_item]){ - $this->open_items[$toggle_item] = true; - } else { - unset($this->open_items[$toggle_item]); - } - - if($this->tree->hasKids(Request::option('open_item'))){ - $this->start_item_id = Request::option('open_item'); - $this->open_ranges = null; - $this->open_items = null; - $this->open_items[Request::option('open_item')] = true; - $this->open_ranges[Request::option('open_item')] = true; - } - - $this->anchor = $toggle_item; - } - - if ($this->start_item_id == "root"){ - $this->open_ranges = null; - $this->open_ranges[$this->start_item_id] = true; - } - } - - function showSemTree(){ - echo "\n"; - if ($this->start_item_id != 'root'){ - echo "\n"; - } - echo "\n
" . $this->getSemPath() - . Assets::img('forumleer.gif', ['size' => '1@20']) . "
"; - $this->showTree($this->start_item_id); - echo "\n
"; - } - - function getSemPath(){ - //$ret = "" .htmlReady($this->tree->root_name) . ""; - if ($parents = $this->tree->getParents($this->start_item_id)){ - for($i = count($parents)-1; $i >= 0; --$i){ - $ret .= " > getSelf("start_item_id={$parents[$i]}&open_item={$parents[$i]}",false)) - . "\">" .htmlReady($this->tree->tree_data[$parents[$i]]["name"]) . ""; - } - } - return $ret; - } - - /** - * returns html for the icons in front of the name of the item - * - * @access private - * @param string $item_id - * @return string - */ - function getItemHeadPics($item_id){ - $head = ""; - $head .= "open_items[$item_id])? URLHelper::getLink($this->getSelf("close_item={$item_id}")) . "\"" . tooltip(_("Dieses Element schließen"),true) . ">" - : URLHelper::getLink($this->getSelf("open_item={$item_id}")) . "\"" . tooltip(_("Dieses Element öffnen"),true) . ">"; - $head .= Icon::create($this->open_items[$item_id] ? 'arr_1down' : 'arr_1right', 'clickable'); - $head .= (!$this->open_items[$item_id]) ? Assets::img('forumleer.gif', ['size' => '5']) : ""; - $head .= ""; - if ($this->tree->hasKids($item_id)){ - $head .= Icon::create('folder-full', 'clickable', ['title' => $this->open_ranges[$item_id]?_('Alle Unterelemente schliessen'):_('Alle Unterelemente öffnen')])->asImg(16, ['class' => 'text-top']); - } else { - $head .= Icon::create('folder-empty', 'clickable', ['title' => _('Dieses Element hat keine Unterelemente')])->asImg(); - } - return $head; - } - - function getItemContent($item_id){ - $content = "\n"; - if ($item_id == "root"){ - $content .= "\n"; - $content .= "\n"; - $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; - return $content; - } - if ($this->tree->tree_data[$item_id]['info']){ - $content .= "\n"; - $content .= formatReady($this->tree->tree_data[$item_id]['info']) . ""; - } - $content .= "" . sprintf(_("Alle Veranstaltungen innerhalb dieses Bereiches in der %sÜbersicht%s"), - "getSelf("cmd=show_sem_range&item_id=$item_id")) ."\">","") . ""; - $content .= " "; - if ($this->tree->getNumEntries($item_id)){ - $content .= "" . _("Einträge auf dieser Ebene:"); - $content .= "\n"; - $entries = $this->tree->getSemData($item_id); - $content .= $this->getSemDetails($entries->getGroupedResult("seminar_id")); - } else { - $content .= "\n" . _("Keine Einträge auf dieser Ebene vorhanden!") . ""; - } - $content .= ""; - return $content; - } - - function getSemDetails($sem_data){ - $content = ""; - $sem_number = -1; - foreach($sem_data as $seminar_id => $data){ - if (key($data['sem_number']) != $sem_number){ - $sem_number = key($data['sem_number']); - $content .= "\n" . $this->tree->sem_dates[$sem_number]['name'] . ""; - } - $sem_name = key($data["Name"]); - $sem_number_end = key($data["sem_number_end"]); - if ($sem_number != $sem_number_end){ - $sem_name .= " (" . $this->tree->sem_dates[$sem_number]['name'] . " - "; - $sem_name .= (($sem_number_end == -1) ? _("unbegrenzt") : $this->tree->sem_dates[$sem_number_end]['name']) . ")"; - } - $content .= "getSelf())) . "\">" . htmlReady($sem_name) . " - ("; - for ($i = 0; $i < count($data["doz_name"]); ++$i){ - $content .= "" . htmlReady(key($data["doz_name"])) . ""; - if($i != count($data["doz_name"])-1){ - $content .= ", "; - } - next($data["doz_name"]); - next($data["doz_uname"]); - } - $content .= ") "; - } - return $content; - } - - function getItemHead($item_id){ - $head = ""; - $head .= parent::getItemHead($item_id); - if ($item_id != "root"){ - $head .= " (" . $this->tree->getNumEntries($item_id,true) . ") " ; - } - return $head; - } - - function getSelf($param = "", $with_start_item = true){ - if ($param) - $url = (($with_start_item) ? "?start_item_id=" . $this->start_item_id . "&" : "?") . $param . "#anchor"; - else - $url = (($with_start_item) ? "?start_item_id=" . $this->start_item_id : "") . "#anchor"; - return $url; - } -} -//test -//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); -//include 'lib/include/html_head.inc.php'; -//include ('lib/seminar_open.php'); // initialise Stud.IP-Session -//$test = new StudipSemTreeView(); -//$test->showTree("c2942084b6140fc2635dfecdf65bf20d"); -//page_close(); -?> diff --git a/lib/classes/StudipSemTreeView.php b/lib/classes/StudipSemTreeView.php new file mode 100644 index 0000000..3e984cc --- /dev/null +++ b/lib/classes/StudipSemTreeView.php @@ -0,0 +1,215 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + + +/** +* class to print out the seminar tree +* +* This class prints out a html representation of the whole or part of the tree +* +* @access public +* @author André Noack +* @package +*/ +class StudipSemTreeView extends TreeView { + + /** + * constructor + * + * @access public + */ + function __construct($start_item_id = "root", $sem_number = null){ + $this->start_item_id = ($start_item_id) ? $start_item_id : "root"; + $this->root_content = $GLOBALS['UNI_INFO']; + $args = null; + if ($sem_number){ + $args = ['sem_number' => $sem_number]; + } + parent::__construct("StudipSemTree", $args); //calling the baseclass constructor + } + + /** + * manages the session variables used for the open/close thing + * + * @access private + */ + function handleOpenRanges(){ + global $_REQUEST; + + $this->open_ranges[$this->start_item_id] = true; + if (Request::option('close_item') || Request::option('open_item')){ + $toggle_item = (Request::option('close_item')) ? Request::option('close_item') : Request::option('open_item'); + if (!$this->open_items[$toggle_item]){ + $this->open_items[$toggle_item] = true; + } else { + unset($this->open_items[$toggle_item]); + } + + if($this->tree->hasKids(Request::option('open_item'))){ + $this->start_item_id = Request::option('open_item'); + $this->open_ranges = null; + $this->open_items = null; + $this->open_items[Request::option('open_item')] = true; + $this->open_ranges[Request::option('open_item')] = true; + } + + $this->anchor = $toggle_item; + } + + if ($this->start_item_id == "root"){ + $this->open_ranges = null; + $this->open_ranges[$this->start_item_id] = true; + } + } + + function showSemTree(){ + echo "\n"; + if ($this->start_item_id != 'root'){ + echo "\n"; + } + echo "\n
" . $this->getSemPath() + . Assets::img('forumleer.gif', ['size' => '1@20']) . "
"; + $this->showTree($this->start_item_id); + echo "\n
"; + } + + function getSemPath(){ + //$ret = "" .htmlReady($this->tree->root_name) . ""; + if ($parents = $this->tree->getParents($this->start_item_id)){ + for($i = count($parents)-1; $i >= 0; --$i){ + $ret .= " > getSelf("start_item_id={$parents[$i]}&open_item={$parents[$i]}",false)) + . "\">" .htmlReady($this->tree->tree_data[$parents[$i]]["name"]) . ""; + } + } + return $ret; + } + + /** + * returns html for the icons in front of the name of the item + * + * @access private + * @param string $item_id + * @return string + */ + function getItemHeadPics($item_id){ + $head = ""; + $head .= "open_items[$item_id])? URLHelper::getLink($this->getSelf("close_item={$item_id}")) . "\"" . tooltip(_("Dieses Element schließen"),true) . ">" + : URLHelper::getLink($this->getSelf("open_item={$item_id}")) . "\"" . tooltip(_("Dieses Element öffnen"),true) . ">"; + $head .= Icon::create($this->open_items[$item_id] ? 'arr_1down' : 'arr_1right', 'clickable'); + $head .= (!$this->open_items[$item_id]) ? Assets::img('forumleer.gif', ['size' => '5']) : ""; + $head .= ""; + if ($this->tree->hasKids($item_id)){ + $head .= Icon::create('folder-full', 'clickable', ['title' => $this->open_ranges[$item_id]?_('Alle Unterelemente schliessen'):_('Alle Unterelemente öffnen')])->asImg(16, ['class' => 'text-top']); + } else { + $head .= Icon::create('folder-empty', 'clickable', ['title' => _('Dieses Element hat keine Unterelemente')])->asImg(); + } + return $head; + } + + function getItemContent($item_id){ + $content = "\n"; + if ($item_id == "root"){ + $content .= "\n"; + $content .= "\n"; + $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; + return $content; + } + if ($this->tree->tree_data[$item_id]['info']){ + $content .= "\n"; + $content .= formatReady($this->tree->tree_data[$item_id]['info']) . ""; + } + $content .= "" . sprintf(_("Alle Veranstaltungen innerhalb dieses Bereiches in der %sÜbersicht%s"), + "getSelf("cmd=show_sem_range&item_id=$item_id")) ."\">","") . ""; + $content .= " "; + if ($this->tree->getNumEntries($item_id)){ + $content .= "" . _("Einträge auf dieser Ebene:"); + $content .= "\n"; + $entries = $this->tree->getSemData($item_id); + $content .= $this->getSemDetails($entries->getGroupedResult("seminar_id")); + } else { + $content .= "\n" . _("Keine Einträge auf dieser Ebene vorhanden!") . ""; + } + $content .= ""; + return $content; + } + + function getSemDetails($sem_data){ + $content = ""; + $sem_number = -1; + foreach($sem_data as $seminar_id => $data){ + if (key($data['sem_number']) != $sem_number){ + $sem_number = key($data['sem_number']); + $content .= "\n" . $this->tree->sem_dates[$sem_number]['name'] . ""; + } + $sem_name = key($data["Name"]); + $sem_number_end = key($data["sem_number_end"]); + if ($sem_number != $sem_number_end){ + $sem_name .= " (" . $this->tree->sem_dates[$sem_number]['name'] . " - "; + $sem_name .= (($sem_number_end == -1) ? _("unbegrenzt") : $this->tree->sem_dates[$sem_number_end]['name']) . ")"; + } + $content .= "getSelf())) . "\">" . htmlReady($sem_name) . " + ("; + for ($i = 0; $i < count($data["doz_name"]); ++$i){ + $content .= "" . htmlReady(key($data["doz_name"])) . ""; + if($i != count($data["doz_name"])-1){ + $content .= ", "; + } + next($data["doz_name"]); + next($data["doz_uname"]); + } + $content .= ") "; + } + return $content; + } + + function getItemHead($item_id){ + $head = ""; + $head .= parent::getItemHead($item_id); + if ($item_id != "root"){ + $head .= " (" . $this->tree->getNumEntries($item_id,true) . ") " ; + } + return $head; + } + + function getSelf($param = "", $with_start_item = true){ + if ($param) + $url = (($with_start_item) ? "?start_item_id=" . $this->start_item_id . "&" : "?") . $param . "#anchor"; + else + $url = (($with_start_item) ? "?start_item_id=" . $this->start_item_id : "") . "#anchor"; + return $url; + } +} +//test +//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); +//include 'lib/include/html_head.inc.php'; +//include ('lib/seminar_open.php'); // initialise Stud.IP-Session +//$test = new StudipSemTreeView(); +//$test->showTree("c2942084b6140fc2635dfecdf65bf20d"); +//page_close(); +?> diff --git a/lib/classes/StudipSemTreeViewAdmin.class.php b/lib/classes/StudipSemTreeViewAdmin.class.php deleted file mode 100644 index edc65c8..0000000 --- a/lib/classes/StudipSemTreeViewAdmin.class.php +++ /dev/null @@ -1,825 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -use Studip\Button, Studip\LinkButton; - - -/** -* class to print out the seminar tree (admin mode) -* -* This class prints out a html representation of the whole or part of the tree -* -* @access public -* @author André Noack -* @package -*/ -class StudipSemTreeViewAdmin extends TreeView -{ - var $admin_ranges = []; - var $msg = []; - var $marked_item; - var $marked_sem; - var $mode; - var $move_item_id = null; - var $edit_item_id = null; - - /** - * constructor - * - * @access public - */ - function __construct($start_item_id = "root"){ - $this->start_item_id = ($start_item_id) ? $start_item_id : "root"; - $this->root_content = $GLOBALS['UNI_INFO']; - parent::__construct("StudipSemTree"); //calling the baseclass constructor - URLHelper::bindLinkParam("_marked_item", $this->marked_item); - $this->marked_sem =& $_SESSION['_marked_sem']; - $this->parseCommand(); - } - - /** - * manages the session variables used for the open/close thing - * - * @access private - */ - function handleOpenRanges(){ - - $this->open_ranges[$this->start_item_id] = true; - - if (Request::option('close_item') || Request::option('open_item')){ - $toggle_item = (Request::option('close_item')) ? Request::option('close_item') : Request::option('open_item'); - if (!$this->open_items[$toggle_item]){ - $this->openItem($toggle_item); - } else { - unset($this->open_items[$toggle_item]); - } - } - - if (Request::option('item_id')) $this->anchor = Request::option('item_id'); - - } - - function openItem($item_id){ - if ($this->tree->hasKids($item_id)){ - $this->start_item_id = $item_id; - $this->open_ranges = null; - $this->open_items = null; - $this->open_items[$item_id] = true; - $this->open_ranges[$item_id] = true; - } else { - $this->open_ranges[$this->tree->tree_data[$item_id]['parent_id']] = true; - $this->open_items[$item_id] = true; - $this->start_item_id = $this->tree->tree_data[$item_id]['parent_id']; - } - if ($this->start_item_id == "root"){ - $this->open_ranges = null; - $this->open_ranges[$this->start_item_id] = true; - } - $this->anchor = $item_id; - } - - function parseCommand(){ - $this->mode = ''; - if (Request::quoted('mode')) - $this->mode = Request::quoted('mode'); - if (Request::option('cmd')){ - $exec_func = "execCommand" . Request::option('cmd'); - if (method_exists($this,$exec_func)){ - if ($this->$exec_func()){ - $this->tree->init(); - } - } - } - if ($this->mode == "MoveItem" || $this->mode == "CopyItem") - $this->move_item_id = $this->marked_item; - } - - public function execCommandOrderItemsAlphabetically() - { - $item_id = Request::option('sort_id'); - $sorted_items_stmt = DBManager::get()->prepare( - 'SELECT * FROM sem_tree LEFT JOIN Institute ON studip_object_id = Institut_id WHERE parent_id = :parent_id ORDER BY IF(studip_object_id, Institute.name, sem_tree.name)' - ); - $sorted_items_stmt->execute([ - 'parent_id' => $item_id, - ]); - $sorted_items = $sorted_items_stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($sorted_items as $priority => $data) { - $update_priority_stmt = DBManager::get()->prepare('UPDATE sem_tree SET priority = :priority WHERE sem_tree_id = :sem_tree_id'); - $update_priority_stmt->execute([ - 'priority' => $priority, - 'sem_tree_id' => $data['sem_tree_id'] - ]); - } - $this->msg[$item_id] = 'info§' . _('Die Einträge im Bereich wurden alphabetisch sortiert.'); - - return true; - } - - function execCommandOrderItem(){ - $direction = Request::quoted('direction'); - $item_id = Request::option('item_id'); - $items_to_order = $this->tree->getKids($this->tree->tree_data[$item_id]['parent_id']); - if (!$this->isParentAdmin($item_id) || !$items_to_order) - return false; - for ($i = 0; $i < count($items_to_order); ++$i){ - if ($item_id == $items_to_order[$i]) - break; - } - if ($direction == "up" && isset($items_to_order[$i-1])){ - $items_to_order[$i] = $items_to_order[$i-1]; - $items_to_order[$i-1] = $item_id; - } elseif (isset($items_to_order[$i+1])){ - $items_to_order[$i] = $items_to_order[$i+1]; - $items_to_order[$i+1] = $item_id; - } - $view = DbView::getView('sem_tree'); - for ($i = 0; $i < count($items_to_order); ++$i){ - $view->params = [$i, $items_to_order[$i]]; - $rs = $view->get_query("view:SEM_TREE_UPD_PRIO"); - } - $this->mode = ""; - $this->msg[$item_id] = "msg§" . (($direction == "up") ? _("Element wurde eine Position nach oben verschoben.") : _("Element wurde eine Position nach unten verschoben.")); - return true; - } - - function execCommandNewItem(){ - $item_id = Request::option('item_id'); - if ($this->isItemAdmin($item_id)){ - $new_item_id = DbView::get_uniqid(); - $this->tree->storeItem($new_item_id,$item_id,_("Neuer Eintrag"), $this->tree->getNumKids($item_id) +1); - $this->openItem($new_item_id); - $this->edit_item_id = $new_item_id; - if ($this->mode != "NewItem") $this->msg[$new_item_id] = "info§" . _("Hier können Sie die Bezeichnung und die Kurzinformation zu diesem Bereich eingeben."); - $this->mode = "NewItem"; - } - return false; - } - - function execCommandEditItem(){ - $item_id = Request::option('item_id'); - if ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)){ - $this->mode = "EditItem"; - $this->anchor = $item_id; - $this->edit_item_id = $item_id; - if($this->tree->tree_data[$this->edit_item_id]['studip_object_id']){ - $this->msg[$item_id] = "info§" . _("Hier können Sie die Kurzinformation zu diesem Bereich eingeben. Der Name kann nicht geändert werden, da es sich um eine Stud.IP-Einrichtung handelt."); - } else { - $this->msg[$item_id] = "info§" . _("Hier können Sie die Bezeichnung und die Kurzinformation zu diesem Bereich eingeben"); - } - } - return false; - } - - function execCommandInsertItem(){ - $item_id = Request::option('item_id'); - $parent_id = Request::option('parent_id'); - $item_name = Request::quoted('edit_name'); - $item_info = Request::quoted('edit_info'); - $item_type = Request::int('edit_type'); - if ($this->mode == "NewItem" && $item_id){ - if ($this->isItemAdmin($parent_id)){ - $priority = count($this->tree->getKids($parent_id)); - if ($this->tree->InsertItem($item_id,$parent_id,$item_name,$item_info,$priority,null,$item_type)){ - $this->mode = ""; - $this->tree->init(); - $this->openItem($item_id); - $this->msg[$item_id] = "msg§" . _("Dieser Bereich wurde neu eingefügt."); - } - } - } - if ($this->mode == "EditItem"){ - if ($this->isParentAdmin($item_id)){ - if ($this->tree->UpdateItem($item_id, $item_name, $item_info, $item_type)){ - $this->msg[$item_id] = "msg§" . _("Bereich wurde geändert."); - } else { - $this->msg[$item_id] = "info§" . _("Keine Veränderungen vorgenommen."); - } - $this->mode = ""; - $this->tree->init(); - $this->openItem($item_id); - } - } - return false; - } - - function execCommandAssertDeleteItem(){ - $item_id = Request::option('item_id'); - if ($this->isParentAdmin($item_id)){ - $this->mode = "AssertDeleteItem"; - $this->open_items[$item_id] = true; - $this->msg[$item_id] = "info§" ._("Sie beabsichtigen diesen Bereich inklusive aller Unterbereiche zu löschen. ") - . sprintf(_("Es werden insgesamt %s Bereiche gelöscht!"),count($this->tree->getKidsKids($item_id))+1) - . "
" . _("Wollen Sie diese Bereiche wirklich löschen?") . "
" - . LinkButton::createAccept(_('Ja!'), - URLHelper::getURL($this->getSelf('cmd=DeleteItem&item_id='.$item_id)), - ['title' => _('löschen')]) - . " " - . LinkButton::createCancel(_('Nein!'), - URLHelper::getURL($this->getSelf('cmd=Cancel&item_id='. $item_id))); - } - return false; - } - - function execCommandDeleteItem(){ - $item_id = Request::option('item_id'); - $item_name = $this->tree->tree_data[$item_id]['name']; - if ($this->isParentAdmin($item_id) && $this->mode == "AssertDeleteItem"){ - $this->openItem($this->tree->tree_data[$item_id]['parent_id']); - $items_to_delete = $this->tree->getKidsKids($item_id); - $items_to_delete[] = $item_id; - $deleted = $this->tree->DeleteItems($items_to_delete); - if ($deleted['items']){ - $this->msg[$this->anchor] = "msg§" . sprintf(_("Der Bereich %s und alle Unterbereiche (insgesamt %s) wurden gelöscht. "),htmlReady($item_name),$deleted['items']); - } else { - $this->msg[$this->anchor] = "error§" . _("Fehler, es konnten keine Bereiche gelöscht werden !"); - } - if ($deleted['entries']){ - $this->msg[$this->anchor] .= sprintf(_("
Es wurden %s Veranstaltungszuordnungen gelöscht. "),$deleted['entries']); - } - $this->mode = ""; - } - return true; - } - - function execCommandMoveItem(){ - $item_id = Request::option('item_id'); - $this->anchor = $item_id; - $this->marked_item = $item_id; - $this->mode = "MoveItem"; - return false; - } - - function execCommandCopyItem(){ - $item_id = Request::option('item_id'); - $this->anchor = $item_id; - $this->marked_item = $item_id; - $this->mode = "CopyItem"; - return false; - } - - function execCommandDoMoveItem(){ - $item_id = Request::option('item_id'); - $item_to_move = $this->marked_item; - if ($this->mode == "MoveItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) - && ($item_to_move != $item_id) && ($this->tree->tree_data[$item_to_move]['parent_id'] != $item_id) - && !$this->tree->isChildOf($item_to_move,$item_id)){ - $view = DbView::getView('sem_tree'); - $view->params = [$item_id, count($this->tree->getKids($item_id)), $item_to_move]; - $rs = $view->get_query("view:SEM_TREE_MOVE_ITEM"); - if ($rs->affected_rows()){ - $this->msg[$item_to_move] = "msg§" . _("Bereich wurde verschoben."); - } else { - $this->msg[$item_to_move] = "error§" . _("Keine Verschiebung durchgeführt."); - } - } - $this->tree->init(); - $this->openItem($item_to_move); - $this->mode = ""; - return false; - } - - function execCommandDoCopyItem(){ - $item_id = Request::option('item_id'); - $item_to_copy = $this->marked_item; - if ($this->mode == "CopyItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) - && ($item_to_copy != $item_id) && ($this->tree->tree_data[$item_to_copy]['parent_id'] != $item_id) - && !$this->tree->isChildOf($item_to_copy,$item_id)){ - $items_to_copy = $this->tree->getKidsKids($item_to_copy); - $seed = DbView::get_uniqid(); - $new_item_id = md5($item_to_copy . $seed); - $parent_id = $item_id; - $num_copy = $this->tree->InsertItem($new_item_id,$parent_id, - addslashes($this->tree->tree_data[$item_to_copy]['name']), - addslashes($this->tree->tree_data[$item_to_copy]['info']), - $this->tree->getMaxPriority($parent_id)+1, - ($this->tree->tree_data[$item_to_copy]['studip_object_id'] ? $this->tree->tree_data[$item_to_copy]['studip_object_id'] : null), - $this->tree->tree_data[$item_to_copy]['type']); - if($num_copy){ - if ($items_to_copy){ - for ($i = 0; $i < count($items_to_copy); ++$i){ - $num_copy += $this->tree->InsertItem(md5($items_to_copy[$i] . $seed), - md5($this->tree->tree_data[$items_to_copy[$i]]['parent_id'] . $seed), - addslashes($this->tree->tree_data[$items_to_copy[$i]]['name']), - addslashes($this->tree->tree_data[$items_to_copy[$i]]['info']), - $this->tree->tree_data[$items_to_copy[$i]]['priority'], - ($this->tree->tree_data[$items_to_copy[$i]]['studip_object_id'] ? $this->tree->tree_data[$items_to_copy[$i]]['studip_object_id'] : null), - $this->tree->tree_data[$item_to_copy]['type']); - } - } - $items_to_copy[] = $item_to_copy; - for ($i = 0; $i < count($items_to_copy); ++$i){ - $sem_entries = $this->tree->getSemIds($items_to_copy[$i], false); - if ($sem_entries){ - for ($j = 0; $j < count($sem_entries); ++$j){ - $num_entries += $this->tree->InsertSemEntry(md5($items_to_copy[$i] . $seed), $sem_entries[$j]); - } - } - } - } - - if ($num_copy){ - $this->msg[$new_item_id] = "msg§" . sprintf(_("%s Bereich(e) wurde(n) kopiert."), $num_copy) . "
" - . sprintf(_("%s Veranstaltungszuordnungen wurden kopiert"), $num_entries); - } else { - $this->msg[$new_item_id] = "error§" . _("Keine Kopie durchgeführt."); - } - $this->tree->init(); - $this->openItem($new_item_id); - } - $this->mode = ""; - return false; - } - - function execCommandInsertFak(){ - if($this->isItemAdmin("root") && Request::quoted('insert_fak')){ - $view = DbView::getView('sem_tree'); - $item_id = $view->get_uniqid(); - $view->params = [$item_id,'root','',$this->tree->getNumKids('root')+1,'',Request::quoted('insert_fak'),0]; - $rs = $view->get_query("view:SEM_TREE_INS_ITEM"); - if ($rs->affected_rows()){ - $this->tree->init(); - $this->openItem($item_id); - $this->msg[$item_id] = "msg§" . _("Dieser Bereich wurde neu eingefügt."); - return false; - } - } - return false; - } - - function execCommandMarkSem(){ - $item_id = Request::option('item_id'); - $marked_sem_array = Request::quotedArray('marked_sem'); - $marked_sem = array_values(array_unique($marked_sem_array)); - $sem_aktion = explode("_",Request::quoted('sem_aktion')); - if (($sem_aktion[0] == 'mark' || $sem_aktion[1] == 'mark') && count($marked_sem)){ - $count_mark = 0; - for ($i = 0; $i < count($marked_sem); ++$i){ - if (!isset($this->marked_sem[$marked_sem[$i]])){ - ++$count_mark; - $this->marked_sem[$marked_sem[$i]] = true; - } - } - if ($count_mark){ - $this->msg[$item_id] = "msg§" . sprintf(_("Es wurde(n) %s Veranstaltung(en) der Merkliste hinzugefügt."),$count_mark); - } - } - if ($this->isItemAdmin($item_id)){ - if (($sem_aktion[0] == 'del' || $sem_aktion[1] == 'del') && count($marked_sem)){ - $not_deleted = []; - foreach($marked_sem as $key => $seminar_id){ - $seminar = new Seminar($seminar_id); - if(count($seminar->getStudyAreas()) == 1){ - $not_deleted[] = $seminar->getName(); - unset($marked_sem[$key]); - } - } - if ($this->msg[$item_id]){ - $this->msg[$item_id] .= "
"; - } else { - $this->msg[$item_id] = "msg§"; - } - if(count($marked_sem)){ - $count_del = $this->tree->DeleteSemEntries($item_id, $marked_sem); - $this->msg[$item_id] .= sprintf(_("%s Veranstaltungszuordnung(en) wurde(n) aufgehoben."),$count_del); - } - if(count($not_deleted)){ - $this->msg[$item_id] .= '
' - . sprintf(_("Für folgende Veranstaltungen wurde die Zuordnung nicht aufgehoben, da es die einzige Zuordnung ist: %s") -, '
'.htmlready(join(', ', $not_deleted))); - } - } - $this->anchor = $item_id; - $this->open_items[$item_id] = true; - return true; - } - return false; - } - - function execCommandCancel(){ - $item_id = Request::option('item_id'); - $this->mode = ""; - $this->anchor = $item_id; - return false; - } - - function showSemTree(){ - ?> - - "; - if ($this->start_item_id != 'root'){ - echo "\n
" - . _("Studienbereiche") . ":
" . $this->getSemPath() - . "
"; - } - echo "\n"; - $this->showTree($this->start_item_id); - echo "\n"; - } - - function getSemPath(){ - $ret = ""; - //$ret = "" .htmlReady($this->tree->root_name) . ""; - if ($parents = $this->tree->getParents($this->start_item_id)){ - for($i = count($parents)-1; $i >= 0; --$i){ - $ret .= " > getSelf("start_item_id={$parents[$i]}&open_item={$parents[$i]}",false)) - . "\">" .htmlReady($this->tree->tree_data[$parents[$i]]["name"]) . ""; - } - } - return $ret; - } - - /** - * returns html for the icons in front of the name of the item - * - * @access private - * @param string $item_id - * @return string - */ - function getItemHeadPics($item_id){ - $head = $this->getItemHeadFrontPic($item_id); - $head .= "\n"; - if ($this->tree->hasKids($item_id)){ - $head .= Icon::create('folder-full', Icon::ROLE_CLICKABLE, ['title' => !empty($this->open_ranges[$item_id]) ? _('Alle Unterelemente schliessen') : _('Alle Unterelemente öffnen')])->asImg(['class' => 'text-top']); - } else { - $head .= Icon::create('folder-empty', 'clickable', ['title' => _('Dieses Element hat keine Unterelemente')])->asImg(); - } - return $head . ""; - } - - function getItemContent($item_id){ - if ($item_id == $this->edit_item_id) { - return $this->getEditItemContent(); - } - if (empty($GLOBALS['SEM_TREE_TYPES'][$this->tree->getValue($item_id, 'type')]['editable'])){ - $is_not_editable = true; - $this->msg[$item_id] = "info§" . sprintf(_("Der Typ dieses Elementes verbietet eine Bearbeitung.")); - } - if ($item_id == $this->move_item_id) { - $this->msg[$item_id] = "info§" . sprintf(_("Dieses Element wurde zum Verschieben / Kopieren markiert. Bitte wählen Sie ein Einfügesymbol %s aus, um das Element zu verschieben / kopieren."), Icon::create('arr_2right', 'sort', ['title' => "Einfügesymbol"])->asImg(16, ["alt" => "Einfügesymbol"])); - } - $content = "\n"; - $content .= $this->getItemMessage($item_id); - $content .= "\n
"; - if (empty($is_not_editable)) { - if ($this->isItemAdmin($item_id) ){ - $content .= LinkButton::create(_('Neues Objekt'), - URLHelper::getURL($this->getSelf('cmd=NewItem&item_id='.$item_id)), - ['title' => _('Innerhalb dieser Ebene ein neues Element einfügen')]) . ' '; - $content .= LinkButton::create(_('Sortieren'), - URLHelper::getURL($this->getSelf('cmd=OrderItemsAlphabetically&sort_id='.$item_id)), - ['title' => _('Sortiert die untergeordneten Elemente alphabetisch')]) . ' '; - } - if ($this->isParentAdmin($item_id) && $item_id != "root"){ - $content .= LinkButton::create(_('Bearbeiten'), - URLHelper::getURL($this->getSelf('cmd=EditItem&item_id=' . $item_id)), - ['title' => 'Dieses Element bearbeiten']) . ' '; - - $content .= LinkButton::create(_('Löschen'), - URLHelper::getURL($this->getSelf('cmd=AssertDeleteItem&item_id=' . $item_id)), - ['title' => _('Dieses Element löschen')]) . ' '; - - if ($this->move_item_id == $item_id && ($this->mode == "MoveItem" || $this->mode == "CopyItem")){ - $content .= LinkButton::create(_('Abbrechen'), - URLHelper::getURL($this->getSelf('cmd=Cancel&item_id=' . $item_id)), - ['title' => _('Verschieben / Kopieren abbrechen')]) . ' '; - } else { - $content .= LinkButton::create(_('Verschieben'), - URLHelper::getURL($this->getSelf('cmd=MoveItem&item_id='.$item_id)), - ['title' => _('Dieses Element in eine andere Ebene verschieben')]) . ' '; - $content .= LinkButton::create(_('Kopieren'), - URLHelper::getURL($this->getSelf('cmd=CopyItem&item_id='.$item_id)), - ['title' => _('Dieses Element in eine andere Ebene kopieren')]); - } - } - } - if ($item_id == 'root' && $this->isItemAdmin($item_id)){ - $view = DbView::getView('sem_tree'); - $rs = $view->get_query("view:SEM_TREE_GET_LONELY_FAK"); - $content .= "\n

getSelf("cmd=InsertFak")) . "\" method=\"post\" class=\"default\">" - . CSRFProtection::tokenTag() - . '
" . Button::create(_('Eintragen'), ['title' => _("Fakultät einfügen")]) . "

"; - } - $content .= "
"; - - $content .= "\n"; - if ($item_id == "root"){ - $content .= "\n"; - $content .= "\n"; - $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; - return $content; - } - if ($this->tree->tree_data[$item_id]['info']){ - $content .= "\n"; - $content .= formatReady($this->tree->tree_data[$item_id]['info']) . ""; - } - $content .= " "; - if ($this->tree->getNumEntries($item_id)) { - $content .= "" . _("Einträge auf dieser Ebene:"); - $content .= "\n"; - $entries = $this->tree->getSemData($item_id); - $content .= $this->getSemDetails($entries,$item_id); - } else { - $content .= "\n" . _("Keine Einträge auf dieser Ebene vorhanden!") . ""; - } - $content .= ""; - return $content; - } - - function getSemDetails($snap, $item_id, $lonely_sem = false){ - $form_name = DbView::get_uniqid(); - $content = "
getSelf("cmd=MarkSem")) ."\" method=\"post\"> - "; - $content .= CSRFProtection::tokenTag(); - $group_by_data = $snap->getGroupedResult("sem_number", "seminar_id"); - $sem_data = $snap->getGroupedResult("seminar_id"); - $group_by_duration = $snap->getGroupedResult("sem_number_end", ["sem_number","seminar_id"]); - foreach ($group_by_duration as $sem_number_end => $detail){ - if ($sem_number_end != -1 && ($detail['sem_number'][$sem_number_end] && count($detail['sem_number']) == 1)){ - continue; - } else { - foreach ($detail['seminar_id'] as $seminar_id => $foo){ - $start_sem = key($sem_data[$seminar_id]["sem_number"]); - if ($sem_number_end == -1){ - $sem_number_end = count($this->tree->sem_dates)-1; - } - for ($i = $start_sem; $i <= $sem_number_end; ++$i){ - if ($group_by_data[$i] && !$tmp_group_by_data[$i]){ - foreach($group_by_data[$i]['seminar_id'] as $id => $bar){ - $tmp_group_by_data[$i]['seminar_id'][$id] = key($sem_data[$id]["Name"]); - } - } - $tmp_group_by_data[$i]['seminar_id'][$seminar_id] = key($sem_data[$seminar_id]["Name"]); - } - } - } - } - if (is_array($tmp_group_by_data)){ - foreach ($tmp_group_by_data as $start_sem => $detail){ - $group_by_data[$start_sem] = $detail; - } - } - - foreach ($group_by_data as $group_field => $sem_ids){ - foreach ($sem_ids['seminar_id'] as $seminar_id => $foo){ - $name = mb_strtolower(key($sem_data[$seminar_id]["Name"])); - $name = str_replace("ä","ae",$name); - $name = str_replace("ö","oe",$name); - $name = str_replace("ü","ue",$name); - $group_by_data[$group_field]['seminar_id'][$seminar_id] = $name; - } - uasort($group_by_data[$group_field]['seminar_id'], 'strnatcmp'); - } - - krsort($group_by_data, SORT_NUMERIC); - - foreach ($group_by_data as $sem_number => $sem_ids){ - $content .= "\n" . $this->tree->sem_dates[$sem_number]['name'] . ""; - if (is_array($sem_ids['seminar_id'])){ - foreach(array_keys($sem_ids['seminar_id']) as $seminar_id) { - $sem_name = key($sem_data[$seminar_id]["Name"]); - $sem_number_start = key($sem_data[$seminar_id]["sem_number"]); - $sem_number_end = key($sem_data[$seminar_id]["sem_number_end"]); - if ($sem_number_start != $sem_number_end){ - $sem_name .= " (" . $this->tree->sem_dates[$sem_number_start]['name'] . " - "; - $sem_name .= (($sem_number_end == -1) ? _("unbegrenzt") : $this->tree->sem_dates[$sem_number_end]['name']) . ")"; - } - $content .= " - getSelf())) . "\">" . htmlReady($sem_name) . " - ("; - $doz_name = array_keys($sem_data[$seminar_id]['doz_name']); - $doz_uname = array_keys($sem_data[$seminar_id]['doz_uname']); - if (is_array($doz_name)){ - uasort($doz_name, 'strnatcasecmp'); - $i = 0; - foreach ($doz_name as $index => $value){ - if ($i == 4){ - $content .= "... getSelf())) . "\">("._("mehr").")"; - break; - } - $content .= "" . htmlReady($value) . ""; - if($i != count($doz_name)-1){ - $content .= ", "; - } - ++$i; - } - } - $content .= ") "; - } - } - } - $content .= "" - . LinkButton::create(_('Auswählen'), ['title' => _('Auswahl umkehren'), 'onClick' => 'invert_selection(\''. $form_name .'\');return false;']) - . "
- " . Button::createAccept(_('OK'), ['title' => _("Gewählte Aktion starten")]) - . "
"; - return $content; - } - - function getEditItemContent(){ - ob_start(); - ?> -
edit_item_id}")) ?>" method="POST" class="default" style="width: 90%; margin: auto;"> - - - - getItemMessage($this->edit_item_id,2) ?>
- -
- - - - - 1) : ?> - - - - - - mode == "NewItem") ? $this->tree->tree_data[$this->edit_item_id]['parent_id'] : $this->edit_item_id; ?> - - -
- -
- _('Einstellungen übernehmen')]) ?> - getSelf('cmd=Cancel&item_id='.$buttonlink_id)), - ['title' => _('Aktion abbrechen')]) - ?> -
-
- - auth['perm'] == "root"){ - return true; - } - if (!($admin_id = $this->tree->tree_data[$this->tree->getAdminRange($item_id)]['studip_object_id'])){ - return false; - } - if(!isset($this->admin_ranges[$admin_id])){ - $view = DbView::getView('sem_tree'); - $view->params[0] = $auth->auth['uid']; - $view->params[1] = $admin_id; - $rs = $view->get_query("view:SEM_TREE_CHECK_PERM"); - $this->admin_ranges[$admin_id] = ($rs->next_record()) ? true : false; - } - if ($this->admin_ranges[$admin_id]){ - return true; - } else { - return false; - } - } - - function isParentAdmin($item_id){ - return $this->isItemAdmin($this->tree->tree_data[$item_id]['parent_id']); - } - - function getItemHead($item_id){ - $head = ""; - if (($this->mode == "MoveItem" || $this->mode == "CopyItem") && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) - && ($this->move_item_id != $item_id) && ($this->tree->tree_data[$this->move_item_id]['parent_id'] != $item_id) - && !$this->tree->isChildOf($this->move_item_id,$item_id)){ - $head .= "getSelf("cmd=Do" . $this->mode . "&item_id=$item_id")) . "\">" - . Icon::create('arr_2right', 'sort', ['title' => _("An dieser Stelle einfügen")])->asImg(16, ["alt" => _("An dieser Stelle einfügen")])." "; - } - $head .= parent::getItemHead($item_id); - if ($item_id != "root"){ - $head .= " (" . $this->tree->getNumEntries($item_id,true) . ") " ; - } - if ($item_id != $this->start_item_id && $this->isParentAdmin($item_id) && $item_id != $this->edit_item_id){ - $head .= ""; - if (!$this->tree->isFirstKid($item_id)){ - $head .= "getSelf("cmd=OrderItem&direction=up&item_id=$item_id")) . - "\">" . Icon::create('arr_2up', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach oben")]) . - ""; - } - if (!$this->tree->isLastKid($item_id)){ - $head .= "getSelf("cmd=OrderItem&direction=down&item_id=$item_id")) . - "\">" . Icon::create('arr_2down', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach unten")]) . - ""; - } - $head .= " "; - } - return $head; - } - - function getItemMessage($item_id,$colspan = 1){ - $content = ""; - if (!empty($this->msg[$item_id])) { - $msg = explode("§",$this->msg[$item_id]); - $pics = [ - 'error' => Icon::create('decline', 'attention'), - 'info' => Icon::create('exclaim', 'inactive'), - 'msg' => Icon::create('accept', 'accept')]; - $content = "\n - - -
" . $pics[$msg[0]]->asImg(['class' => 'text-top']) . "" . $msg[1] . "
"; - } - return $content; - } - - function getSelf($param = "", $with_start_item = true){ - $url_params = "foo=" . DbView::get_uniqid(); - if ($this->mode) $url_params .= "&mode=" . $this->mode; - if ($with_start_item) $url_params .= "&start_item_id=" . $this->start_item_id; - if ($param) $url_params .= '&' . $param; - return parent::getSelf($url_params); - } -} -//test -//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); -//include 'lib/include/html_head.inc.php'; -//include ('lib/seminar_open.php'); // initialise Stud.IP-Session -//$test = new StudipSemTreeViewAdmin(Request::quoted('start_item_id')); -//$test->showSemTree(); -//echo "
";
-//print_r($_open_items);
-//page_close();
-?>
diff --git a/lib/classes/StudipSemTreeViewAdmin.php b/lib/classes/StudipSemTreeViewAdmin.php
new file mode 100644
index 0000000..edc65c8
--- /dev/null
+++ b/lib/classes/StudipSemTreeViewAdmin.php
@@ -0,0 +1,825 @@
+
+// Suchi & Berg GmbH 
+// +---------------------------------------------------------------------------+
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or any later version.
+// +---------------------------------------------------------------------------+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+// +---------------------------------------------------------------------------+
+
+use Studip\Button, Studip\LinkButton;
+
+
+/**
+* class to print out the seminar tree (admin mode)
+*
+* This class prints out a html representation of the whole or part of the tree
+*
+* @access   public
+* @author   André Noack 
+* @package
+*/
+class StudipSemTreeViewAdmin extends TreeView
+{
+    var $admin_ranges = [];
+    var $msg = [];
+    var $marked_item;
+    var $marked_sem;
+    var $mode;
+    var $move_item_id = null;
+    var $edit_item_id = null;
+
+    /**
+    * constructor
+    *
+    * @access public
+    */
+    function __construct($start_item_id = "root"){
+        $this->start_item_id = ($start_item_id) ? $start_item_id : "root";
+        $this->root_content = $GLOBALS['UNI_INFO'];
+        parent::__construct("StudipSemTree"); //calling the baseclass constructor
+        URLHelper::bindLinkParam("_marked_item", $this->marked_item);
+        $this->marked_sem =& $_SESSION['_marked_sem'];
+        $this->parseCommand();
+    }
+
+    /**
+    * manages the session variables used for the open/close thing
+    *
+    * @access   private
+    */
+    function handleOpenRanges(){
+
+        $this->open_ranges[$this->start_item_id] = true;
+
+        if (Request::option('close_item') || Request::option('open_item')){
+            $toggle_item = (Request::option('close_item')) ? Request::option('close_item') : Request::option('open_item');
+            if (!$this->open_items[$toggle_item]){
+                $this->openItem($toggle_item);
+            } else {
+                unset($this->open_items[$toggle_item]);
+            }
+        }
+
+        if (Request::option('item_id')) $this->anchor = Request::option('item_id');
+
+    }
+
+    function openItem($item_id){
+        if ($this->tree->hasKids($item_id)){
+            $this->start_item_id = $item_id;
+            $this->open_ranges = null;
+            $this->open_items = null;
+            $this->open_items[$item_id] = true;
+            $this->open_ranges[$item_id] = true;
+        } else {
+            $this->open_ranges[$this->tree->tree_data[$item_id]['parent_id']] = true;
+            $this->open_items[$item_id] = true;
+            $this->start_item_id = $this->tree->tree_data[$item_id]['parent_id'];
+        }
+        if ($this->start_item_id == "root"){
+            $this->open_ranges = null;
+            $this->open_ranges[$this->start_item_id] = true;
+        }
+        $this->anchor = $item_id;
+    }
+
+    function parseCommand(){
+        $this->mode = '';
+        if (Request::quoted('mode'))
+        $this->mode = Request::quoted('mode');
+        if (Request::option('cmd')){
+            $exec_func = "execCommand" . Request::option('cmd');
+            if (method_exists($this,$exec_func)){
+                if ($this->$exec_func()){
+                    $this->tree->init();
+                }
+            }
+        }
+        if ($this->mode == "MoveItem" || $this->mode == "CopyItem")
+        $this->move_item_id = $this->marked_item;
+    }
+
+    public function execCommandOrderItemsAlphabetically()
+    {
+        $item_id = Request::option('sort_id');
+        $sorted_items_stmt = DBManager::get()->prepare(
+            'SELECT * FROM sem_tree LEFT JOIN Institute ON studip_object_id = Institut_id WHERE parent_id = :parent_id ORDER BY IF(studip_object_id, Institute.name, sem_tree.name)'
+        );
+        $sorted_items_stmt->execute([
+            'parent_id' => $item_id,
+        ]);
+        $sorted_items = $sorted_items_stmt->fetchAll(PDO::FETCH_ASSOC);
+        foreach ($sorted_items as $priority => $data) {
+            $update_priority_stmt = DBManager::get()->prepare('UPDATE sem_tree SET priority = :priority WHERE sem_tree_id = :sem_tree_id');
+            $update_priority_stmt->execute([
+                'priority' => $priority,
+                'sem_tree_id' => $data['sem_tree_id']
+            ]);
+        }
+        $this->msg[$item_id] = 'info§' . _('Die Einträge im Bereich wurden alphabetisch sortiert.');
+
+        return true;
+    }
+
+    function execCommandOrderItem(){
+        $direction = Request::quoted('direction');
+        $item_id = Request::option('item_id');
+        $items_to_order = $this->tree->getKids($this->tree->tree_data[$item_id]['parent_id']);
+        if (!$this->isParentAdmin($item_id) || !$items_to_order)
+        return false;
+        for ($i = 0; $i < count($items_to_order); ++$i){
+            if ($item_id == $items_to_order[$i])
+            break;
+        }
+        if ($direction == "up" && isset($items_to_order[$i-1])){
+            $items_to_order[$i] = $items_to_order[$i-1];
+            $items_to_order[$i-1] = $item_id;
+        } elseif (isset($items_to_order[$i+1])){
+            $items_to_order[$i] = $items_to_order[$i+1];
+            $items_to_order[$i+1] = $item_id;
+        }
+        $view = DbView::getView('sem_tree');
+        for ($i = 0; $i < count($items_to_order); ++$i){
+            $view->params = [$i, $items_to_order[$i]];
+            $rs = $view->get_query("view:SEM_TREE_UPD_PRIO");
+        }
+        $this->mode = "";
+        $this->msg[$item_id] = "msg§" . (($direction == "up") ? _("Element wurde eine Position nach oben verschoben.") : _("Element wurde eine Position nach unten verschoben."));
+        return true;
+    }
+
+    function execCommandNewItem(){
+        $item_id = Request::option('item_id');
+        if ($this->isItemAdmin($item_id)){
+            $new_item_id = DbView::get_uniqid();
+            $this->tree->storeItem($new_item_id,$item_id,_("Neuer Eintrag"), $this->tree->getNumKids($item_id) +1);
+            $this->openItem($new_item_id);
+            $this->edit_item_id = $new_item_id;
+            if ($this->mode != "NewItem") $this->msg[$new_item_id] = "info§" . _("Hier können Sie die Bezeichnung und die Kurzinformation zu diesem Bereich eingeben.");
+            $this->mode = "NewItem";
+        }
+        return false;
+    }
+
+    function execCommandEditItem(){
+        $item_id = Request::option('item_id');
+        if ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)){
+            $this->mode = "EditItem";
+            $this->anchor = $item_id;
+            $this->edit_item_id = $item_id;
+            if($this->tree->tree_data[$this->edit_item_id]['studip_object_id']){
+                $this->msg[$item_id] = "info§" . _("Hier können Sie die Kurzinformation zu diesem Bereich eingeben. Der Name kann nicht geändert werden, da es sich um eine Stud.IP-Einrichtung handelt.");
+            } else {
+                $this->msg[$item_id] = "info§" . _("Hier können Sie die Bezeichnung und die Kurzinformation zu diesem Bereich eingeben");
+            }
+        }
+        return false;
+    }
+
+    function execCommandInsertItem(){
+        $item_id = Request::option('item_id');
+        $parent_id = Request::option('parent_id');
+        $item_name = Request::quoted('edit_name');
+        $item_info = Request::quoted('edit_info');
+        $item_type = Request::int('edit_type');
+        if ($this->mode == "NewItem" && $item_id){
+            if ($this->isItemAdmin($parent_id)){
+                $priority = count($this->tree->getKids($parent_id));
+                if ($this->tree->InsertItem($item_id,$parent_id,$item_name,$item_info,$priority,null,$item_type)){
+                    $this->mode = "";
+                    $this->tree->init();
+                    $this->openItem($item_id);
+                    $this->msg[$item_id] = "msg§" . _("Dieser Bereich wurde neu eingefügt.");
+                }
+            }
+        }
+        if ($this->mode == "EditItem"){
+            if ($this->isParentAdmin($item_id)){
+                if ($this->tree->UpdateItem($item_id, $item_name, $item_info, $item_type)){
+                    $this->msg[$item_id] = "msg§" . _("Bereich wurde geändert.");
+                } else {
+                    $this->msg[$item_id] = "info§" . _("Keine Veränderungen vorgenommen.");
+                }
+                $this->mode = "";
+                $this->tree->init();
+                $this->openItem($item_id);
+            }
+        }
+        return false;
+    }
+
+    function execCommandAssertDeleteItem(){
+        $item_id = Request::option('item_id');
+        if ($this->isParentAdmin($item_id)){
+            $this->mode = "AssertDeleteItem";
+            $this->open_items[$item_id] = true;
+            $this->msg[$item_id] = "info§" ._("Sie beabsichtigen diesen Bereich inklusive aller Unterbereiche zu löschen. ")
+            . sprintf(_("Es werden insgesamt %s Bereiche gelöscht!"),count($this->tree->getKidsKids($item_id))+1)
+            . "
" . _("Wollen Sie diese Bereiche wirklich löschen?") . "
" + . LinkButton::createAccept(_('Ja!'), + URLHelper::getURL($this->getSelf('cmd=DeleteItem&item_id='.$item_id)), + ['title' => _('löschen')]) + . " " + . LinkButton::createCancel(_('Nein!'), + URLHelper::getURL($this->getSelf('cmd=Cancel&item_id='. $item_id))); + } + return false; + } + + function execCommandDeleteItem(){ + $item_id = Request::option('item_id'); + $item_name = $this->tree->tree_data[$item_id]['name']; + if ($this->isParentAdmin($item_id) && $this->mode == "AssertDeleteItem"){ + $this->openItem($this->tree->tree_data[$item_id]['parent_id']); + $items_to_delete = $this->tree->getKidsKids($item_id); + $items_to_delete[] = $item_id; + $deleted = $this->tree->DeleteItems($items_to_delete); + if ($deleted['items']){ + $this->msg[$this->anchor] = "msg§" . sprintf(_("Der Bereich %s und alle Unterbereiche (insgesamt %s) wurden gelöscht. "),htmlReady($item_name),$deleted['items']); + } else { + $this->msg[$this->anchor] = "error§" . _("Fehler, es konnten keine Bereiche gelöscht werden !"); + } + if ($deleted['entries']){ + $this->msg[$this->anchor] .= sprintf(_("
Es wurden %s Veranstaltungszuordnungen gelöscht. "),$deleted['entries']); + } + $this->mode = ""; + } + return true; + } + + function execCommandMoveItem(){ + $item_id = Request::option('item_id'); + $this->anchor = $item_id; + $this->marked_item = $item_id; + $this->mode = "MoveItem"; + return false; + } + + function execCommandCopyItem(){ + $item_id = Request::option('item_id'); + $this->anchor = $item_id; + $this->marked_item = $item_id; + $this->mode = "CopyItem"; + return false; + } + + function execCommandDoMoveItem(){ + $item_id = Request::option('item_id'); + $item_to_move = $this->marked_item; + if ($this->mode == "MoveItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) + && ($item_to_move != $item_id) && ($this->tree->tree_data[$item_to_move]['parent_id'] != $item_id) + && !$this->tree->isChildOf($item_to_move,$item_id)){ + $view = DbView::getView('sem_tree'); + $view->params = [$item_id, count($this->tree->getKids($item_id)), $item_to_move]; + $rs = $view->get_query("view:SEM_TREE_MOVE_ITEM"); + if ($rs->affected_rows()){ + $this->msg[$item_to_move] = "msg§" . _("Bereich wurde verschoben."); + } else { + $this->msg[$item_to_move] = "error§" . _("Keine Verschiebung durchgeführt."); + } + } + $this->tree->init(); + $this->openItem($item_to_move); + $this->mode = ""; + return false; + } + + function execCommandDoCopyItem(){ + $item_id = Request::option('item_id'); + $item_to_copy = $this->marked_item; + if ($this->mode == "CopyItem" && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) + && ($item_to_copy != $item_id) && ($this->tree->tree_data[$item_to_copy]['parent_id'] != $item_id) + && !$this->tree->isChildOf($item_to_copy,$item_id)){ + $items_to_copy = $this->tree->getKidsKids($item_to_copy); + $seed = DbView::get_uniqid(); + $new_item_id = md5($item_to_copy . $seed); + $parent_id = $item_id; + $num_copy = $this->tree->InsertItem($new_item_id,$parent_id, + addslashes($this->tree->tree_data[$item_to_copy]['name']), + addslashes($this->tree->tree_data[$item_to_copy]['info']), + $this->tree->getMaxPriority($parent_id)+1, + ($this->tree->tree_data[$item_to_copy]['studip_object_id'] ? $this->tree->tree_data[$item_to_copy]['studip_object_id'] : null), + $this->tree->tree_data[$item_to_copy]['type']); + if($num_copy){ + if ($items_to_copy){ + for ($i = 0; $i < count($items_to_copy); ++$i){ + $num_copy += $this->tree->InsertItem(md5($items_to_copy[$i] . $seed), + md5($this->tree->tree_data[$items_to_copy[$i]]['parent_id'] . $seed), + addslashes($this->tree->tree_data[$items_to_copy[$i]]['name']), + addslashes($this->tree->tree_data[$items_to_copy[$i]]['info']), + $this->tree->tree_data[$items_to_copy[$i]]['priority'], + ($this->tree->tree_data[$items_to_copy[$i]]['studip_object_id'] ? $this->tree->tree_data[$items_to_copy[$i]]['studip_object_id'] : null), + $this->tree->tree_data[$item_to_copy]['type']); + } + } + $items_to_copy[] = $item_to_copy; + for ($i = 0; $i < count($items_to_copy); ++$i){ + $sem_entries = $this->tree->getSemIds($items_to_copy[$i], false); + if ($sem_entries){ + for ($j = 0; $j < count($sem_entries); ++$j){ + $num_entries += $this->tree->InsertSemEntry(md5($items_to_copy[$i] . $seed), $sem_entries[$j]); + } + } + } + } + + if ($num_copy){ + $this->msg[$new_item_id] = "msg§" . sprintf(_("%s Bereich(e) wurde(n) kopiert."), $num_copy) . "
" + . sprintf(_("%s Veranstaltungszuordnungen wurden kopiert"), $num_entries); + } else { + $this->msg[$new_item_id] = "error§" . _("Keine Kopie durchgeführt."); + } + $this->tree->init(); + $this->openItem($new_item_id); + } + $this->mode = ""; + return false; + } + + function execCommandInsertFak(){ + if($this->isItemAdmin("root") && Request::quoted('insert_fak')){ + $view = DbView::getView('sem_tree'); + $item_id = $view->get_uniqid(); + $view->params = [$item_id,'root','',$this->tree->getNumKids('root')+1,'',Request::quoted('insert_fak'),0]; + $rs = $view->get_query("view:SEM_TREE_INS_ITEM"); + if ($rs->affected_rows()){ + $this->tree->init(); + $this->openItem($item_id); + $this->msg[$item_id] = "msg§" . _("Dieser Bereich wurde neu eingefügt."); + return false; + } + } + return false; + } + + function execCommandMarkSem(){ + $item_id = Request::option('item_id'); + $marked_sem_array = Request::quotedArray('marked_sem'); + $marked_sem = array_values(array_unique($marked_sem_array)); + $sem_aktion = explode("_",Request::quoted('sem_aktion')); + if (($sem_aktion[0] == 'mark' || $sem_aktion[1] == 'mark') && count($marked_sem)){ + $count_mark = 0; + for ($i = 0; $i < count($marked_sem); ++$i){ + if (!isset($this->marked_sem[$marked_sem[$i]])){ + ++$count_mark; + $this->marked_sem[$marked_sem[$i]] = true; + } + } + if ($count_mark){ + $this->msg[$item_id] = "msg§" . sprintf(_("Es wurde(n) %s Veranstaltung(en) der Merkliste hinzugefügt."),$count_mark); + } + } + if ($this->isItemAdmin($item_id)){ + if (($sem_aktion[0] == 'del' || $sem_aktion[1] == 'del') && count($marked_sem)){ + $not_deleted = []; + foreach($marked_sem as $key => $seminar_id){ + $seminar = new Seminar($seminar_id); + if(count($seminar->getStudyAreas()) == 1){ + $not_deleted[] = $seminar->getName(); + unset($marked_sem[$key]); + } + } + if ($this->msg[$item_id]){ + $this->msg[$item_id] .= "
"; + } else { + $this->msg[$item_id] = "msg§"; + } + if(count($marked_sem)){ + $count_del = $this->tree->DeleteSemEntries($item_id, $marked_sem); + $this->msg[$item_id] .= sprintf(_("%s Veranstaltungszuordnung(en) wurde(n) aufgehoben."),$count_del); + } + if(count($not_deleted)){ + $this->msg[$item_id] .= '
' + . sprintf(_("Für folgende Veranstaltungen wurde die Zuordnung nicht aufgehoben, da es die einzige Zuordnung ist: %s") +, '
'.htmlready(join(', ', $not_deleted))); + } + } + $this->anchor = $item_id; + $this->open_items[$item_id] = true; + return true; + } + return false; + } + + function execCommandCancel(){ + $item_id = Request::option('item_id'); + $this->mode = ""; + $this->anchor = $item_id; + return false; + } + + function showSemTree(){ + ?> + + "; + if ($this->start_item_id != 'root'){ + echo "\n
" + . _("Studienbereiche") . ":
" . $this->getSemPath() + . "
"; + } + echo "\n"; + $this->showTree($this->start_item_id); + echo "\n"; + } + + function getSemPath(){ + $ret = ""; + //$ret = "" .htmlReady($this->tree->root_name) . ""; + if ($parents = $this->tree->getParents($this->start_item_id)){ + for($i = count($parents)-1; $i >= 0; --$i){ + $ret .= " > getSelf("start_item_id={$parents[$i]}&open_item={$parents[$i]}",false)) + . "\">" .htmlReady($this->tree->tree_data[$parents[$i]]["name"]) . ""; + } + } + return $ret; + } + + /** + * returns html for the icons in front of the name of the item + * + * @access private + * @param string $item_id + * @return string + */ + function getItemHeadPics($item_id){ + $head = $this->getItemHeadFrontPic($item_id); + $head .= "\n"; + if ($this->tree->hasKids($item_id)){ + $head .= Icon::create('folder-full', Icon::ROLE_CLICKABLE, ['title' => !empty($this->open_ranges[$item_id]) ? _('Alle Unterelemente schliessen') : _('Alle Unterelemente öffnen')])->asImg(['class' => 'text-top']); + } else { + $head .= Icon::create('folder-empty', 'clickable', ['title' => _('Dieses Element hat keine Unterelemente')])->asImg(); + } + return $head . ""; + } + + function getItemContent($item_id){ + if ($item_id == $this->edit_item_id) { + return $this->getEditItemContent(); + } + if (empty($GLOBALS['SEM_TREE_TYPES'][$this->tree->getValue($item_id, 'type')]['editable'])){ + $is_not_editable = true; + $this->msg[$item_id] = "info§" . sprintf(_("Der Typ dieses Elementes verbietet eine Bearbeitung.")); + } + if ($item_id == $this->move_item_id) { + $this->msg[$item_id] = "info§" . sprintf(_("Dieses Element wurde zum Verschieben / Kopieren markiert. Bitte wählen Sie ein Einfügesymbol %s aus, um das Element zu verschieben / kopieren."), Icon::create('arr_2right', 'sort', ['title' => "Einfügesymbol"])->asImg(16, ["alt" => "Einfügesymbol"])); + } + $content = "\n"; + $content .= $this->getItemMessage($item_id); + $content .= "\n
"; + if (empty($is_not_editable)) { + if ($this->isItemAdmin($item_id) ){ + $content .= LinkButton::create(_('Neues Objekt'), + URLHelper::getURL($this->getSelf('cmd=NewItem&item_id='.$item_id)), + ['title' => _('Innerhalb dieser Ebene ein neues Element einfügen')]) . ' '; + $content .= LinkButton::create(_('Sortieren'), + URLHelper::getURL($this->getSelf('cmd=OrderItemsAlphabetically&sort_id='.$item_id)), + ['title' => _('Sortiert die untergeordneten Elemente alphabetisch')]) . ' '; + } + if ($this->isParentAdmin($item_id) && $item_id != "root"){ + $content .= LinkButton::create(_('Bearbeiten'), + URLHelper::getURL($this->getSelf('cmd=EditItem&item_id=' . $item_id)), + ['title' => 'Dieses Element bearbeiten']) . ' '; + + $content .= LinkButton::create(_('Löschen'), + URLHelper::getURL($this->getSelf('cmd=AssertDeleteItem&item_id=' . $item_id)), + ['title' => _('Dieses Element löschen')]) . ' '; + + if ($this->move_item_id == $item_id && ($this->mode == "MoveItem" || $this->mode == "CopyItem")){ + $content .= LinkButton::create(_('Abbrechen'), + URLHelper::getURL($this->getSelf('cmd=Cancel&item_id=' . $item_id)), + ['title' => _('Verschieben / Kopieren abbrechen')]) . ' '; + } else { + $content .= LinkButton::create(_('Verschieben'), + URLHelper::getURL($this->getSelf('cmd=MoveItem&item_id='.$item_id)), + ['title' => _('Dieses Element in eine andere Ebene verschieben')]) . ' '; + $content .= LinkButton::create(_('Kopieren'), + URLHelper::getURL($this->getSelf('cmd=CopyItem&item_id='.$item_id)), + ['title' => _('Dieses Element in eine andere Ebene kopieren')]); + } + } + } + if ($item_id == 'root' && $this->isItemAdmin($item_id)){ + $view = DbView::getView('sem_tree'); + $rs = $view->get_query("view:SEM_TREE_GET_LONELY_FAK"); + $content .= "\n

getSelf("cmd=InsertFak")) . "\" method=\"post\" class=\"default\">" + . CSRFProtection::tokenTag() + . '
" . Button::create(_('Eintragen'), ['title' => _("Fakultät einfügen")]) . "

"; + } + $content .= "
"; + + $content .= "\n"; + if ($item_id == "root"){ + $content .= "\n"; + $content .= "\n"; + $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . htmlReady($this->root_content) ."
"; + return $content; + } + if ($this->tree->tree_data[$item_id]['info']){ + $content .= "\n"; + $content .= formatReady($this->tree->tree_data[$item_id]['info']) . ""; + } + $content .= " "; + if ($this->tree->getNumEntries($item_id)) { + $content .= "" . _("Einträge auf dieser Ebene:"); + $content .= "\n"; + $entries = $this->tree->getSemData($item_id); + $content .= $this->getSemDetails($entries,$item_id); + } else { + $content .= "\n" . _("Keine Einträge auf dieser Ebene vorhanden!") . ""; + } + $content .= ""; + return $content; + } + + function getSemDetails($snap, $item_id, $lonely_sem = false){ + $form_name = DbView::get_uniqid(); + $content = "
getSelf("cmd=MarkSem")) ."\" method=\"post\"> + "; + $content .= CSRFProtection::tokenTag(); + $group_by_data = $snap->getGroupedResult("sem_number", "seminar_id"); + $sem_data = $snap->getGroupedResult("seminar_id"); + $group_by_duration = $snap->getGroupedResult("sem_number_end", ["sem_number","seminar_id"]); + foreach ($group_by_duration as $sem_number_end => $detail){ + if ($sem_number_end != -1 && ($detail['sem_number'][$sem_number_end] && count($detail['sem_number']) == 1)){ + continue; + } else { + foreach ($detail['seminar_id'] as $seminar_id => $foo){ + $start_sem = key($sem_data[$seminar_id]["sem_number"]); + if ($sem_number_end == -1){ + $sem_number_end = count($this->tree->sem_dates)-1; + } + for ($i = $start_sem; $i <= $sem_number_end; ++$i){ + if ($group_by_data[$i] && !$tmp_group_by_data[$i]){ + foreach($group_by_data[$i]['seminar_id'] as $id => $bar){ + $tmp_group_by_data[$i]['seminar_id'][$id] = key($sem_data[$id]["Name"]); + } + } + $tmp_group_by_data[$i]['seminar_id'][$seminar_id] = key($sem_data[$seminar_id]["Name"]); + } + } + } + } + if (is_array($tmp_group_by_data)){ + foreach ($tmp_group_by_data as $start_sem => $detail){ + $group_by_data[$start_sem] = $detail; + } + } + + foreach ($group_by_data as $group_field => $sem_ids){ + foreach ($sem_ids['seminar_id'] as $seminar_id => $foo){ + $name = mb_strtolower(key($sem_data[$seminar_id]["Name"])); + $name = str_replace("ä","ae",$name); + $name = str_replace("ö","oe",$name); + $name = str_replace("ü","ue",$name); + $group_by_data[$group_field]['seminar_id'][$seminar_id] = $name; + } + uasort($group_by_data[$group_field]['seminar_id'], 'strnatcmp'); + } + + krsort($group_by_data, SORT_NUMERIC); + + foreach ($group_by_data as $sem_number => $sem_ids){ + $content .= "\n" . $this->tree->sem_dates[$sem_number]['name'] . ""; + if (is_array($sem_ids['seminar_id'])){ + foreach(array_keys($sem_ids['seminar_id']) as $seminar_id) { + $sem_name = key($sem_data[$seminar_id]["Name"]); + $sem_number_start = key($sem_data[$seminar_id]["sem_number"]); + $sem_number_end = key($sem_data[$seminar_id]["sem_number_end"]); + if ($sem_number_start != $sem_number_end){ + $sem_name .= " (" . $this->tree->sem_dates[$sem_number_start]['name'] . " - "; + $sem_name .= (($sem_number_end == -1) ? _("unbegrenzt") : $this->tree->sem_dates[$sem_number_end]['name']) . ")"; + } + $content .= " + getSelf())) . "\">" . htmlReady($sem_name) . " + ("; + $doz_name = array_keys($sem_data[$seminar_id]['doz_name']); + $doz_uname = array_keys($sem_data[$seminar_id]['doz_uname']); + if (is_array($doz_name)){ + uasort($doz_name, 'strnatcasecmp'); + $i = 0; + foreach ($doz_name as $index => $value){ + if ($i == 4){ + $content .= "... getSelf())) . "\">("._("mehr").")"; + break; + } + $content .= "" . htmlReady($value) . ""; + if($i != count($doz_name)-1){ + $content .= ", "; + } + ++$i; + } + } + $content .= ") "; + } + } + } + $content .= "" + . LinkButton::create(_('Auswählen'), ['title' => _('Auswahl umkehren'), 'onClick' => 'invert_selection(\''. $form_name .'\');return false;']) + . "
+ " . Button::createAccept(_('OK'), ['title' => _("Gewählte Aktion starten")]) + . "
"; + return $content; + } + + function getEditItemContent(){ + ob_start(); + ?> +
edit_item_id}")) ?>" method="POST" class="default" style="width: 90%; margin: auto;"> + + + + getItemMessage($this->edit_item_id,2) ?>
+ +
+ + + + + 1) : ?> + + + + + + mode == "NewItem") ? $this->tree->tree_data[$this->edit_item_id]['parent_id'] : $this->edit_item_id; ?> + + +
+ +
+ _('Einstellungen übernehmen')]) ?> + getSelf('cmd=Cancel&item_id='.$buttonlink_id)), + ['title' => _('Aktion abbrechen')]) + ?> +
+
+ + auth['perm'] == "root"){ + return true; + } + if (!($admin_id = $this->tree->tree_data[$this->tree->getAdminRange($item_id)]['studip_object_id'])){ + return false; + } + if(!isset($this->admin_ranges[$admin_id])){ + $view = DbView::getView('sem_tree'); + $view->params[0] = $auth->auth['uid']; + $view->params[1] = $admin_id; + $rs = $view->get_query("view:SEM_TREE_CHECK_PERM"); + $this->admin_ranges[$admin_id] = ($rs->next_record()) ? true : false; + } + if ($this->admin_ranges[$admin_id]){ + return true; + } else { + return false; + } + } + + function isParentAdmin($item_id){ + return $this->isItemAdmin($this->tree->tree_data[$item_id]['parent_id']); + } + + function getItemHead($item_id){ + $head = ""; + if (($this->mode == "MoveItem" || $this->mode == "CopyItem") && ($this->isItemAdmin($item_id) || $this->isParentAdmin($item_id)) + && ($this->move_item_id != $item_id) && ($this->tree->tree_data[$this->move_item_id]['parent_id'] != $item_id) + && !$this->tree->isChildOf($this->move_item_id,$item_id)){ + $head .= "getSelf("cmd=Do" . $this->mode . "&item_id=$item_id")) . "\">" + . Icon::create('arr_2right', 'sort', ['title' => _("An dieser Stelle einfügen")])->asImg(16, ["alt" => _("An dieser Stelle einfügen")])." "; + } + $head .= parent::getItemHead($item_id); + if ($item_id != "root"){ + $head .= " (" . $this->tree->getNumEntries($item_id,true) . ") " ; + } + if ($item_id != $this->start_item_id && $this->isParentAdmin($item_id) && $item_id != $this->edit_item_id){ + $head .= ""; + if (!$this->tree->isFirstKid($item_id)){ + $head .= "getSelf("cmd=OrderItem&direction=up&item_id=$item_id")) . + "\">" . Icon::create('arr_2up', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach oben")]) . + ""; + } + if (!$this->tree->isLastKid($item_id)){ + $head .= "getSelf("cmd=OrderItem&direction=down&item_id=$item_id")) . + "\">" . Icon::create('arr_2down', 'sort')->asImg(['class' => 'text-top', 'title' => _("Element nach unten")]) . + ""; + } + $head .= " "; + } + return $head; + } + + function getItemMessage($item_id,$colspan = 1){ + $content = ""; + if (!empty($this->msg[$item_id])) { + $msg = explode("§",$this->msg[$item_id]); + $pics = [ + 'error' => Icon::create('decline', 'attention'), + 'info' => Icon::create('exclaim', 'inactive'), + 'msg' => Icon::create('accept', 'accept')]; + $content = "\n + + +
" . $pics[$msg[0]]->asImg(['class' => 'text-top']) . "" . $msg[1] . "
"; + } + return $content; + } + + function getSelf($param = "", $with_start_item = true){ + $url_params = "foo=" . DbView::get_uniqid(); + if ($this->mode) $url_params .= "&mode=" . $this->mode; + if ($with_start_item) $url_params .= "&start_item_id=" . $this->start_item_id; + if ($param) $url_params .= '&' . $param; + return parent::getSelf($url_params); + } +} +//test +//page_open(array("sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User")); +//include 'lib/include/html_head.inc.php'; +//include ('lib/seminar_open.php'); // initialise Stud.IP-Session +//$test = new StudipSemTreeViewAdmin(Request::quoted('start_item_id')); +//$test->showSemTree(); +//echo "
";
+//print_r($_open_items);
+//page_close();
+?>
diff --git a/lib/classes/StudipSemTreeViewSimple.class.php b/lib/classes/StudipSemTreeViewSimple.class.php
deleted file mode 100644
index e2dba76..0000000
--- a/lib/classes/StudipSemTreeViewSimple.class.php
+++ /dev/null
@@ -1,244 +0,0 @@
-
-// Suchi & Berg GmbH 
-// +---------------------------------------------------------------------------+
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or any later version.
-// +---------------------------------------------------------------------------+
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-// +---------------------------------------------------------------------------+
-
-/**
-* class to print out the seminar tree
-*
-* This class prints out a html representation a part of the tree
-*
-* @access   public
-* @author   André Noack 
-* @package
-*/
-class StudipSemTreeViewSimple
-{
-    var $tree;
-    var $show_entries;
-    var $start_item_id;
-    var $root_content;
-
-    /**
-    * constructor
-    *
-    */
-    public function __construct($start_item_id = 'root', $sem_number = false, $sem_status = false, $visible_only = false)
-    {
-        $this->start_item_id = ($start_item_id) ? $start_item_id : "root";
-        $this->root_content = $GLOBALS['UNI_INFO'];
-        $args = null;
-        if ($sem_number !== false){
-            $args['sem_number'] = $sem_number;
-        }
-        if ($sem_status !== false){
-            $args['sem_status'] =  $sem_status;
-        }
-        $args['visible_only'] = $visible_only;
-        $this->tree = TreeAbstract::GetInstance("StudipSemTree",$args);
-        $this->tree->enable_lonely_sem = false;
-        if (empty($this->tree->tree_data[$this->start_item_id])) {
-            $this->start_item_id = "root";
-        }
-    }
-
-    public function showSemTree($start_id = null)
-    {
-        echo '
-            
-                
-                    
-                    
-                
-                
-                    
-                
-                
-                    
-                
-            
-
- -
'. - '
'. - $this->getSemPath($start_id); - echo - '
-
-
'. - formatReady($this->tree->getValue($this->start_item_id, 'name')). - '
-
'; - if ($this->tree->getValue($this->start_item_id, 'info')) { - echo formatReady($this->tree->getValue($this->start_item_id, 'info')); - }else{ - echo _("Keine weitere Info vorhanden"); - } - echo'
-
-
'; - echo ' -
-
'; - if ($this->start_item_id != 'root') { - echo ' - ' . - Icon::create('arr_2left', 'clickable')->asImg(['class' => 'text-top', 'title' =>_('eine Ebene zurück')]) . - ''; - } else { - echo ' '; - } - echo ' -
'; - $num_all_entries = $this->showKids($this->start_item_id); - echo ' -
'; - $this->showContent($this->start_item_id, $num_all_entries); - echo ' -
'; - } - - public function showKids($item_id) - { - $num_kids = $this->tree->getNumKids($item_id); - $all_kids = $this->tree->getKids($item_id); - $kids = []; - if(!$GLOBALS['perm']->have_perm(Config::GetInstance()->getValue('SEM_TREE_SHOW_EMPTY_AREAS_PERM')) && $num_kids){ - foreach($all_kids as $kid){ - if($this->tree->getNumKids($kid) || $this->tree->getNumEntries($kid,true)) $kids[] = $kid; - } - $num_kids = count($kids); - } else { - $kids = $all_kids; - } - $num_all_entries = 0; - $kids_table = ' - - - -
- - -
    '; - } - } - if (!$num_kids){ - $kids_table .= "
  • "; - $kids_table .= _("Auf dieser Ebene existieren keine weiteren Unterebenen."); - $kids_table .= "
  • "; - } - - $kids_table .= "
"; - echo $kids_table; - return $num_all_entries; - } - - public function getInfoIcon($item_id) - { - $info = $item_id === 'root' ? $this->root_content : ''; - return $info ? tooltipicon(kill_format($info)) : ''; - } - - public function showContent($item_id, $num_all_entries = 0) - { - echo "\n
"; - if ($item_id != "root"){ - if ($num_entries = $this->tree->getNumEntries($item_id)){ - if ($this->show_entries != "level"){ - echo "getSelf("cmd=show_sem_range&item_id=$item_id")) ."\">"; - } - printf(_("%s Einträge auf dieser Ebene. "),$num_entries); - if ($this->show_entries != "level"){ - echo ""; - } - } else { - echo _("Keine Einträge auf dieser Ebene vorhanden!"); - } - if ($this->tree->hasKids($item_id) && $num_all_entries){ - echo "  /  "; - if ($this->show_entries != "sublevels"){ - if ($num_all_entries <= 100) echo "getSelf("cmd=show_sem_range&item_id={$this->start_item_id}_withkids")) ."\">"; - } - printf(_("%s Einträge in allen Unterebenen vorhanden"), $num_all_entries); - if ($this->show_entries != "sublevels"){ - echo ""; - } - } - } - echo "\n
"; - } - - public function getSemPath($start_id = null) - { - $ret = ''; - $parents = $this->tree->getParents($this->start_item_id); - if ($parents) { - $add_item = false; - $start_id = $start_id === null ? 'root' : $start_id; - for($i = count($parents) - 1; $i >= 0; --$i){ - if ($add_item || $start_id == $parents[$i]) { - $ret .= ($add_item === TRUE ? ' / ' : '') - . "getSelf("start_item_id={$parents[$i]}", false)). "\">". htmlReady($this->tree->getValue($parents[$i], "name")).""; - $add_item = true; - } - } - } - if ($this->start_item_id == "root") { - $ret = "getSelf("start_item_id=root",false)) . "\">" . $this->tree->root_name . ""; - } else { - $ret .= " / getSelf("start_item_id={$this->start_item_id}",false)) . "\">" . htmlReady($this->tree->getValue($this->start_item_id, "name")) . ""; - $ret .= " /  "; - } - return $ret; - } - - /** - * @return string url NOT escaped - */ - public function getSelf($param = "", $with_start_item = true) - { - $url_params = (($with_start_item) ? "start_item_id=" . $this->start_item_id . "&" : "") . $param ; - return URLHelper::getURL('?' . $url_params); - } -} -?> diff --git a/lib/classes/StudipSemTreeViewSimple.php b/lib/classes/StudipSemTreeViewSimple.php new file mode 100644 index 0000000..e2dba76 --- /dev/null +++ b/lib/classes/StudipSemTreeViewSimple.php @@ -0,0 +1,244 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* class to print out the seminar tree +* +* This class prints out a html representation a part of the tree +* +* @access public +* @author André Noack +* @package +*/ +class StudipSemTreeViewSimple +{ + var $tree; + var $show_entries; + var $start_item_id; + var $root_content; + + /** + * constructor + * + */ + public function __construct($start_item_id = 'root', $sem_number = false, $sem_status = false, $visible_only = false) + { + $this->start_item_id = ($start_item_id) ? $start_item_id : "root"; + $this->root_content = $GLOBALS['UNI_INFO']; + $args = null; + if ($sem_number !== false){ + $args['sem_number'] = $sem_number; + } + if ($sem_status !== false){ + $args['sem_status'] = $sem_status; + } + $args['visible_only'] = $visible_only; + $this->tree = TreeAbstract::GetInstance("StudipSemTree",$args); + $this->tree->enable_lonely_sem = false; + if (empty($this->tree->tree_data[$this->start_item_id])) { + $this->start_item_id = "root"; + } + } + + public function showSemTree($start_id = null) + { + echo ' + + + + + + + + + + + +
+
+ +
'. + '
'. + $this->getSemPath($start_id); + echo + '
+
+
'. + formatReady($this->tree->getValue($this->start_item_id, 'name')). + '
+
'; + if ($this->tree->getValue($this->start_item_id, 'info')) { + echo formatReady($this->tree->getValue($this->start_item_id, 'info')); + }else{ + echo _("Keine weitere Info vorhanden"); + } + echo'
+
+
'; + echo ' +
+
'; + if ($this->start_item_id != 'root') { + echo ' + ' . + Icon::create('arr_2left', 'clickable')->asImg(['class' => 'text-top', 'title' =>_('eine Ebene zurück')]) . + ''; + } else { + echo ' '; + } + echo ' +
'; + $num_all_entries = $this->showKids($this->start_item_id); + echo ' +
'; + $this->showContent($this->start_item_id, $num_all_entries); + echo ' +
'; + } + + public function showKids($item_id) + { + $num_kids = $this->tree->getNumKids($item_id); + $all_kids = $this->tree->getKids($item_id); + $kids = []; + if(!$GLOBALS['perm']->have_perm(Config::GetInstance()->getValue('SEM_TREE_SHOW_EMPTY_AREAS_PERM')) && $num_kids){ + foreach($all_kids as $kid){ + if($this->tree->getNumKids($kid) || $this->tree->getNumEntries($kid,true)) $kids[] = $kid; + } + $num_kids = count($kids); + } else { + $kids = $all_kids; + } + $num_all_entries = 0; + $kids_table = ' + + + +
+ + +
    '; + } + } + if (!$num_kids){ + $kids_table .= "
  • "; + $kids_table .= _("Auf dieser Ebene existieren keine weiteren Unterebenen."); + $kids_table .= "
  • "; + } + + $kids_table .= "
"; + echo $kids_table; + return $num_all_entries; + } + + public function getInfoIcon($item_id) + { + $info = $item_id === 'root' ? $this->root_content : ''; + return $info ? tooltipicon(kill_format($info)) : ''; + } + + public function showContent($item_id, $num_all_entries = 0) + { + echo "\n
"; + if ($item_id != "root"){ + if ($num_entries = $this->tree->getNumEntries($item_id)){ + if ($this->show_entries != "level"){ + echo "getSelf("cmd=show_sem_range&item_id=$item_id")) ."\">"; + } + printf(_("%s Einträge auf dieser Ebene. "),$num_entries); + if ($this->show_entries != "level"){ + echo ""; + } + } else { + echo _("Keine Einträge auf dieser Ebene vorhanden!"); + } + if ($this->tree->hasKids($item_id) && $num_all_entries){ + echo "  /  "; + if ($this->show_entries != "sublevels"){ + if ($num_all_entries <= 100) echo "getSelf("cmd=show_sem_range&item_id={$this->start_item_id}_withkids")) ."\">"; + } + printf(_("%s Einträge in allen Unterebenen vorhanden"), $num_all_entries); + if ($this->show_entries != "sublevels"){ + echo ""; + } + } + } + echo "\n
"; + } + + public function getSemPath($start_id = null) + { + $ret = ''; + $parents = $this->tree->getParents($this->start_item_id); + if ($parents) { + $add_item = false; + $start_id = $start_id === null ? 'root' : $start_id; + for($i = count($parents) - 1; $i >= 0; --$i){ + if ($add_item || $start_id == $parents[$i]) { + $ret .= ($add_item === TRUE ? ' / ' : '') + . "getSelf("start_item_id={$parents[$i]}", false)). "\">". htmlReady($this->tree->getValue($parents[$i], "name")).""; + $add_item = true; + } + } + } + if ($this->start_item_id == "root") { + $ret = "getSelf("start_item_id=root",false)) . "\">" . $this->tree->root_name . ""; + } else { + $ret .= " / getSelf("start_item_id={$this->start_item_id}",false)) . "\">" . htmlReady($this->tree->getValue($this->start_item_id, "name")) . ""; + $ret .= " /  "; + } + return $ret; + } + + /** + * @return string url NOT escaped + */ + public function getSelf($param = "", $with_start_item = true) + { + $url_params = (($with_start_item) ? "start_item_id=" . $this->start_item_id . "&" : "") . $param ; + return URLHelper::getURL('?' . $url_params); + } +} +?> diff --git a/lib/classes/StudipStudyAreaSelection.class.php b/lib/classes/StudipStudyAreaSelection.class.php deleted file mode 100644 index ea2ea6f..0000000 --- a/lib/classes/StudipStudyAreaSelection.class.php +++ /dev/null @@ -1,332 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -/** - * Objects of this class represent the state of the study area selection form. - * - * Note: Many of the methods return "$this" to let you easily cascade method - * calls: $selection->toggleShowAll()->setSelected("012345etc."); - * - * @package studip - * - * @author mlunzena - * @copyright (c) Authors - */ - -class StudipStudyAreaSelection { - - private $selected; - private $showAll; - - private $areas; - - private $searchKey; - private $searchResult; - - /** - * This constructor can be called with or without a course ID. If a course ID - * has been sent, the selected areas are populated by that course's already - * chosen study areas. If no course ID is given, it is assumed that you are - * creating a new course at the moment. - * - * @param string optional; the ID of the course to prepopulate the form - * with - * - * @return void - */ - function __construct($course_id = NULL) { - $this->selected = StudipStudyArea::getRootArea(); - $this->showAll = FALSE; - - $this->areas = []; - - $this->searchKey = ''; - $this->clearSearchResult(); - - if (isset($course_id)) { - $this->populateAreasForCourse($course_id); - } - } - - - /** - * This method populates this instance with the already chosen study areas. - * - * @param string the course's ID - * - * @return void - */ - private function populateAreasForCourse($id) { - $areas = StudipStudyArea::getStudyAreasForCourse($id); - $this->setAreas($areas); - $this->sortAreas(); - } - - - /** - * Sorts the internal representation of the areas by their paths according to - * the current locale. - * - * @return void - */ - private function sortAreas() - { - uasort($this->areas, function ($a, $b) { - return strcoll($a->getPath(' · '), $b->getPath(' · ')); - }); - } - - - /** - * @return string the current search term - */ - function getSearchKey() { - return $this->searchKey; - } - - - /** - * @param string a search term - * - * @return object this instance - */ - function setSearchKey($searchKey) { - $this->searchKey = (string) $searchKey; - - $this->clearSearchResult(); - return $this; - } - - - /** - * @return bool returns TRUE if the search key was set meaning that - * we are currently searching; returns FALSE otherwise - */ - function searched() { - return $this->searchKey !== ''; - } - - - /** - * Clears the current search result. - * - * @return object this instance - */ - function clearSearchResult() { - $this->searchResult = NULL; - return $this; - } - - - /** - * Returns an array of search results. This result is memoized for - * performance though setting the search key anew clears the memo again. - * - * @return array an array of search results - */ - function getSearchResult() { - - # no search key -> return empty array - if ($this->searchKey === '') { - return []; - } - - # not memoized yet, do so now - if (is_null($this->searchResult)) { - $this->searchResult = StudipStudyArea::search($this->searchKey); - usort($this->searchResult, [__CLASS__, 'sortSearchResult']); - } - - return $this->searchResult; - } - - static function sortSearchResult($a, $b) { - return strcmp($a->getPath('·'), $b->getPath('·')); - } - - - /** - * @return object the currently selected study area - */ - function getSelected() { - return $this->selected; - } - - - /** - * @param mixed either an MD5ish ID of the study area to select or the - * area object itself - * - * @return object this instance - */ - function setSelected($selected) { - if (!is_object($selected)) { - $this->selected = StudipStudyArea::find($selected); - } - else { - $this->selected = $selected; - } - return $this; - } - - - /** - * @return bool returns TRUE if the subtrees should be expanded - * completely or FALSE otherwise - */ - function getShowAll() { - return $this->showAll; - } - - - /** - * @param bool the new state of the expansion of subtrees - * - * @return object this instance - */ - function setShowAll($showAll) { - $this->showAll = $showAll; - return $this; - } - - - /** - * Toggles the state of the expansion of subtrees. - * - * @return object this instance - */ - function toggleShowAll() { - $this->showAll = !$this->showAll; - return $this; - } - - - /** - * Returns all the IDs of the selected study areas. - * - * @return array an array of MD5ish strings representing the IDs of the - * selected study areas - */ - function getAreaIDs() { - return array_keys($this->areas); - } - - - /** - * Returns all the selected study areas. - * - * @return array an array of StudipStudyArea representing the selected - * study areas - */ - function getAreas() { - return $this->areas; - } - - - /** - * Sets the study areas of this selection. One can provide either MD5ish ID - * strings or instances of StudipStudyArea. - * - * @param array an array of either MD5ish ID strings or StudipStudyAreas - * - * @return object the called instance itself - */ - function setAreas($areas) { - $this->areas = []; - foreach ($areas as $area) { - $this->add($area); - } - return $this; - } - - - /** - * @param mixed the MD5ish ID of a study area or the area object itself - * - * @return bool returns TRUE if this area is selected, FALSE otherwise - */ - function includes($area) { - $id = is_object($area) ? $area->getID() : $area; - return isset($this->areas[$id]); - } - - - /** - * @return integer returns the number of the selected study areas - */ - function size() { - return sizeof($this->areas); - } - - - /** - * This method adds an area to the selected study areas. - * - * @param string the MD5ish ID of the study area to add - * - * @return object this instance - */ - function add($area) { - # convert to an object - if (!is_object($area)) { - $area = StudipStudyArea::find($area); - } - $id = $area->getID(); - if (!isset($this->areas[$id])) { - $this->areas[$id] = $area; - } - $this->sortAreas(); - return $this; - } - - - /** - * This method removes an area from the selected study areas. - * - * @param string the MD5ish ID of the study area to add - * - * @return object this instance - */ - function remove($area) { - if (is_object($area)) { - $area = $area->getID(); - } - if (isset($this->areas[(string) $area])) { - unset($this->areas[$area]); - } - return $this; - } - - - /** - * Returns the trail -- the path from the root of the tree of study areas down - * to the currently selected area. - * - * # TODO (mlunzena) this has to be refactored as well - * - * @return array an array of study areas; currently each item is an - * hashmap containing the ID of each area using the key - * 'id' and the name of the study area using the key 'name' - */ - function getTrail() { - $area = $this->selected; - $trail = [$area->getID() => $area]; - while ($parent = $area->getParent()) { - $trail[$parent->getID()] = $parent; - $area = $parent; - } - $trail[StudipStudyArea::ROOT] = StudipStudyArea::getRootArea(); - return array_reverse($trail, TRUE); - } -} diff --git a/lib/classes/StudipStudyAreaSelection.php b/lib/classes/StudipStudyAreaSelection.php new file mode 100644 index 0000000..ea2ea6f --- /dev/null +++ b/lib/classes/StudipStudyAreaSelection.php @@ -0,0 +1,332 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +/** + * Objects of this class represent the state of the study area selection form. + * + * Note: Many of the methods return "$this" to let you easily cascade method + * calls: $selection->toggleShowAll()->setSelected("012345etc."); + * + * @package studip + * + * @author mlunzena + * @copyright (c) Authors + */ + +class StudipStudyAreaSelection { + + private $selected; + private $showAll; + + private $areas; + + private $searchKey; + private $searchResult; + + /** + * This constructor can be called with or without a course ID. If a course ID + * has been sent, the selected areas are populated by that course's already + * chosen study areas. If no course ID is given, it is assumed that you are + * creating a new course at the moment. + * + * @param string optional; the ID of the course to prepopulate the form + * with + * + * @return void + */ + function __construct($course_id = NULL) { + $this->selected = StudipStudyArea::getRootArea(); + $this->showAll = FALSE; + + $this->areas = []; + + $this->searchKey = ''; + $this->clearSearchResult(); + + if (isset($course_id)) { + $this->populateAreasForCourse($course_id); + } + } + + + /** + * This method populates this instance with the already chosen study areas. + * + * @param string the course's ID + * + * @return void + */ + private function populateAreasForCourse($id) { + $areas = StudipStudyArea::getStudyAreasForCourse($id); + $this->setAreas($areas); + $this->sortAreas(); + } + + + /** + * Sorts the internal representation of the areas by their paths according to + * the current locale. + * + * @return void + */ + private function sortAreas() + { + uasort($this->areas, function ($a, $b) { + return strcoll($a->getPath(' · '), $b->getPath(' · ')); + }); + } + + + /** + * @return string the current search term + */ + function getSearchKey() { + return $this->searchKey; + } + + + /** + * @param string a search term + * + * @return object this instance + */ + function setSearchKey($searchKey) { + $this->searchKey = (string) $searchKey; + + $this->clearSearchResult(); + return $this; + } + + + /** + * @return bool returns TRUE if the search key was set meaning that + * we are currently searching; returns FALSE otherwise + */ + function searched() { + return $this->searchKey !== ''; + } + + + /** + * Clears the current search result. + * + * @return object this instance + */ + function clearSearchResult() { + $this->searchResult = NULL; + return $this; + } + + + /** + * Returns an array of search results. This result is memoized for + * performance though setting the search key anew clears the memo again. + * + * @return array an array of search results + */ + function getSearchResult() { + + # no search key -> return empty array + if ($this->searchKey === '') { + return []; + } + + # not memoized yet, do so now + if (is_null($this->searchResult)) { + $this->searchResult = StudipStudyArea::search($this->searchKey); + usort($this->searchResult, [__CLASS__, 'sortSearchResult']); + } + + return $this->searchResult; + } + + static function sortSearchResult($a, $b) { + return strcmp($a->getPath('·'), $b->getPath('·')); + } + + + /** + * @return object the currently selected study area + */ + function getSelected() { + return $this->selected; + } + + + /** + * @param mixed either an MD5ish ID of the study area to select or the + * area object itself + * + * @return object this instance + */ + function setSelected($selected) { + if (!is_object($selected)) { + $this->selected = StudipStudyArea::find($selected); + } + else { + $this->selected = $selected; + } + return $this; + } + + + /** + * @return bool returns TRUE if the subtrees should be expanded + * completely or FALSE otherwise + */ + function getShowAll() { + return $this->showAll; + } + + + /** + * @param bool the new state of the expansion of subtrees + * + * @return object this instance + */ + function setShowAll($showAll) { + $this->showAll = $showAll; + return $this; + } + + + /** + * Toggles the state of the expansion of subtrees. + * + * @return object this instance + */ + function toggleShowAll() { + $this->showAll = !$this->showAll; + return $this; + } + + + /** + * Returns all the IDs of the selected study areas. + * + * @return array an array of MD5ish strings representing the IDs of the + * selected study areas + */ + function getAreaIDs() { + return array_keys($this->areas); + } + + + /** + * Returns all the selected study areas. + * + * @return array an array of StudipStudyArea representing the selected + * study areas + */ + function getAreas() { + return $this->areas; + } + + + /** + * Sets the study areas of this selection. One can provide either MD5ish ID + * strings or instances of StudipStudyArea. + * + * @param array an array of either MD5ish ID strings or StudipStudyAreas + * + * @return object the called instance itself + */ + function setAreas($areas) { + $this->areas = []; + foreach ($areas as $area) { + $this->add($area); + } + return $this; + } + + + /** + * @param mixed the MD5ish ID of a study area or the area object itself + * + * @return bool returns TRUE if this area is selected, FALSE otherwise + */ + function includes($area) { + $id = is_object($area) ? $area->getID() : $area; + return isset($this->areas[$id]); + } + + + /** + * @return integer returns the number of the selected study areas + */ + function size() { + return sizeof($this->areas); + } + + + /** + * This method adds an area to the selected study areas. + * + * @param string the MD5ish ID of the study area to add + * + * @return object this instance + */ + function add($area) { + # convert to an object + if (!is_object($area)) { + $area = StudipStudyArea::find($area); + } + $id = $area->getID(); + if (!isset($this->areas[$id])) { + $this->areas[$id] = $area; + } + $this->sortAreas(); + return $this; + } + + + /** + * This method removes an area from the selected study areas. + * + * @param string the MD5ish ID of the study area to add + * + * @return object this instance + */ + function remove($area) { + if (is_object($area)) { + $area = $area->getID(); + } + if (isset($this->areas[(string) $area])) { + unset($this->areas[$area]); + } + return $this; + } + + + /** + * Returns the trail -- the path from the root of the tree of study areas down + * to the currently selected area. + * + * # TODO (mlunzena) this has to be refactored as well + * + * @return array an array of study areas; currently each item is an + * hashmap containing the ID of each area using the key + * 'id' and the name of the study area using the key 'name' + */ + function getTrail() { + $area = $this->selected; + $trail = [$area->getID() => $area]; + while ($parent = $area->getParent()) { + $trail[$parent->getID()] = $parent; + $area = $parent; + } + $trail[StudipStudyArea::ROOT] = StudipStudyArea::getRootArea(); + return array_reverse($trail, TRUE); + } +} diff --git a/lib/classes/StudygroupAvatar.class.php b/lib/classes/StudygroupAvatar.class.php deleted file mode 100644 index 8e27f8e..0000000 --- a/lib/classes/StudygroupAvatar.class.php +++ /dev/null @@ -1,16 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - - -/** -* Abstract Base Class to handle in-memory tree structures -* -* This class provides an interface to basic handling of structure of tree structures -* -* @access public -* @author André Noack -* @package -*/ -class TreeAbstract { - - /** - * the name of the root element - * - * @access private - * @var string $root_name - */ - var $root_name; - /** - * object to handle database queries - * - * @access private - * @var object DbView $view - */ - var $view; - /** - * array containing all tree items - * - * associative array, key is an unique identifier (eg primary key from DB table) - * value is another assoc. array containing the other fieldname/fieldvalue pairs - * these fieldnames must be used : - * parent_id, name, priority - * @access public - * @var array $tree_data - */ - var $tree_data = []; - /** - * array containing the direct childs of all items - * - * assoc. array, key is one from $tree_data, value is numeric array with keys from childs - * @access private - * @var array $tree_childs - */ - var $tree_childs = []; - - /** - * array containing the number of direct childs of all items - * - * assoc. array, key is one from $tree_data - * @access private - * @var array $tree_num_childs - */ - var $tree_num_childs = []; - - var $index_offset = 0; - - /** - * static method used to ensure that only one instance exists - * - * use this method if you need a reference to the tree object
- * usage:
$my_tree = StudipRangeTree::GetInstance("name_of_tree_class")
- * - * @param string $class_name the name of the used tree_class - * @param mixed $args argumentlist passed to the constructor in the tree_class (if needed) - * @return mixed always an object, type is one of AbstractTree s childclasses - */ - public static function GetInstance($class_name, $args = null, $invalidate_cache = false) - { - static $tree_instance; - $class_hash = ''; - if ($args){ - $class_hash = $class_name . "_" . md5(serialize($args)); - } elseif ($args === false && is_array($tree_instance)){ - foreach ($tree_instance as $key => $value){ - $tmp_name = explode("_",$key); - if ($tmp_name[0] == $class_name){ - $class_hash = $key; - break; - } - } - if (!$class_hash){ - $class_hash = $class_name; - } - } else { - $class_hash = $class_name; - } - if (empty($tree_instance[$class_hash]) || $invalidate_cache){ - $tree_instance[$class_hash] = new $class_name($args); - } - - return $tree_instance[$class_hash]; - } - - /** - * constructor - * - * do not use directly, call &GetInstance() - */ - protected function __construct() - { - $this->view = new DbView(); - $this->init(); - } - - /** - * initializes the tree - * - * stores all tree items in array $tree_data - * must be overriden - */ - public function init() - { - $this->tree_childs = []; - $this->tree_num_childs = []; - $this->tree_data = []; - $this->index_offset = 0; - $this->tree_data['root'] = ['parent_id' => null, 'name' => &$this->root_name, 'index' => 0]; - } - - /** - * store one item in tree_data array - * - * store one item in tree_data array - * - * @param string $item_id - * @param string $parent_id - * @param string $name - * @param integer $priority - * - */ - public function storeItem($item_id,$parent_id,$name,$priority) - { - $this->tree_data[$item_id]["parent_id"] = $parent_id; - $this->tree_data[$item_id]["priority"] = $priority; - $this->tree_data[$item_id]["name"] = $name; - $this->tree_childs[$parent_id][] = $item_id; - if (empty($this->tree_num_childs[$parent_id])) { - $this->tree_num_childs[$parent_id] = 0; - } - $this->tree_num_childs[$parent_id]++; - return; - } - - /** - * build an index for sorting purpose - * - * build an index for sorting purpose - * - * @param string $item_id - * - */ - public function buildIndex($item_id = false) - { - if ($item_id === false && $this->index_offset > 0) { - return; - } - if (!$item_id) { - $item_id = "root"; - } - $this->tree_data[$item_id]['index'] = $this->index_offset; - ++$this->index_offset; - if (($num_kids = $this->getNumKids($item_id))) { - for($i = 0; $i < $num_kids; ++$i){ - $this->buildIndex($this->tree_childs[$item_id][$i]); - } - } - return; - } - - /** - * returns all direct kids - * - * @param string $item_id - * @return array - */ - public function getKids($item_id) - { - return (isset($this->tree_childs[$item_id]) && is_array($this->tree_childs[$item_id])) ? $this->tree_childs[$item_id] : []; - } - - /** - * returns the number of all direct kids - * - * @param string $item_id - * @param bool $in_recursion - * @return int - */ - public function getNumKids($item_id) - { - if(!isset($this->tree_num_childs[$item_id])){ - $this->tree_num_childs[$item_id] = (!empty($this->tree_childs[$item_id]) && is_array($this->tree_childs[$item_id])) ? count($this->tree_childs[$item_id]) : 0; - } - return $this->tree_num_childs[$item_id]; - } - - /** - * returns all direct kids and kids of kids and so on... - * - * @param string $item_id - * @param bool $in_recursion only used in recursion - * @return array - */ - public function getKidsKids($item_id, $in_recursion = false) - { - static $kidskids; - if (!$kidskids || !$in_recursion){ - $kidskids = []; - } - $num_kids = $this->getNumKids($item_id); - if ($num_kids){ - $kids = $this->getKids($item_id); - $kidskids = array_merge((array)$kidskids, (array)$kids); - for ($i = 0; $i < $num_kids; ++$i){ - $this->getKidsKids($kids[$i],true); - } - } - return (!$in_recursion) ? $kidskids : []; - } - - /** - * returns the number of all kids and kidskids... - * - * @param string $item_id - * @param bool $in_recursion - * @return int - */ - public function getNumKidsKids($item_id, $in_recursion = false) - { - static $num_kidskids; - if (!$num_kidskids || !$in_recursion){ - $num_kidskids = 0; - } - $num_kids = $this->getNumKids($item_id); - if ($num_kids){ - $kids = $this->getKids($item_id); - $num_kidskids += $num_kids; - for ($i = 0; $i < $num_kids; ++$i){ - $this->getNumKidsKids($kids[$i],true); - } - } - return (!$in_recursion) ? $num_kidskids : 0; - } - - /** - * checks if item is the last kid - * - * @param string $item_id - * @return boolean - */ - public function isLastKid($item_id) - { - $parent_id = $this->tree_data[$item_id]['parent_id']; - $num_kids = $this->getNumKids($parent_id); - if (!$parent_id || !$num_kids) { - return false; - } - return $this->tree_childs[$parent_id][$num_kids-1] == $item_id; - } - - /** - * checks if item is the first kid - * - * @param string $item_id - * @return boolean - */ - public function isFirstKid($item_id) - { - $parent_id = $this->tree_data[$item_id]['parent_id']; - $num_kids = $this->getNumKids($parent_id); - if (!$parent_id || !$num_kids) { - return false; - } - return $this->tree_childs[$parent_id][0] == $item_id; - } - - /** - * checks if given item is a kid or kidkid...of given ancestor - * - * checks if given item is a kid or kidkid...of given ancestor - * - * @param string $ancestor_id - * @param string $item_id - * @return boolean - */ - public function isChildOf($ancestor_id,$item_id) - { - return in_array($item_id,$this->getKidsKids($ancestor_id)); - } - - /** - * checks if item has one or more kids - * - * @param string $item_id - * @return boolean - */ - public function hasKids($item_id) - { - return $this->getNumKids($item_id) > 0; - } - - /** - * Returns tree path - * - * returns a string with the item and all parents separated with a slash - * - * @param string $item_id - * @return string - */ - public function getItemPath($item_id) - { - if (!$this->tree_data[$item_id]) { - return false; - } - - $path = $this->tree_data[$item_id]['name']; - while($item_id && $item_id !== 'root') { - $item_id = $this->tree_data[$item_id]['parent_id']; - $path = $this->tree_data[$item_id]['name'] . " / " . $path; - } - return $path; - } - - /** - * Returns tree path as array of item_id s - * - * returns an array containing all parents of given item - * - * @param string $item_id - * @return array - */ - public function getParents($item_id) - { - if (empty($this->tree_data[$item_id])) { - return []; - } - - $result = []; - while ($item_id && $item_id !== 'root') { - $item_id = $this->tree_data[$item_id]['parent_id']; - $result[] = $item_id; - } - return $result; - } - - public function getShortPath($item_id, $length = null, $delimeter = ">", $offset = 0) - { - if (!$this->tree_data[$item_id] || $item_id === 'root') { - return false; - } - $parents = array_reverse($this->getParents($item_id)); - array_shift($parents); - array_push($parents, $item_id); - $that = $this; - $parents_names = array_map(function($i) use ($that) {return $that->tree_data[$i]['name'];}, array_slice($parents, $offset, $length ? $length : null)); - return join(" $delimeter ", $parents_names); - } - - /** - * Returns the maximum priority value from a parents child - * - * @param string $parent_id - * @return int - */ - public function getMaxPriority($parent_id) - { - $children = $this->getKids($parent_id); - $last = $this->getNumKids($parent_id) - 1; - return (int) $this->tree_data[$children[$last]]['priority']; - } - - public function getNumEntries($item_id, $num_entries_from_kids = false) - { - if (!$num_entries_from_kids || !$this->hasKids($item_id)){ - return $this->tree_data[$item_id]["entries"]; - } else { - return $this->getNumEntriesKids($item_id); - } - } - - public function getNumEntriesKids($item_id, $in_recursion = false) - { - static $num_entries; - if (!$in_recursion){ - $num_entries = 0; - } - $num_entries += $this->tree_data[$item_id]["entries"]; - $num_kids = $this->getNumKids($item_id); - if ($num_kids){ - $kids = $this->getKids($item_id); - for ($i = 0; $i < $num_kids; ++$i){ - $this->getNumEntriesKids($kids[$i],true); - } - } - return (!$in_recursion) ? $num_entries : null; - } - - public function getValue($item_id, $field) - { - return isset($this->tree_data[$item_id][$field]) - ? $this->tree_data[$item_id][$field] - : null; - } -} diff --git a/lib/classes/TreeAbstract.php b/lib/classes/TreeAbstract.php new file mode 100644 index 0000000..ccdb6e1 --- /dev/null +++ b/lib/classes/TreeAbstract.php @@ -0,0 +1,431 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + + +/** +* Abstract Base Class to handle in-memory tree structures +* +* This class provides an interface to basic handling of structure of tree structures +* +* @access public +* @author André Noack +* @package +*/ +class TreeAbstract { + + /** + * the name of the root element + * + * @access private + * @var string $root_name + */ + var $root_name; + /** + * object to handle database queries + * + * @access private + * @var object DbView $view + */ + var $view; + /** + * array containing all tree items + * + * associative array, key is an unique identifier (eg primary key from DB table) + * value is another assoc. array containing the other fieldname/fieldvalue pairs + * these fieldnames must be used : + * parent_id, name, priority + * @access public + * @var array $tree_data + */ + var $tree_data = []; + /** + * array containing the direct childs of all items + * + * assoc. array, key is one from $tree_data, value is numeric array with keys from childs + * @access private + * @var array $tree_childs + */ + var $tree_childs = []; + + /** + * array containing the number of direct childs of all items + * + * assoc. array, key is one from $tree_data + * @access private + * @var array $tree_num_childs + */ + var $tree_num_childs = []; + + var $index_offset = 0; + + /** + * static method used to ensure that only one instance exists + * + * use this method if you need a reference to the tree object
+ * usage:
$my_tree = StudipRangeTree::GetInstance("name_of_tree_class")
+ * + * @param string $class_name the name of the used tree_class + * @param mixed $args argumentlist passed to the constructor in the tree_class (if needed) + * @return mixed always an object, type is one of AbstractTree s childclasses + */ + public static function GetInstance($class_name, $args = null, $invalidate_cache = false) + { + static $tree_instance; + $class_hash = ''; + if ($args){ + $class_hash = $class_name . "_" . md5(serialize($args)); + } elseif ($args === false && is_array($tree_instance)){ + foreach ($tree_instance as $key => $value){ + $tmp_name = explode("_",$key); + if ($tmp_name[0] == $class_name){ + $class_hash = $key; + break; + } + } + if (!$class_hash){ + $class_hash = $class_name; + } + } else { + $class_hash = $class_name; + } + if (empty($tree_instance[$class_hash]) || $invalidate_cache){ + $tree_instance[$class_hash] = new $class_name($args); + } + + return $tree_instance[$class_hash]; + } + + /** + * constructor + * + * do not use directly, call &GetInstance() + */ + protected function __construct() + { + $this->view = new DbView(); + $this->init(); + } + + /** + * initializes the tree + * + * stores all tree items in array $tree_data + * must be overriden + */ + public function init() + { + $this->tree_childs = []; + $this->tree_num_childs = []; + $this->tree_data = []; + $this->index_offset = 0; + $this->tree_data['root'] = ['parent_id' => null, 'name' => &$this->root_name, 'index' => 0]; + } + + /** + * store one item in tree_data array + * + * store one item in tree_data array + * + * @param string $item_id + * @param string $parent_id + * @param string $name + * @param integer $priority + * + */ + public function storeItem($item_id,$parent_id,$name,$priority) + { + $this->tree_data[$item_id]["parent_id"] = $parent_id; + $this->tree_data[$item_id]["priority"] = $priority; + $this->tree_data[$item_id]["name"] = $name; + $this->tree_childs[$parent_id][] = $item_id; + if (empty($this->tree_num_childs[$parent_id])) { + $this->tree_num_childs[$parent_id] = 0; + } + $this->tree_num_childs[$parent_id]++; + return; + } + + /** + * build an index for sorting purpose + * + * build an index for sorting purpose + * + * @param string $item_id + * + */ + public function buildIndex($item_id = false) + { + if ($item_id === false && $this->index_offset > 0) { + return; + } + if (!$item_id) { + $item_id = "root"; + } + $this->tree_data[$item_id]['index'] = $this->index_offset; + ++$this->index_offset; + if (($num_kids = $this->getNumKids($item_id))) { + for($i = 0; $i < $num_kids; ++$i){ + $this->buildIndex($this->tree_childs[$item_id][$i]); + } + } + return; + } + + /** + * returns all direct kids + * + * @param string $item_id + * @return array + */ + public function getKids($item_id) + { + return (isset($this->tree_childs[$item_id]) && is_array($this->tree_childs[$item_id])) ? $this->tree_childs[$item_id] : []; + } + + /** + * returns the number of all direct kids + * + * @param string $item_id + * @param bool $in_recursion + * @return int + */ + public function getNumKids($item_id) + { + if(!isset($this->tree_num_childs[$item_id])){ + $this->tree_num_childs[$item_id] = (!empty($this->tree_childs[$item_id]) && is_array($this->tree_childs[$item_id])) ? count($this->tree_childs[$item_id]) : 0; + } + return $this->tree_num_childs[$item_id]; + } + + /** + * returns all direct kids and kids of kids and so on... + * + * @param string $item_id + * @param bool $in_recursion only used in recursion + * @return array + */ + public function getKidsKids($item_id, $in_recursion = false) + { + static $kidskids; + if (!$kidskids || !$in_recursion){ + $kidskids = []; + } + $num_kids = $this->getNumKids($item_id); + if ($num_kids){ + $kids = $this->getKids($item_id); + $kidskids = array_merge((array)$kidskids, (array)$kids); + for ($i = 0; $i < $num_kids; ++$i){ + $this->getKidsKids($kids[$i],true); + } + } + return (!$in_recursion) ? $kidskids : []; + } + + /** + * returns the number of all kids and kidskids... + * + * @param string $item_id + * @param bool $in_recursion + * @return int + */ + public function getNumKidsKids($item_id, $in_recursion = false) + { + static $num_kidskids; + if (!$num_kidskids || !$in_recursion){ + $num_kidskids = 0; + } + $num_kids = $this->getNumKids($item_id); + if ($num_kids){ + $kids = $this->getKids($item_id); + $num_kidskids += $num_kids; + for ($i = 0; $i < $num_kids; ++$i){ + $this->getNumKidsKids($kids[$i],true); + } + } + return (!$in_recursion) ? $num_kidskids : 0; + } + + /** + * checks if item is the last kid + * + * @param string $item_id + * @return boolean + */ + public function isLastKid($item_id) + { + $parent_id = $this->tree_data[$item_id]['parent_id']; + $num_kids = $this->getNumKids($parent_id); + if (!$parent_id || !$num_kids) { + return false; + } + return $this->tree_childs[$parent_id][$num_kids-1] == $item_id; + } + + /** + * checks if item is the first kid + * + * @param string $item_id + * @return boolean + */ + public function isFirstKid($item_id) + { + $parent_id = $this->tree_data[$item_id]['parent_id']; + $num_kids = $this->getNumKids($parent_id); + if (!$parent_id || !$num_kids) { + return false; + } + return $this->tree_childs[$parent_id][0] == $item_id; + } + + /** + * checks if given item is a kid or kidkid...of given ancestor + * + * checks if given item is a kid or kidkid...of given ancestor + * + * @param string $ancestor_id + * @param string $item_id + * @return boolean + */ + public function isChildOf($ancestor_id,$item_id) + { + return in_array($item_id,$this->getKidsKids($ancestor_id)); + } + + /** + * checks if item has one or more kids + * + * @param string $item_id + * @return boolean + */ + public function hasKids($item_id) + { + return $this->getNumKids($item_id) > 0; + } + + /** + * Returns tree path + * + * returns a string with the item and all parents separated with a slash + * + * @param string $item_id + * @return string + */ + public function getItemPath($item_id) + { + if (!$this->tree_data[$item_id]) { + return false; + } + + $path = $this->tree_data[$item_id]['name']; + while($item_id && $item_id !== 'root') { + $item_id = $this->tree_data[$item_id]['parent_id']; + $path = $this->tree_data[$item_id]['name'] . " / " . $path; + } + return $path; + } + + /** + * Returns tree path as array of item_id s + * + * returns an array containing all parents of given item + * + * @param string $item_id + * @return array + */ + public function getParents($item_id) + { + if (empty($this->tree_data[$item_id])) { + return []; + } + + $result = []; + while ($item_id && $item_id !== 'root') { + $item_id = $this->tree_data[$item_id]['parent_id']; + $result[] = $item_id; + } + return $result; + } + + public function getShortPath($item_id, $length = null, $delimeter = ">", $offset = 0) + { + if (!$this->tree_data[$item_id] || $item_id === 'root') { + return false; + } + $parents = array_reverse($this->getParents($item_id)); + array_shift($parents); + array_push($parents, $item_id); + $that = $this; + $parents_names = array_map(function($i) use ($that) {return $that->tree_data[$i]['name'];}, array_slice($parents, $offset, $length ? $length : null)); + return join(" $delimeter ", $parents_names); + } + + /** + * Returns the maximum priority value from a parents child + * + * @param string $parent_id + * @return int + */ + public function getMaxPriority($parent_id) + { + $children = $this->getKids($parent_id); + $last = $this->getNumKids($parent_id) - 1; + return (int) $this->tree_data[$children[$last]]['priority']; + } + + public function getNumEntries($item_id, $num_entries_from_kids = false) + { + if (!$num_entries_from_kids || !$this->hasKids($item_id)){ + return $this->tree_data[$item_id]["entries"]; + } else { + return $this->getNumEntriesKids($item_id); + } + } + + public function getNumEntriesKids($item_id, $in_recursion = false) + { + static $num_entries; + if (!$in_recursion){ + $num_entries = 0; + } + $num_entries += $this->tree_data[$item_id]["entries"]; + $num_kids = $this->getNumKids($item_id); + if ($num_kids){ + $kids = $this->getKids($item_id); + for ($i = 0; $i < $num_kids; ++$i){ + $this->getNumEntriesKids($kids[$i],true); + } + } + return (!$in_recursion) ? $num_entries : null; + } + + public function getValue($item_id, $field) + { + return isset($this->tree_data[$item_id][$field]) + ? $this->tree_data[$item_id][$field] + : null; + } +} diff --git a/lib/classes/TreeView.class.php b/lib/classes/TreeView.class.php deleted file mode 100644 index 243a9b9..0000000 --- a/lib/classes/TreeView.class.php +++ /dev/null @@ -1,446 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** -* Class to print out html represantation of a tree object based on TreeAbstract.class.php -* -* Class to print out html represantation of a tree object based on TreeAbstract.class.php -* -* @access public -* @author André Noack -* @package -*/ -class TreeView { - - /** - * Reference to the tree structure - * - * @access private - * @var object StudipRangeTree $tree - */ - var $tree; - /** - * name of used tree class - * - * @access private - * @var string $tree_class_name - */ - var $tree_class_name; - /** - * contains the item with the current html anchor - * - * @access public - * @var string $anchor - */ - var $anchor; - /** - * array containing all open items - * - * this is a reference to a global session variable, managed by PHPLib - * @access public - * @var array $open_items - */ - var $open_items; - /** - * array containing all open item nodes - * - * this is a reference to a global session variable, managed by PHPLib - * @access public - * @var array $open_ranges - */ - var $open_ranges; - /** - * the item to start with - * - * @access private - * @var string $start_item_id - */ - var $start_item_id; - /** - * the content of the root element - * - * @access public - * @var string $root_content - */ - var $root_content; - - /** - * the maximum amount of columns in a title - * - * @access public - * @var string $max_cols - */ - var $max_cols = 80; - - /** - * draw red icons - * - * @access public - * @var boolean $use_aging - */ - var $use_aging = false; - var $pic_open; - var $pic_close; - - /** - * constructor - * - * @access public - * @param string $tree_class_name name of used tree class - * @param mixed $args argument passed to the tree class - */ - public function __construct($tree_class_name,$args = null) - { - $this->tree_class_name = $tree_class_name; - $this->tree = TreeAbstract::GetInstance($tree_class_name, $args); - // TODO Die Logik hinter forumgrau2 und forumgraurunt2 muss - // komplett erneuert werden; dann können auch Instanzen der - // Klasse "Icon" verwendet werden. - $this->pic_open = $this->use_aging - ? 'forumgraurunt2.png' - : 'icons/blue/arr_1down.svg'; - $this->pic_close = $this->use_aging - ? 'forumgrau2.png' - : 'icons/blue/arr_1right.svg'; - - URLHelper::bindLinkParam('open_ranges', $this->open_ranges); - URLHelper::bindLinkParam('open_items', $this->open_items); - - $this->handleOpenRanges(); - } - - /** - * manages the link parameters used for the open/close thing - * - * @access private - */ - private function handleOpenRanges() - { - $close_range = Request::option('close_range'); - if ($close_range) { - if ($close_range === 'root'){ - $this->open_ranges = null; - $this->open_items = null; - } else { - $kidskids = $this->tree->getKidsKids($close_range); - $kidskids[] = $close_range; - foreach ($kidskids as $kid) { - unset($this->open_ranges[$kid]); - unset($this->open_items[$kid]); - } - } - $this->anchor = $close_range; - } - - $open_range = Request::option('open_range'); - if ($open_range) { - $kidskids = $this->tree->getKidsKids($open_range); - $kidskids[] = $open_range; - foreach ($kidskids as $kid) { - $this->open_ranges[$kid] = true; - } - $this->anchor = $open_range; - } - - $toggle_item = Request::option('close_item') ?: Request::option('open_item'); - if ($toggle_item){ - if (!empty($this->open_items[$toggle_item])) { - unset($this->open_items[$toggle_item]); - } else { - $this->openItem($toggle_item); - $this->openRange($toggle_item); - } - $this->anchor = $toggle_item; - } - - if (Request::option('item_id')) { - $this->anchor = Request::option('item_id'); - } - } - - function openItem($item_id) - { - $this->open_items[$item_id] = true; - $this->openRange($this->tree->tree_data[$item_id]['parent_id']); - } - - function openRange($item_id) - { - $this->open_ranges[$item_id] = true; - - $parents = $this->tree->getParents($item_id); - foreach ($parents as $parent) { - $this->open_ranges[$parent] = true; - } - } - - /** - * prints out the tree beginning with a given item - * - * @access public - * @param string $item_id - */ - function showTree($item_id = "root"){ - $items = []; - if (!is_array($item_id)){ - $items[0] = $item_id; - $this->start_item_id = $item_id; - } else { - $items = $item_id; - } - $num_items = count($items); - for ($j = 0; $j < $num_items; ++$j){ - $this->printLevelOutput($items[$j]); - $this->printItemOutput($items[$j]); - if ($this->tree->hasKids($items[$j]) && !empty($this->open_ranges[$items[$j]])) { - $this->showTree($this->tree->tree_childs[$items[$j]]); - } - } - return; -} - - /** - * prints out the lines before an item ("Strichlogik" (c) rstockm) - * - * @access private - * @param string $item_id - */ - function printLevelOutput($item_id) - { - $level_output = ""; - if ($item_id != $this->start_item_id){ - if ($this->tree->isLastKid($item_id)) - $level_output = "" - . Assets::img('forumstrich2.gif') - . ""; //last - else - $level_output = "" - . Assets::img('forumstrich3.gif') - . ""; //crossing - $parent_id = $item_id; - while($this->tree->tree_data[$parent_id]['parent_id'] != $this->start_item_id){ - $parent_id = $this->tree->tree_data[$parent_id]['parent_id']; - if ($this->tree->isLastKid($parent_id)) - $level_output = "" - . Assets::img('forumleer.gif', ['size' => '10@20']) - . "" . $level_output; //nothing - else - $level_output = "" - . Assets::img('forumstrich.gif') - . "" . $level_output; //vertical line - } - } - echo "\n$level_output"; - return; - } - - /** - * prints out one item - * - * @access private - * @param string $item_id - */ - function printItemOutput($item_id) - { - echo $this->getItemHeadPics($item_id); - echo "\n
"; - if ($this->anchor == $item_id) - echo ""; - echo Assets::img('forumleer.gif', ['size' => '1@20']); - if ($this->anchor == $item_id) - echo ""; - echo "\n"; - echo $this->getItemHead($item_id); - echo "
"; - if (!empty($this->open_items[$item_id])) { - $this->printItemDetails($item_id); - } - return; - } - - /** - * prints out the details for an item, if item is open - * - * @access private - * @param string $item_id - */ - function printItemDetails($item_id){ - $level_output = ''; - if (!$this->tree->hasKids($item_id) || !$this->open_ranges[$item_id] || $item_id == $this->start_item_id) - $level_output = "" - . Assets::img('forumleer.gif', ['size' => '10@20']) - . "" . $level_output; - else - $level_output = "" - . Assets::img('forumleer.gif', ['size' => '10@20']) - . "" . $level_output; - - if (($this->tree->isLastKid($item_id) && !($item_id == $this->start_item_id)) || (!$this->open_ranges[$item_id] && $item_id == $this->start_item_id) || ($item_id == $this->start_item_id && !$this->tree->hasKids($item_id))) - $level_output = "" - . Assets::img('forumleer.gif', ['size' => '10@20']) - . "" . $level_output; - else - $level_output = "" - . Assets::img('forumleer.gif', ['size' => '10@20']) - . "" . $level_output; - if ($item_id != $this->start_item_id){ - $parent_id = $item_id; - while($this->tree->tree_data[$parent_id]['parent_id'] != $this->start_item_id){ - $parent_id = $this->tree->tree_data[$parent_id]['parent_id']; - if ($this->tree->isLastKid($parent_id)) - $level_output = "" - . Assets::img('forumleer.gif', ['size' => '10@20']) - . "" . $level_output; //nothing - else - $level_output = "" - . Assets::img('forumleer.gif', ['size' => '10@20']) - . "" . ($level_output ?? ''); //vertical line - } - } - //$level_output = "" . $level_output; - - echo "$level_output"; - echo "

"; - echo $this->getItemContent($item_id); - echo "
"; - } - - /** - * returns html for the icons in front of the name of the item - * - * @access private - * @param string $item_id - * @return string - */ - function getItemHeadPics($item_id) - { - $head = $this->getItemHeadFrontPic($item_id); - $head .= "\n"; - if ($this->tree->hasKids($item_id)){ - $head .= "open_ranges[$item_id]) - ? URLHelper::getLink($this->getSelf("close_range={$item_id}")) - : URLHelper::getLink($this->getSelf("open_range={$item_id}")); - $head .= "\">"; - $head .= Icon::create('folder-full', 'clickable', ['title' => !empty($this->open_ranges[$item_id]) ? _('Alle Unterelemente schließen') : _('Alle Unterelemente öffnen')])->asImg(16, ['class' => 'text-top']); - $head .= ""; - } else { - $head .= Icon::create('folder-empty', 'clickable', ['title' => _('Dieses Element hat keine Unterelemente')])->asImg(); - } - return $head . ""; - } - - function getItemHeadFrontPic($item_id) - { - if ($this->use_aging){ - $head = "getAgingColor($item_id) . "\" class=\"" - . (($this->open_items[$item_id]) ? 'printhead3' : 'printhead2') - . "\" nowrap width=\"1%\" align=\"left\" valign=\"top\">"; - } else { - $head = ""; - } - $head .= "open_items[$item_id]) - ? URLHelper::getLink($this->getSelf("close_item={$item_id}")) . "\"" . tooltip(_("Dieses Element schließen"),true) . ">" - : URLHelper::getLink($this->getSelf("open_item={$item_id}")) . "\"" . tooltip(_("Dieses Element öffnen"),true) . ">"; - $head .= Assets::img(!empty($this->open_items[$item_id]) ? $this->pic_open : $this->pic_close); - #$head .= (!$this->open_items[$item_id]) ? "" : ""; - $head .= ""; - $head .= ''; - return $head; - } - - /** - * returns html for the name of the item - * - * @access private - * @param string $item_id - * @return string - */ - function getItemHead($item_id){ - $head = ""; - $head .= " open_items[$item_id]) - ? URLHelper::getLink($this->getSelf("close_item={$item_id}")) . "\"" . tooltip(_("Dieses Element schließen"),true) . ">" - : URLHelper::getLink($this->getSelf("open_item={$item_id}")) . "\"" . tooltip(_("Dieses Element öffnen"),true) . ">"; - $head .= htmlReady(my_substr($this->tree->tree_data[$item_id]['name'],0,$this->max_cols)); - $head .= (!empty($this->open_items[$item_id])) ? "" : ""; - return $head; - } - - /** - * returns html for the content body of the item - * - * @access private - * @param string $item_id - * @return string - */ - function getItemContent($item_id){ - $content = "\n"; - if ($item_id == "root"){ - $content .= "\n"; - $content .= "\n"; - $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . $this->root_content ."
"; - return $content; - } - $content .= "\nInhalt für Element {$this->tree->tree_data[$item_id]['name']} ($item_id)
".formatReady($this->tree->tree_data[$item_id]['description']).""; - $content .= ""; - return $content; - } - - function getAgingColor($item_id){ - $the_time = time(); - $chdate = $this->tree->tree_data[$item_id]['chdate']; - if ($chdate == 0){ - $timecolor = "#BBBBBB"; - } else { - if (($the_time - $chdate) < 86400){ - $timecolor = "#FF0000"; - } else { - $timediff = (int) log(($the_time - $chdate) / 86400 + 1) * 15; - if ($timediff >= 68){ - $timediff = 68; - } - $red = dechex(255 - $timediff); - $other = dechex(119 + $timediff); - $timecolor = "#" . $red . $other . $other; - } - } - return $timecolor; - } - - /** - * returns script name - * - * @access private - * @param string $param - * @return string - */ - function getSelf($param = ""){ - return "?" . $param . "#anchor"; - } -} diff --git a/lib/classes/TreeView.php b/lib/classes/TreeView.php new file mode 100644 index 0000000..243a9b9 --- /dev/null +++ b/lib/classes/TreeView.php @@ -0,0 +1,446 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* Class to print out html represantation of a tree object based on TreeAbstract.class.php +* +* Class to print out html represantation of a tree object based on TreeAbstract.class.php +* +* @access public +* @author André Noack +* @package +*/ +class TreeView { + + /** + * Reference to the tree structure + * + * @access private + * @var object StudipRangeTree $tree + */ + var $tree; + /** + * name of used tree class + * + * @access private + * @var string $tree_class_name + */ + var $tree_class_name; + /** + * contains the item with the current html anchor + * + * @access public + * @var string $anchor + */ + var $anchor; + /** + * array containing all open items + * + * this is a reference to a global session variable, managed by PHPLib + * @access public + * @var array $open_items + */ + var $open_items; + /** + * array containing all open item nodes + * + * this is a reference to a global session variable, managed by PHPLib + * @access public + * @var array $open_ranges + */ + var $open_ranges; + /** + * the item to start with + * + * @access private + * @var string $start_item_id + */ + var $start_item_id; + /** + * the content of the root element + * + * @access public + * @var string $root_content + */ + var $root_content; + + /** + * the maximum amount of columns in a title + * + * @access public + * @var string $max_cols + */ + var $max_cols = 80; + + /** + * draw red icons + * + * @access public + * @var boolean $use_aging + */ + var $use_aging = false; + var $pic_open; + var $pic_close; + + /** + * constructor + * + * @access public + * @param string $tree_class_name name of used tree class + * @param mixed $args argument passed to the tree class + */ + public function __construct($tree_class_name,$args = null) + { + $this->tree_class_name = $tree_class_name; + $this->tree = TreeAbstract::GetInstance($tree_class_name, $args); + // TODO Die Logik hinter forumgrau2 und forumgraurunt2 muss + // komplett erneuert werden; dann können auch Instanzen der + // Klasse "Icon" verwendet werden. + $this->pic_open = $this->use_aging + ? 'forumgraurunt2.png' + : 'icons/blue/arr_1down.svg'; + $this->pic_close = $this->use_aging + ? 'forumgrau2.png' + : 'icons/blue/arr_1right.svg'; + + URLHelper::bindLinkParam('open_ranges', $this->open_ranges); + URLHelper::bindLinkParam('open_items', $this->open_items); + + $this->handleOpenRanges(); + } + + /** + * manages the link parameters used for the open/close thing + * + * @access private + */ + private function handleOpenRanges() + { + $close_range = Request::option('close_range'); + if ($close_range) { + if ($close_range === 'root'){ + $this->open_ranges = null; + $this->open_items = null; + } else { + $kidskids = $this->tree->getKidsKids($close_range); + $kidskids[] = $close_range; + foreach ($kidskids as $kid) { + unset($this->open_ranges[$kid]); + unset($this->open_items[$kid]); + } + } + $this->anchor = $close_range; + } + + $open_range = Request::option('open_range'); + if ($open_range) { + $kidskids = $this->tree->getKidsKids($open_range); + $kidskids[] = $open_range; + foreach ($kidskids as $kid) { + $this->open_ranges[$kid] = true; + } + $this->anchor = $open_range; + } + + $toggle_item = Request::option('close_item') ?: Request::option('open_item'); + if ($toggle_item){ + if (!empty($this->open_items[$toggle_item])) { + unset($this->open_items[$toggle_item]); + } else { + $this->openItem($toggle_item); + $this->openRange($toggle_item); + } + $this->anchor = $toggle_item; + } + + if (Request::option('item_id')) { + $this->anchor = Request::option('item_id'); + } + } + + function openItem($item_id) + { + $this->open_items[$item_id] = true; + $this->openRange($this->tree->tree_data[$item_id]['parent_id']); + } + + function openRange($item_id) + { + $this->open_ranges[$item_id] = true; + + $parents = $this->tree->getParents($item_id); + foreach ($parents as $parent) { + $this->open_ranges[$parent] = true; + } + } + + /** + * prints out the tree beginning with a given item + * + * @access public + * @param string $item_id + */ + function showTree($item_id = "root"){ + $items = []; + if (!is_array($item_id)){ + $items[0] = $item_id; + $this->start_item_id = $item_id; + } else { + $items = $item_id; + } + $num_items = count($items); + for ($j = 0; $j < $num_items; ++$j){ + $this->printLevelOutput($items[$j]); + $this->printItemOutput($items[$j]); + if ($this->tree->hasKids($items[$j]) && !empty($this->open_ranges[$items[$j]])) { + $this->showTree($this->tree->tree_childs[$items[$j]]); + } + } + return; +} + + /** + * prints out the lines before an item ("Strichlogik" (c) rstockm) + * + * @access private + * @param string $item_id + */ + function printLevelOutput($item_id) + { + $level_output = ""; + if ($item_id != $this->start_item_id){ + if ($this->tree->isLastKid($item_id)) + $level_output = "" + . Assets::img('forumstrich2.gif') + . ""; //last + else + $level_output = "" + . Assets::img('forumstrich3.gif') + . ""; //crossing + $parent_id = $item_id; + while($this->tree->tree_data[$parent_id]['parent_id'] != $this->start_item_id){ + $parent_id = $this->tree->tree_data[$parent_id]['parent_id']; + if ($this->tree->isLastKid($parent_id)) + $level_output = "" + . Assets::img('forumleer.gif', ['size' => '10@20']) + . "" . $level_output; //nothing + else + $level_output = "" + . Assets::img('forumstrich.gif') + . "" . $level_output; //vertical line + } + } + echo "\n$level_output"; + return; + } + + /** + * prints out one item + * + * @access private + * @param string $item_id + */ + function printItemOutput($item_id) + { + echo $this->getItemHeadPics($item_id); + echo "\n
"; + if ($this->anchor == $item_id) + echo ""; + echo Assets::img('forumleer.gif', ['size' => '1@20']); + if ($this->anchor == $item_id) + echo ""; + echo "\n"; + echo $this->getItemHead($item_id); + echo "
"; + if (!empty($this->open_items[$item_id])) { + $this->printItemDetails($item_id); + } + return; + } + + /** + * prints out the details for an item, if item is open + * + * @access private + * @param string $item_id + */ + function printItemDetails($item_id){ + $level_output = ''; + if (!$this->tree->hasKids($item_id) || !$this->open_ranges[$item_id] || $item_id == $this->start_item_id) + $level_output = "" + . Assets::img('forumleer.gif', ['size' => '10@20']) + . "" . $level_output; + else + $level_output = "" + . Assets::img('forumleer.gif', ['size' => '10@20']) + . "" . $level_output; + + if (($this->tree->isLastKid($item_id) && !($item_id == $this->start_item_id)) || (!$this->open_ranges[$item_id] && $item_id == $this->start_item_id) || ($item_id == $this->start_item_id && !$this->tree->hasKids($item_id))) + $level_output = "" + . Assets::img('forumleer.gif', ['size' => '10@20']) + . "" . $level_output; + else + $level_output = "" + . Assets::img('forumleer.gif', ['size' => '10@20']) + . "" . $level_output; + if ($item_id != $this->start_item_id){ + $parent_id = $item_id; + while($this->tree->tree_data[$parent_id]['parent_id'] != $this->start_item_id){ + $parent_id = $this->tree->tree_data[$parent_id]['parent_id']; + if ($this->tree->isLastKid($parent_id)) + $level_output = "" + . Assets::img('forumleer.gif', ['size' => '10@20']) + . "" . $level_output; //nothing + else + $level_output = "" + . Assets::img('forumleer.gif', ['size' => '10@20']) + . "" . ($level_output ?? ''); //vertical line + } + } + //$level_output = "" . $level_output; + + echo "$level_output"; + echo "

"; + echo $this->getItemContent($item_id); + echo "
"; + } + + /** + * returns html for the icons in front of the name of the item + * + * @access private + * @param string $item_id + * @return string + */ + function getItemHeadPics($item_id) + { + $head = $this->getItemHeadFrontPic($item_id); + $head .= "\n"; + if ($this->tree->hasKids($item_id)){ + $head .= "open_ranges[$item_id]) + ? URLHelper::getLink($this->getSelf("close_range={$item_id}")) + : URLHelper::getLink($this->getSelf("open_range={$item_id}")); + $head .= "\">"; + $head .= Icon::create('folder-full', 'clickable', ['title' => !empty($this->open_ranges[$item_id]) ? _('Alle Unterelemente schließen') : _('Alle Unterelemente öffnen')])->asImg(16, ['class' => 'text-top']); + $head .= ""; + } else { + $head .= Icon::create('folder-empty', 'clickable', ['title' => _('Dieses Element hat keine Unterelemente')])->asImg(); + } + return $head . ""; + } + + function getItemHeadFrontPic($item_id) + { + if ($this->use_aging){ + $head = "getAgingColor($item_id) . "\" class=\"" + . (($this->open_items[$item_id]) ? 'printhead3' : 'printhead2') + . "\" nowrap width=\"1%\" align=\"left\" valign=\"top\">"; + } else { + $head = ""; + } + $head .= "open_items[$item_id]) + ? URLHelper::getLink($this->getSelf("close_item={$item_id}")) . "\"" . tooltip(_("Dieses Element schließen"),true) . ">" + : URLHelper::getLink($this->getSelf("open_item={$item_id}")) . "\"" . tooltip(_("Dieses Element öffnen"),true) . ">"; + $head .= Assets::img(!empty($this->open_items[$item_id]) ? $this->pic_open : $this->pic_close); + #$head .= (!$this->open_items[$item_id]) ? "" : ""; + $head .= ""; + $head .= ''; + return $head; + } + + /** + * returns html for the name of the item + * + * @access private + * @param string $item_id + * @return string + */ + function getItemHead($item_id){ + $head = ""; + $head .= " open_items[$item_id]) + ? URLHelper::getLink($this->getSelf("close_item={$item_id}")) . "\"" . tooltip(_("Dieses Element schließen"),true) . ">" + : URLHelper::getLink($this->getSelf("open_item={$item_id}")) . "\"" . tooltip(_("Dieses Element öffnen"),true) . ">"; + $head .= htmlReady(my_substr($this->tree->tree_data[$item_id]['name'],0,$this->max_cols)); + $head .= (!empty($this->open_items[$item_id])) ? "" : ""; + return $head; + } + + /** + * returns html for the content body of the item + * + * @access private + * @param string $item_id + * @return string + */ + function getItemContent($item_id){ + $content = "\n"; + if ($item_id == "root"){ + $content .= "\n"; + $content .= "\n"; + $content .= "\n
" . htmlReady($this->tree->root_name) ."
" . $this->root_content ."
"; + return $content; + } + $content .= "\nInhalt für Element {$this->tree->tree_data[$item_id]['name']} ($item_id)
".formatReady($this->tree->tree_data[$item_id]['description']).""; + $content .= ""; + return $content; + } + + function getAgingColor($item_id){ + $the_time = time(); + $chdate = $this->tree->tree_data[$item_id]['chdate']; + if ($chdate == 0){ + $timecolor = "#BBBBBB"; + } else { + if (($the_time - $chdate) < 86400){ + $timecolor = "#FF0000"; + } else { + $timediff = (int) log(($the_time - $chdate) / 86400 + 1) * 15; + if ($timediff >= 68){ + $timediff = 68; + } + $red = dechex(255 - $timediff); + $other = dechex(119 + $timediff); + $timecolor = "#" . $red . $other . $other; + } + } + return $timecolor; + } + + /** + * returns script name + * + * @access private + * @param string $param + * @return string + */ + function getSelf($param = ""){ + return "?" . $param . "#anchor"; + } +} diff --git a/lib/classes/UpdateInformation.class.php b/lib/classes/UpdateInformation.class.php deleted file mode 100644 index 959ef72..0000000 --- a/lib/classes/UpdateInformation.class.php +++ /dev/null @@ -1,118 +0,0 @@ - - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -class UserConfig extends RangeConfig implements PrivacyObject -{ - /** - * range type - */ - const RANGE_TYPE = 'user'; - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $usr_conf = [[]]; - foreach (new UserConfig($storage->user_id) as $key => $val) { - $usr_conf[0][$key] = is_array($val) ? print_r($val, true) : $val; - } - if ($usr_conf) { - $storage->addTabularData(_('Benutzer Konfigurationen'), 'user_config', $usr_conf); - } - } -} diff --git a/lib/classes/UserConfig.php b/lib/classes/UserConfig.php new file mode 100644 index 0000000..4cb1ff6 --- /dev/null +++ b/lib/classes/UserConfig.php @@ -0,0 +1,40 @@ + + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class UserConfig extends RangeConfig implements PrivacyObject +{ + /** + * range type + */ + const RANGE_TYPE = 'user'; + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $usr_conf = [[]]; + foreach (new UserConfig($storage->user_id) as $key => $val) { + $usr_conf[0][$key] = is_array($val) ? print_r($val, true) : $val; + } + if ($usr_conf) { + $storage->addTabularData(_('Benutzer Konfigurationen'), 'user_config', $usr_conf); + } + } +} diff --git a/lib/classes/UserLookup.class.php b/lib/classes/UserLookup.class.php deleted file mode 100644 index bdd9fc3..0000000 --- a/lib/classes/UserLookup.class.php +++ /dev/null @@ -1,503 +0,0 @@ -setFilter('fachsemester', range(1, 6)); - * - * # Filter all users that have an 'autor' or 'tutor' permission - * $user_lookup->setFilter('status', ['autor', 'tutor']); - * - * # Get a list of all matching user ids (sorted by the user's names) - * $user_ids = $user_lookup->execute(UserLookup::FLAG_SORT_NAME); - * - * # Get another list of all matching user ids but this time we want - * # the complete unordered dataset - * $user_ids = $user_lookup->execute(UserLookup::FLAG_RETURN_FULL_INFO); - * @endcode - * - * @author Jan-Hendrik Willms - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.1 - */ -class UserLookup -{ - // At the moment, the cache is only used for the GetValuesForType method - const USE_CACHE = false; - const CACHE_DURATION = 3600; // 1 hour - - const FLAG_SORT_NAME = 1; - const FLAG_RETURN_FULL_INFO = 2; - - // Special constant to use for a combined study group filter - const USE_COMBINED_STUDYGROUP_FILTER = 'use-combined-studygroup-filter'; - - /** - * Predefined array of filter criteria - * - * @var array - */ - protected static $types = [ - 'abschluss' => [ - 'filter' => self::USE_COMBINED_STUDYGROUP_FILTER, - 'values' => 'UserLookup::abschlussValues', - ], - 'fach' => [ - 'filter' => self::USE_COMBINED_STUDYGROUP_FILTER, - 'values' => 'UserLookup::fachValues', - ], - 'fachsemester' => [ - 'filter' => self::USE_COMBINED_STUDYGROUP_FILTER, - 'values' => 'UserLookup::fachsemesterValues', - ], - 'institut' => [ - 'filter' => 'UserLookup::institutFilter', - 'values' => 'UserLookup::institutValues', - ], - 'status' => [ - 'filter' => 'UserLookup::statusFilter', - 'values' => 'UserLookup::statusValues', - ], - 'domain' => [ - 'filter' => 'UserLookup::domainFilter', - 'values' => 'UserLookup::domainValues', - ], - 'role' => [ - 'filter' => 'UserLookup::roleFilter', - 'values' => 'UserLookup::roleValues', - ], - ]; - - /** - * Contains the resulting filter set - * @var array - */ - private $filters = []; - - /** - * Adds another type filter to the set of current filters. - * - * Multiple filters for the same filter type result in an AND filter - * within this type while multiple filters across filter types result - * in an OR filter across these types. - * - * @param string $type Type of filter to add - * @param string $value Value to filter against - * @return UserLookup Returns itself to allow chaining - */ - public function setFilter($type, $value) - { - if (!array_key_exists($type, self::$types)) { - throw new Exception('[UserLookup] Cannot set filter for unknown type "' . $type . '"'); - } - - if (!isset($this->filters[$type])) { - $this->filters[$type] = []; - } - - $this->filters[$type] = array_merge($this->filters[$type], (array) $value); - - return $this; - } - - /** - * Executes the actual lookup by executing all individual filter types - * and returning the intersection of all according result sets. - * - * Possible flags: - * - FLAG_SORT_NAME Sorts the user ids in the result by the - * actual user names - * - FLAG_RETURN_FULL_INFO Returns rudimental user info instead of just - * the ids (as an array with the user id as key - * and an array containting the info as value) - * - * @param int $flags Optional set of flags as seen above - * @return array Either a simple list of user ids or an associative - * array of user ids and user info if FLAG_RETURN_FULL_INFO - * is set - */ - public function execute($flags = null) - { - if (count($this->filters) === 0) { - throw new Exception('[UserLookup] Cannot execute empty filter set'); - } - - $result = null; - foreach ($this->getFilters() as $filter) { - $temp_result = call_user_func($filter['callable'], $filter['needles']); - - if ($result === null) { - $result = $temp_result; - } else { - $result = array_intersect($result, $temp_result); - } - } - - if (($flags & self::FLAG_SORT_NAME) && !($flags & self::FLAG_RETURN_FULL_INFO)) { - $query = "SELECT user_id - FROM auth_user_md5 - WHERE user_id IN (?) - ORDER BY Nachname ASC, Vorname ASC"; - $result = DBManager::get()->fetchFirst($query, [$result ?: '']); - } - - if (!empty($result) && ($flags & self::FLAG_RETURN_FULL_INFO)) { - $query = "SELECT `user_id`, `username`, `Vorname`, `Nachname`, `Email`, `perms` - FROM `auth_user_md5` - WHERE `user_id` IN (?)"; - if ($flags & self::FLAG_SORT_NAME) { - $query .= " ORDER BY Nachname ASC, Vorname ASC"; - } - - $result = DBManager::get()->fetchGrouped($query, [$result ?: '']); - } - - return $result; - } - - protected function getFilters() - { - $filters = []; - $study_course_filter = []; - foreach ($this->filters as $type => $values) { - if (self::$types[$type]['filter'] === self::USE_COMBINED_STUDYGROUP_FILTER) { - $study_course_filter[$type] = $values; - } else { - $filters[] = [ - 'callable' => self::$types[$type]['filter'], - 'needles' => $values, - ]; - } - } - - if ($study_course_filter) { - $filters[] = [ - 'callable' => [$this, 'combinedStudyCourseFilter'], - 'needles' => $study_course_filter, - ]; - } - - return $filters; - } - - /** - * Clears all defined filters. - * - * @return UserLookup Returns itself to allow chaining - */ - public function clearFilters() - { - $this->filters = []; - return $this; - } - - /** - * Adds or updates a filter criterion the global set of criteria. - * - * @param string $name Name of the criterion type - * @param callback $values_callback Callback for the type's values - * @param callback $filter_callback Actual filter callback for a defined - * set of needles - */ - public static function addType($name, $values_callback, $filter_callback) - { - if (!is_callable($values_callback)) { - throw new Exception('[UserLookup] Values callback for type "' . $name . '" is not callable'); - } - if (!is_callable($filter_callback)) { - throw new Exception('[UserLookup] Filter callback for type "' . $name . '" is not callable'); - } - - self::$types[$name] = [ - 'filter' => $filter_callback, - 'values' => $values_callback, - ]; - } - - /** - * Returns all valid values for a certain criterion type. - * - * @param string $type Name of the criterion type - * @return array Associative array containing the values as keys and - * descriptive names as values - */ - public static function getValuesForType($type) - { - if (!array_key_exists($type, self::$types)) { - throw new Exception('[UserLookup] Unknown type "' . $type . '"'); - } - - if (!self::USE_CACHE) { - return call_user_func(self::$types[$type]['values']); - } - - $cache = \Studip\Cache\Factory::getCache(); - $cache_key = "UserLookup/{$type}/values"; - $cached_values = $cache->read($cache_key); - if ($cached_values) { - return unserialize($cached_values); - } - - $values = call_user_func(self::$types[$type]['values']); - - $cache->write($cache_key, serialize($values), self::CACHE_DURATION); - - return $values; - } - - /** - * Return all user with matching studiengang_id in $needles - * @param array $needles List of studiengang ids to filter against - * @return array List of user ids matching the given filter - */ - protected static function fachFilter($needles) - { - $query = "SELECT user_id FROM user_studiengang WHERE fach_id IN (?)"; - return DBManager::get()->fetchFirst($query, [$needles ?: '']); - } - - /** - * Return all studycourses - * @return array Associative array of studiengang ids and studiengang names - */ - protected static function fachValues() - { - $query = "SELECT `fach_id`, `name` - FROM `fach` - ORDER BY `name` ASC"; - return DBManager::get()->fetchPairs($query); - } - - /** - * Return all user with matching abschluss_id in $needles - * @param array $needles List of abschluss ids to filter against - * @return array List of user ids matching the given filter - */ - protected static function abschlussFilter($needles) - { - $query = "SELECT user_id FROM user_studiengang WHERE abschluss_id IN (?)"; - return DBManager::get()->fetchFirst($query, [$needles ?: '']); - } - - /** - * Return all studydegrees - * @return array Associative array of abschluss ids and abschluss names - */ - protected static function abschlussValues() - { - $query = "SELECT `abschluss_id`, `name` - FROM `abschluss` - ORDER BY `name` ASC"; - return DBManager::get()->fetchPairs($query); - } - - /** - * Return all users with a matching fachsemester given in $needles - * @param array $needles List of fachsemesters to filter against - * @return array List of user ids matching the given filter - */ - protected static function fachsemesterFilter($needles) - { - $query = "SELECT user_id FROM user_studiengang WHERE semester IN (?)"; - return DBManager::get()->fetchFirst($query, [$needles ?: '']); - } - - /** - * Create a array with all possible values for studysemesters - * @return array Associative array of fachsemesters and fachsemesters - * (pretty dull, i know) - */ - protected static function fachsemesterValues() - { - $query = "SELECT MAX(`semester`) FROM `user_studiengang`"; - $max = DBManager::get()->fetchColumn($query); - $values = range(1, $max); - return array_combine($values, $values); - } - - /** - * Returns all user with a matching set of values in user study course - * table. - * @return array List of user ids matching the given filter - */ - private static function combinedStudyCourseFilter(array $needles) - { - $type_column_mapping = [ - 'abschluss' => 'abschluss_id', - 'fach' => 'fach_id', - 'fachsemester' => 'semester', - ]; - - $conditions = []; - $parameters = []; - foreach ($needles as $type => $needles) { - $column = $type_column_mapping[$type]; - - $conditions[] = "`{$column}` IN (:{$column})"; - $parameters[":{$column}"] = $needles; - } - - $query = "SELECT `user_id` - FROM `user_studiengang` - WHERE " . implode(' AND ', $conditions); - - return DBManager::get()->fetchFirst($query, $parameters); - } - - /** - * Return all users with a matching institut_id given in $needles - * @param array $needles List of institut ids to filter against - * @return array List of user ids matching the given filter - */ - protected static function institutFilter($needles) - { - if (!$needles) { - return []; - } - - $query = "SELECT `user_id` - FROM `user_inst` - WHERE `Institut_id` IN (?)"; - return DBManager::get()->fetchFirst($query, [$needles]); - } - - /** - * Return all faculty's and instituts - * @return array Associative array of institut ids and institut data - * (Be aware that this array is multidimensional) - */ - protected static function institutValues() - { - $query = "SELECT `fakultaets_id`, `Institut_id`, `Name`, - `fakultaets_id` = `Institut_id` AS is_fakultaet - FROM `Institute` - ORDER BY `Institut_id` = `fakultaets_id` DESC, `Name` ASC"; - $db_result = DBManager::get()->query($query)->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC); - - $result = []; - foreach ($db_result as $fakultaets_id => $items) { - foreach ($items as $item) { - if (!isset($result[$fakultaets_id])) { - $result[$fakultaets_id] = [ - 'name' => $item['Name'], - 'values' => [], - ]; - } else { - $result[$fakultaets_id]['values'][$item['Institut_id']] = $item['Name']; - } - } - } - return $result; - } - - /** - * Return all users with a matching status given in $needles - * @param array $needles List of statusses to filter against - * @return array List of user ids matching the given filter - */ - protected static function statusFilter($needles) - { - if (!$needles) { - return []; - } - - $query = "SELECT `user_id` - FROM `auth_user_md5` - WHERE `perms` IN (?)"; - return DBManager::get()->fetchFirst($query, [$needles]); - } - - /** - * Return all valid statusses - * @return array Associative array of status name and description - */ - protected static function statusValues() - { - return [ - 'autor' => _('Autor'), - 'tutor' => _('Tutor'), - 'dozent' => _('Dozent'), - 'admin' => _('Admin'), - 'root' => _('Root'), - ]; - } - - /** - * Return all users with a matching domain given in $needles - * @param array $needles List of domain ids to filter against - * @return array List of user ids matching the given filter - */ - protected static function domainFilter($needles) - { - if (!$needles) { - return []; - } - - $query = "SELECT `user_id` - FROM `user_userdomains` - WHERE `userdomain_id` IN (?)"; - return DBManager::get()->fetchFirst($query, [$needles]); - } - - /** - * Return all valid domains - * @return array Associative array of domain id and name - */ - protected static function domainValues() - { - $domains = []; - $domains['keine'] = _('Ohne Domain'); - foreach (UserDomain::getUserDomains() as $domain) { - $domains[$domain->getId()] = $domain->name; - } - - return $domains; - } - - /** - * Return all users with a matching assigned role given in $needles - * - * @param array $needles List of domain ids to filter against - * @return array List of user ids matching the given filter - */ - protected static function roleFilter($needles): array - { - if (!$needles) { - return []; - } - - $user_ids = []; - foreach ($needles as $role_id) { - $users = RolePersistence::getUsersWithRoleById($role_id, false); - foreach ($users as $user) { - $user_ids[$user->id] = $user->id; - } - } - - return array_values($user_ids); - } - - /** - * Return all valid roles - * - * @return array Associative array of domain id and name - */ - protected static function roleValues(): array - { - $roles = []; - $roles['keine'] = _('Ohne Rollenzuweisung'); - foreach (RolePersistence::getAllRoles() as $role) { - $roles[$role->getRoleid()] = $role->getRolename(); - } - - return $roles; - } -} diff --git a/lib/classes/UserLookup.php b/lib/classes/UserLookup.php new file mode 100644 index 0000000..bdd9fc3 --- /dev/null +++ b/lib/classes/UserLookup.php @@ -0,0 +1,503 @@ +setFilter('fachsemester', range(1, 6)); + * + * # Filter all users that have an 'autor' or 'tutor' permission + * $user_lookup->setFilter('status', ['autor', 'tutor']); + * + * # Get a list of all matching user ids (sorted by the user's names) + * $user_ids = $user_lookup->execute(UserLookup::FLAG_SORT_NAME); + * + * # Get another list of all matching user ids but this time we want + * # the complete unordered dataset + * $user_ids = $user_lookup->execute(UserLookup::FLAG_RETURN_FULL_INFO); + * @endcode + * + * @author Jan-Hendrik Willms + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.1 + */ +class UserLookup +{ + // At the moment, the cache is only used for the GetValuesForType method + const USE_CACHE = false; + const CACHE_DURATION = 3600; // 1 hour + + const FLAG_SORT_NAME = 1; + const FLAG_RETURN_FULL_INFO = 2; + + // Special constant to use for a combined study group filter + const USE_COMBINED_STUDYGROUP_FILTER = 'use-combined-studygroup-filter'; + + /** + * Predefined array of filter criteria + * + * @var array + */ + protected static $types = [ + 'abschluss' => [ + 'filter' => self::USE_COMBINED_STUDYGROUP_FILTER, + 'values' => 'UserLookup::abschlussValues', + ], + 'fach' => [ + 'filter' => self::USE_COMBINED_STUDYGROUP_FILTER, + 'values' => 'UserLookup::fachValues', + ], + 'fachsemester' => [ + 'filter' => self::USE_COMBINED_STUDYGROUP_FILTER, + 'values' => 'UserLookup::fachsemesterValues', + ], + 'institut' => [ + 'filter' => 'UserLookup::institutFilter', + 'values' => 'UserLookup::institutValues', + ], + 'status' => [ + 'filter' => 'UserLookup::statusFilter', + 'values' => 'UserLookup::statusValues', + ], + 'domain' => [ + 'filter' => 'UserLookup::domainFilter', + 'values' => 'UserLookup::domainValues', + ], + 'role' => [ + 'filter' => 'UserLookup::roleFilter', + 'values' => 'UserLookup::roleValues', + ], + ]; + + /** + * Contains the resulting filter set + * @var array + */ + private $filters = []; + + /** + * Adds another type filter to the set of current filters. + * + * Multiple filters for the same filter type result in an AND filter + * within this type while multiple filters across filter types result + * in an OR filter across these types. + * + * @param string $type Type of filter to add + * @param string $value Value to filter against + * @return UserLookup Returns itself to allow chaining + */ + public function setFilter($type, $value) + { + if (!array_key_exists($type, self::$types)) { + throw new Exception('[UserLookup] Cannot set filter for unknown type "' . $type . '"'); + } + + if (!isset($this->filters[$type])) { + $this->filters[$type] = []; + } + + $this->filters[$type] = array_merge($this->filters[$type], (array) $value); + + return $this; + } + + /** + * Executes the actual lookup by executing all individual filter types + * and returning the intersection of all according result sets. + * + * Possible flags: + * - FLAG_SORT_NAME Sorts the user ids in the result by the + * actual user names + * - FLAG_RETURN_FULL_INFO Returns rudimental user info instead of just + * the ids (as an array with the user id as key + * and an array containting the info as value) + * + * @param int $flags Optional set of flags as seen above + * @return array Either a simple list of user ids or an associative + * array of user ids and user info if FLAG_RETURN_FULL_INFO + * is set + */ + public function execute($flags = null) + { + if (count($this->filters) === 0) { + throw new Exception('[UserLookup] Cannot execute empty filter set'); + } + + $result = null; + foreach ($this->getFilters() as $filter) { + $temp_result = call_user_func($filter['callable'], $filter['needles']); + + if ($result === null) { + $result = $temp_result; + } else { + $result = array_intersect($result, $temp_result); + } + } + + if (($flags & self::FLAG_SORT_NAME) && !($flags & self::FLAG_RETURN_FULL_INFO)) { + $query = "SELECT user_id + FROM auth_user_md5 + WHERE user_id IN (?) + ORDER BY Nachname ASC, Vorname ASC"; + $result = DBManager::get()->fetchFirst($query, [$result ?: '']); + } + + if (!empty($result) && ($flags & self::FLAG_RETURN_FULL_INFO)) { + $query = "SELECT `user_id`, `username`, `Vorname`, `Nachname`, `Email`, `perms` + FROM `auth_user_md5` + WHERE `user_id` IN (?)"; + if ($flags & self::FLAG_SORT_NAME) { + $query .= " ORDER BY Nachname ASC, Vorname ASC"; + } + + $result = DBManager::get()->fetchGrouped($query, [$result ?: '']); + } + + return $result; + } + + protected function getFilters() + { + $filters = []; + $study_course_filter = []; + foreach ($this->filters as $type => $values) { + if (self::$types[$type]['filter'] === self::USE_COMBINED_STUDYGROUP_FILTER) { + $study_course_filter[$type] = $values; + } else { + $filters[] = [ + 'callable' => self::$types[$type]['filter'], + 'needles' => $values, + ]; + } + } + + if ($study_course_filter) { + $filters[] = [ + 'callable' => [$this, 'combinedStudyCourseFilter'], + 'needles' => $study_course_filter, + ]; + } + + return $filters; + } + + /** + * Clears all defined filters. + * + * @return UserLookup Returns itself to allow chaining + */ + public function clearFilters() + { + $this->filters = []; + return $this; + } + + /** + * Adds or updates a filter criterion the global set of criteria. + * + * @param string $name Name of the criterion type + * @param callback $values_callback Callback for the type's values + * @param callback $filter_callback Actual filter callback for a defined + * set of needles + */ + public static function addType($name, $values_callback, $filter_callback) + { + if (!is_callable($values_callback)) { + throw new Exception('[UserLookup] Values callback for type "' . $name . '" is not callable'); + } + if (!is_callable($filter_callback)) { + throw new Exception('[UserLookup] Filter callback for type "' . $name . '" is not callable'); + } + + self::$types[$name] = [ + 'filter' => $filter_callback, + 'values' => $values_callback, + ]; + } + + /** + * Returns all valid values for a certain criterion type. + * + * @param string $type Name of the criterion type + * @return array Associative array containing the values as keys and + * descriptive names as values + */ + public static function getValuesForType($type) + { + if (!array_key_exists($type, self::$types)) { + throw new Exception('[UserLookup] Unknown type "' . $type . '"'); + } + + if (!self::USE_CACHE) { + return call_user_func(self::$types[$type]['values']); + } + + $cache = \Studip\Cache\Factory::getCache(); + $cache_key = "UserLookup/{$type}/values"; + $cached_values = $cache->read($cache_key); + if ($cached_values) { + return unserialize($cached_values); + } + + $values = call_user_func(self::$types[$type]['values']); + + $cache->write($cache_key, serialize($values), self::CACHE_DURATION); + + return $values; + } + + /** + * Return all user with matching studiengang_id in $needles + * @param array $needles List of studiengang ids to filter against + * @return array List of user ids matching the given filter + */ + protected static function fachFilter($needles) + { + $query = "SELECT user_id FROM user_studiengang WHERE fach_id IN (?)"; + return DBManager::get()->fetchFirst($query, [$needles ?: '']); + } + + /** + * Return all studycourses + * @return array Associative array of studiengang ids and studiengang names + */ + protected static function fachValues() + { + $query = "SELECT `fach_id`, `name` + FROM `fach` + ORDER BY `name` ASC"; + return DBManager::get()->fetchPairs($query); + } + + /** + * Return all user with matching abschluss_id in $needles + * @param array $needles List of abschluss ids to filter against + * @return array List of user ids matching the given filter + */ + protected static function abschlussFilter($needles) + { + $query = "SELECT user_id FROM user_studiengang WHERE abschluss_id IN (?)"; + return DBManager::get()->fetchFirst($query, [$needles ?: '']); + } + + /** + * Return all studydegrees + * @return array Associative array of abschluss ids and abschluss names + */ + protected static function abschlussValues() + { + $query = "SELECT `abschluss_id`, `name` + FROM `abschluss` + ORDER BY `name` ASC"; + return DBManager::get()->fetchPairs($query); + } + + /** + * Return all users with a matching fachsemester given in $needles + * @param array $needles List of fachsemesters to filter against + * @return array List of user ids matching the given filter + */ + protected static function fachsemesterFilter($needles) + { + $query = "SELECT user_id FROM user_studiengang WHERE semester IN (?)"; + return DBManager::get()->fetchFirst($query, [$needles ?: '']); + } + + /** + * Create a array with all possible values for studysemesters + * @return array Associative array of fachsemesters and fachsemesters + * (pretty dull, i know) + */ + protected static function fachsemesterValues() + { + $query = "SELECT MAX(`semester`) FROM `user_studiengang`"; + $max = DBManager::get()->fetchColumn($query); + $values = range(1, $max); + return array_combine($values, $values); + } + + /** + * Returns all user with a matching set of values in user study course + * table. + * @return array List of user ids matching the given filter + */ + private static function combinedStudyCourseFilter(array $needles) + { + $type_column_mapping = [ + 'abschluss' => 'abschluss_id', + 'fach' => 'fach_id', + 'fachsemester' => 'semester', + ]; + + $conditions = []; + $parameters = []; + foreach ($needles as $type => $needles) { + $column = $type_column_mapping[$type]; + + $conditions[] = "`{$column}` IN (:{$column})"; + $parameters[":{$column}"] = $needles; + } + + $query = "SELECT `user_id` + FROM `user_studiengang` + WHERE " . implode(' AND ', $conditions); + + return DBManager::get()->fetchFirst($query, $parameters); + } + + /** + * Return all users with a matching institut_id given in $needles + * @param array $needles List of institut ids to filter against + * @return array List of user ids matching the given filter + */ + protected static function institutFilter($needles) + { + if (!$needles) { + return []; + } + + $query = "SELECT `user_id` + FROM `user_inst` + WHERE `Institut_id` IN (?)"; + return DBManager::get()->fetchFirst($query, [$needles]); + } + + /** + * Return all faculty's and instituts + * @return array Associative array of institut ids and institut data + * (Be aware that this array is multidimensional) + */ + protected static function institutValues() + { + $query = "SELECT `fakultaets_id`, `Institut_id`, `Name`, + `fakultaets_id` = `Institut_id` AS is_fakultaet + FROM `Institute` + ORDER BY `Institut_id` = `fakultaets_id` DESC, `Name` ASC"; + $db_result = DBManager::get()->query($query)->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC); + + $result = []; + foreach ($db_result as $fakultaets_id => $items) { + foreach ($items as $item) { + if (!isset($result[$fakultaets_id])) { + $result[$fakultaets_id] = [ + 'name' => $item['Name'], + 'values' => [], + ]; + } else { + $result[$fakultaets_id]['values'][$item['Institut_id']] = $item['Name']; + } + } + } + return $result; + } + + /** + * Return all users with a matching status given in $needles + * @param array $needles List of statusses to filter against + * @return array List of user ids matching the given filter + */ + protected static function statusFilter($needles) + { + if (!$needles) { + return []; + } + + $query = "SELECT `user_id` + FROM `auth_user_md5` + WHERE `perms` IN (?)"; + return DBManager::get()->fetchFirst($query, [$needles]); + } + + /** + * Return all valid statusses + * @return array Associative array of status name and description + */ + protected static function statusValues() + { + return [ + 'autor' => _('Autor'), + 'tutor' => _('Tutor'), + 'dozent' => _('Dozent'), + 'admin' => _('Admin'), + 'root' => _('Root'), + ]; + } + + /** + * Return all users with a matching domain given in $needles + * @param array $needles List of domain ids to filter against + * @return array List of user ids matching the given filter + */ + protected static function domainFilter($needles) + { + if (!$needles) { + return []; + } + + $query = "SELECT `user_id` + FROM `user_userdomains` + WHERE `userdomain_id` IN (?)"; + return DBManager::get()->fetchFirst($query, [$needles]); + } + + /** + * Return all valid domains + * @return array Associative array of domain id and name + */ + protected static function domainValues() + { + $domains = []; + $domains['keine'] = _('Ohne Domain'); + foreach (UserDomain::getUserDomains() as $domain) { + $domains[$domain->getId()] = $domain->name; + } + + return $domains; + } + + /** + * Return all users with a matching assigned role given in $needles + * + * @param array $needles List of domain ids to filter against + * @return array List of user ids matching the given filter + */ + protected static function roleFilter($needles): array + { + if (!$needles) { + return []; + } + + $user_ids = []; + foreach ($needles as $role_id) { + $users = RolePersistence::getUsersWithRoleById($role_id, false); + foreach ($users as $user) { + $user_ids[$user->id] = $user->id; + } + } + + return array_values($user_ids); + } + + /** + * Return all valid roles + * + * @return array Associative array of domain id and name + */ + protected static function roleValues(): array + { + $roles = []; + $roles['keine'] = _('Ohne Rollenzuweisung'); + foreach (RolePersistence::getAllRoles() as $role) { + $roles[$role->getRoleid()] = $role->getRolename(); + } + + return $roles; + } +} diff --git a/lib/classes/UserManagement.class.php b/lib/classes/UserManagement.class.php deleted file mode 100644 index 015e6cd..0000000 --- a/lib/classes/UserManagement.class.php +++ /dev/null @@ -1,1353 +0,0 @@ - - * @author Suchi & Berg GmbH - * @copyright 2009 Stud.IP - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL Licence 2 - * @category Stud.IP - */ - -// Imports -require_once 'lib/statusgruppe.inc.php'; // remove user from statusgroups -require_once 'lib/messaging.inc.php'; // remove messages send or recieved by user -require_once 'lib/object.inc.php'; - -/** - * UserManagement.class.php - * - * Management for the Stud.IP global users - * - */ -class UserManagement -{ - private $user; - private $validator; - private $user_data; - - public $msg; - - private static $pwd_hasher; - - public static function getPwdHasher() - { - if (self::$pwd_hasher === null) { - self::$pwd_hasher = new PasswordHash(8, Config::get()->PHPASS_USE_PORTABLE_HASH); - } - return self::$pwd_hasher; - } - - /** - * Constructor - * - * Pass nothing to create a new user, or the user_id from an existing user to change or delete - * @param string $user_id the user which should be retrieved - */ - public function __construct($user_id = false) - { - $this->validator = new email_validation_class(); - $this->validator->timeout = 10; // How long do we wait for response of mailservers? - $this->getFromDatabase($user_id); - } - - public function __get($attr) - { - if ($attr === 'user_data') { - return $this->user_data; - } - } - - public function __set($attr, $value) - { - if ($attr === 'user_data') { - if (!is_array($value)) { - throw new InvalidArgumentException('user_data only accepts array'); - } - return $this->user_data->setData($value, true); - } - } - - /** - * load user data from database into internal array - * - * @param string $user_id the user which should be retrieved - */ - public function getFromDatabase($user_id) - { - $this->user = User::toObject($user_id); - if (!$this->user) { - $this->user = new User(); - } - $this->user_data = new UserDataAdapter($this->user); - } - - /** - * store user data from internal array into database - * - * @access private - * @return bool all data stored? - */ - private function storeToDatabase() - { - if ($this->user->isNew()) { - if ($this->user->store()) { - StudipLog::log('USER_CREATE', $this->user->id, null, implode(';', $this->user->toArray('username vorname nachname perms email'))); - return true; - } else { - return false; - } - } - - $nperms = [ - 'user' => 0, - 'autor' => 1, - 'tutor' => 2, - 'dozent' => 3 - ]; - - if ($this->user->isDirty('perms')) { - if ($this->user->perms === 'dozent' && in_array($this->user->getPristineValue('perms'), ['user','autor','tutor'])) { - $this->logInstUserDel($this->user->id, "inst_perms = 'user'"); - $this->user->institute_memberships->unsetBy('inst_perms', 'user'); - // make user visible globally if dozent may not be invisible (StEP 00158) - if (Config::get()->DOZENT_ALWAYS_VISIBLE && $this->user->visible !== 'never') { - $this->user->visible = 'yes'; - } - if ($nperms[$this->user->perms] < $nperms[$this->user->getPristineValue('perms')]) { - $old_status = []; - foreach ($nperms as $status => $n) { - if ($n > $nperms[$this->user->perms] && $n <= $nperms[$this->user->getPristineValue('perms')]) { - $old_status[] = $status; - } - } - $new_status = $this->user->perms; - CourseMember::findEachBySQL( - function (CourseMember $cm) use ($new_status) { - $cm->status = $new_status; - $cm->store(); - }, - 'INNER JOIN seminare ON (seminare.Seminar_id = seminar_user.Seminar_id) - WHERE seminar_user.user_id = ? - AND seminar_user.status IN (?) - AND seminare.status NOT IN (?) - ', - [ - $this->user->id, - $old_status, - studygroup_sem_types() - ] - ); - } - } - } - foreach (words('username vorname nachname perms email title_front title_rear password') as $field) { - // logging - if ($this->user->isFieldDirty($field)) { - $old_value = $this->user->getPristineValue($field); - $value = $this->user->getValue($field); - switch ($field) { - case 'username': - StudipLog::log('USER_CHANGE_USERNAME', $this->user->id, null, "{$old_value} -> {$value}"); - break; - case 'vorname': - StudipLog::log('USER_CHANGE_NAME', $this->user->id, null, "Vorname: {$old_value} -> {$value}"); - break; - case 'nachname': - StudipLog::log('USER_CHANGE_NAME', $this->user->id, null, "Nachname: {$old_value} -> {$value}"); - break; - case 'perms': - StudipLog::log('USER_CHANGE_PERMS', $this->user->id, null, "{$old_value} -> {$value}"); - break; - case 'email': - StudipLog::log('USER_CHANGE_EMAIL', $this->user->id, null, "{$old_value} -> {$value}"); - break; - case 'title_front': - StudipLog::log('USER_CHANGE_TITLE', $this->user->id, null, "title_front: {$old_value} -> {$value}"); - break; - case 'title_rear': - StudipLog::log('USER_CHANGE_TITLE', $this->user->id, null, "title_rear: {$old_value} -> {$value}"); - case 'password': - StudipLog::log('USER_CHANGE_PASSWORD', $this->user->id, null, "password: {$old_value} -> {$value}"); - break; - } - } - } - - $changed = $this->user->store(); - return (bool) $changed; - } - - - /** - * generate a secure password of $length characters [a-z0-9] - * - * @param integer $length number of characters - * @return string password - */ - public function generate_password($length) - { - $pass = ""; - mt_srand((double) microtime() * 1000000); - for ($i = 1; $i <= $length; $i++) { - $temp = mt_rand() % 36; - if ($temp < 10) { - $temp += 48; // 0 = chr(48), 9 = chr(57) - } else { - $temp += 87; // a = chr(97), z = chr(122) - } - $pass .= chr($temp); - } - return $pass; - } - - - /** - * Check if Email-Adress is valid and reachable - * - * @param string Email-Adress to check - * @return bool Email-Adress valid and reachable? - */ - private function checkMail($Email) - { - // Adress correct? - if (!$this->validator->ValidateEmailAddress($Email)) { - $this->msg .= 'error§' . _('E-Mail-Adresse syntaktisch falsch!') . '§'; - return false; - } - - // E-Mail reachable? - if (!$this->validator->ValidateEmailHost($Email)) { - // Mailserver nicht erreichbar, ablehnen - $this->msg .= 'error§' . _('Mailserver ist nicht erreichbar!') . '§'; - return false; - } - - if (!$this->validator->ValidateEmailBox($Email)) { - // Nutzer unbekannt, ablehnen - $this->msg .= 'error§' . sprintf(_('E-Mail an %s ist nicht zustellbar!'), $Email) . '§'; - return false; - } - - return true; - } - - /** - * Create a new studip user with the given parameters - * - * @param array structure: array('string table_name.field_name'=>'string value') - * @return bool Creation successful? - */ - public function createNewUser($newuser) - { - global $perm; - - // Do we have permission to do so? - if (!$perm->have_perm('admin')) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts anzulegen.') . '§'; - return false; - } - - if (!$perm->is_fak_admin() && $newuser['auth_user_md5.perms'] === 'admin') { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Admin-Accounts anzulegen.') . '§'; - return false; - } - - if (!$perm->have_perm('root') && $newuser['auth_user_md5.perms'] === 'root') { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Root-Accounts anzulegen.') . '§'; - return false; - } - - // Do we have all necessary data? - if (empty($newuser['auth_user_md5.username']) || empty($newuser['auth_user_md5.perms']) || empty($newuser['auth_user_md5.Email'])) { - $this->msg .= 'error§' . _('Bitte geben Sie Username, Status und E-Mail an!') . '§'; - return false; - } - - // Is the username correct? - if (!$this->validator->ValidateUsername($newuser['auth_user_md5.username'])) { - $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; - return false; - } - - // Can we reach the email? - if (!$this->checkMail($newuser['auth_user_md5.Email'])) { - return false; - } - - if (!$newuser['auth_user_md5.auth_plugin']) { - $newuser['auth_user_md5.auth_plugin'] = 'standard'; - } - - // Store new values in internal array - $this->getFromDatabase(null); - $this->user_data->setData($newuser); - - if ($this->user_data['auth_user_md5.auth_plugin'] === 'standard') { - $password = $this->generate_password(8); - $this->user_data['auth_user_md5.password'] = self::getPwdHasher()->HashPassword($password); - } - - // Does the user already exist? - // NOTE: This should be a transaction, but it is not... - $temp = User::findByUsername($newuser['auth_user_md5.username']); - if ($temp) { - $this->msg .= 'error§' . sprintf(_('BenutzerIn %s ist schon vorhanden!'), $newuser['auth_user_md5.username']) . '§'; - return false; - } - - if (!$this->storeToDatabase()) { - $this->msg .= 'error§' . sprintf(_('BenutzerIn "%s" konnte nicht angelegt werden.'), $newuser['auth_user_md5.username']) . '§'; - return false; - } - - $this->msg .= 'msg§' . sprintf(_('BenutzerIn "%s" angelegt.'), $newuser['auth_user_md5.username']) . '§'; - - // Automated entering new users, based on their status (perms) - $result = AutoInsert::instance()->saveUser($this->user_data['auth_user_md5.user_id'], $this->user_data['auth_user_md5.perms']); - - foreach ($result['added'] as $item) { - $this->msg .= 'msg§' . sprintf(_('Das automatische Eintragen in die Veranstaltung %s wurde durchgeführt.'), $item) . '§'; - } - foreach ($result['removed'] as $item) { - $this->msg .= 'msg§' . sprintf(_('Das automatische Austragen aus der Veranstaltung %s wurde durchgeführt.'), $item) . '§'; - } - - // include language-specific subject and mailbody - $user_language = $this->user_data['user_info.preferred_language'] ?: Config::get()->DEFAULT_LANGUAGE; - - // send mail with password generation link - self::sendPasswordMail($this->user, true); - $this->msg .= 'msg§' . _('Es wurde eine Mail mit Anweisungen zum Setzen des Passworts durch die/den Nutzer/in verschickt.') . '§'; - - // add default visibility settings - Visibility::createDefaultCategories($this->user_data['auth_user_md5.user_id']); - - return true; - } - - /** - * Create a new preliminary studip user with the given parameters - * - * @param array structure: array('string table_name.field_name'=>'string value') - * @return bool Creation successful? - */ - public function createPreliminaryUser($newuser) - { - global $perm; - - $this->getFromDatabase(null); - $this->user_data->setData($newuser); - // Do we have permission to do so? - if (!$perm->have_perm('admin')) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts anzulegen.') . '§'; - return false; - } - if (in_array($this->user->perms, words('root admin'))) { - $this->msg .= 'error§' . _('Es können keine vorläufigen Administrationsaccounts angelegt werden.') . '§'; - return false; - } - if (!$this->user->id) { - $this->user->setId($this->user->getNewId()); - } - if (!$this->user->username) { - $this->user->username = $this->user->id; - } - $this->user->auth_plugin = null; - $this->user->visible = 'never'; - - // Do we have all necessary data? - if (empty ($this->user->perms) || empty ($this->user->vorname) || empty ($this->user->nachname)) { - $this->msg .= 'error§' . _('Bitte geben Sie Status, Vorname und Nachname an!') . '§'; - return false; - } - - // Is the username correct? - if (!$this->validator->ValidateUsername($this->user->username)) { - $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; - return false; - } - - // Does the user already exist? - // NOTE: This should be a transaction, but it is not... - $temp = User::findByUsername($this->user->username); - if ($temp) { - $this->msg .= 'error§' . sprintf(_('BenutzerIn %s ist schon vorhanden!'), $this->user->username) . '§'; - return false; - } - - if (!$this->storeToDatabase()) { - $this->msg .= 'error§' . sprintf(_('BenutzerIn "%s" konnte nicht angelegt werden.'), $this->user->username) . '§'; - return false; - } - - $this->msg .= 'msg§' . sprintf(_('BenutzerIn "%s" (vorläufig) angelegt.'), $this->user->username) . '§'; - - // add default visibility settings - Visibility::createDefaultCategories($this->user->id); - - return true; - } - - /** - * Change an existing studip user according to the given parameters - * - * @param array structure: array('string table_name.field_name'=>'string value') - * @return bool Change successful? - */ - public function changeUser($newuser) - { - global $perm; - - // Do we have permission to do so? - if (!$perm->have_perm('admin')) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu verändern.') . '§'; - return false; - } - - if (!$perm->is_fak_admin() && $newuser['auth_user_md5.perms'] === 'admin') { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Admin-Accounts anzulegen.') . '§'; - return false; - } - - if (!$perm->have_perm('root') && $newuser['auth_user_md5.perms'] === 'root') { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Root-Accounts anzulegen.') . '§'; - return false; - } - - if (!$perm->have_perm('root')) { - if (!$perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Admin-Accounts zu verändern.') . '§'; - return false; - } - - if ($this->user_data['auth_user_md5.perms'] === 'root') { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Root-Accounts zu verändern.') . '§'; - return false; - } - - if ($perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { - if (!$this->adminOK()) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu verändern.') . '§'; - return false; - } - } - } - - // active dozent? (ignore the studygroup guys) - $status = studygroup_sem_types(); - - if (empty($status)) { - $count = 0; - } else { - $query = "SELECT COUNT(*) - FROM seminar_user AS su - LEFT JOIN seminare AS s USING (Seminar_id) - WHERE su.user_id = ? - AND s.status NOT IN (?) - AND su.status = 'dozent' - AND (SELECT COUNT(*) FROM seminar_user su2 WHERE Seminar_id = su.Seminar_id AND su2.status = 'dozent') = 1 - GROUP BY user_id"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $this->user_data['auth_user_md5.user_id'], - $status, - ]); - $count = $statement->fetchColumn(); - } - if ($count && isset($newuser['auth_user_md5.perms']) && $newuser['auth_user_md5.perms'] !== 'dozent') { - $this->msg .= 'error§' . sprintf(_('Der Benutzer %s ist alleiniger Lehrperson in %s aktiven Veranstaltungen und kann daher nicht in einen anderen Status versetzt werden!'), $this->user_data['auth_user_md5.username'], $count) . '§'; - return false; - } - - // active admin? - if ($this->user_data['auth_user_md5.perms'] === 'admin' && $newuser['auth_user_md5.perms'] !== 'admin') { - // count number of institutes where the user is admin - $query = "SELECT COUNT(*) - FROM user_inst - WHERE user_id = ? AND inst_perms = 'admin' - GROUP BY Institut_id"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - - // if there are institutes with admin-perms, add error-message and deny change - if ($count = $statement->fetchColumn()) { - $this->msg .= 'error§'. sprintf(_('Der Benutzer %s ist Admin in %s Einrichtungen und kann daher nicht in einen anderen Status versetzt werden!'), $this->user_data['auth_user_md5.username'], $count) . '§'; - return false; - } - } - - // Is the username correct? - if (isset($newuser['auth_user_md5.username'])) { - if ($this->user_data['auth_user_md5.username'] != $newuser['auth_user_md5.username']) { - if (!$this->validator->ValidateUsername($newuser['auth_user_md5.username'])) { - $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; - return false; - } - $check_uname = StudipAuthAbstract::CheckUsername($newuser['auth_user_md5.username']); - if ($check_uname['found']) { - $this->msg .= 'error§' . _('Der Benutzername wird bereits von einem anderen Benutzer verwendet. Bitte wählen Sie einen anderen Benutzernamen!') . '§'; - return false; - } else { - //$this->msg .= "info§" . $check_uname['error'] ."§"; - } - } else - unset($newuser['auth_user_md5.username']); - } - - // Can we reach the email? - if (isset($newuser['auth_user_md5.Email'])) { - if (!$this->checkMail($newuser['auth_user_md5.Email'])) { - return false; - } - } - - // Store changed values in internal array if allowed - $old_perms = $this->user_data['auth_user_md5.perms']; - $auth_plugin = $this->user_data['auth_user_md5.auth_plugin']; - foreach ($newuser as $key => $value) { - if (!StudipAuthAbstract::CheckField($key, $auth_plugin)) { - $this->user_data[$key] = $value; - } else if ($this->user_data[$key] !== $value) { - $this->msg .= 'error§' . sprintf(_('Das Feld %s können Sie nicht ändern!'), $key) . '§'; - return false; - } - } - - if (!$this->storeToDatabase()) { - $this->msg .= 'info§' . _('Es wurden keine Veränderungen der Grunddaten vorgenommen.') . '§'; - return true; - } - - $this->msg .= 'msg§' . sprintf(_('Benutzer "%s" verändert.'), $this->user_data['auth_user_md5.username']) . '§'; - if ($auth_plugin !== null) { - // Automated entering new users, based on their status (perms) - $result = AutoInsert::instance()->saveUser($this->user_data['auth_user_md5.user_id'], $newuser['auth_user_md5.perms']); - foreach ($result['added'] as $item) { - $this->msg .= 'msg§' . sprintf(_('Das automatische Eintragen in die Veranstaltung %s wurde durchgeführt.'), $item) . '§'; - } - foreach ($result['removed'] as $item) { - $this->msg .= 'msg§' . sprintf(_('Das automatische Austragen aus der Veranstaltung %s wurde durchgeführt.'), $item) . '§'; - } - // include language-specific subject and mailbody - $user_language = getUserLanguagePath($this->user_data['auth_user_md5.user_id']); - $Zeit = strftime('%x, %X'); - - // TODO: This should be refactored so that the included file returns an array - include "locale/{$user_language}/LC_MAILS/change_mail.inc.php"; // Defines $subject and $mailbody - - // send mail - StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject ?? '', $mailbody ?? ''); - } - // Upgrade to admin or root? - if (in_array($newuser['auth_user_md5.perms'], ['admin', 'root'])) { - $this->re_sort_position_in_seminar_user(); - - // delete all seminar entries - $course_member = SimpleCollection::createFromArray( - CourseMember::findByUser($this->user_data['auth_user_md5.user_id']) - ); - $seminar_ids = $course_member->pluck('seminar_id'); - $count = 0; - foreach($course_member as $member) { - $member->delete(); - $count++; - } - if ($count) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus Veranstaltungen gelöscht.'), $count) . '§'; - array_map('AdmissionApplication::addMembers', $seminar_ids); - } - // delete all entries from waiting lists - $admission_members = SimpleCollection::createFromArray( - AdmissionApplication::findByUser($this->user_data['auth_user_md5.user_id']) - ); - $seminar_ids = $admission_members->pluck('seminar_id'); - $count = 0; - foreach ($admission_members as $admission_member) { - $admission_member->delete(); - $count++; - } - if ($count) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus Wartelisten gelöscht.'), $count) . '§'; - array_map('AdmissionApplication::addMembers', $seminar_ids); - } - // delete 'Studiengaenge' - if ($count = UserStudyCourse::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']])) { - $this->msg .= 'info§' . sprintf(_('%s Zuordnungen zu Studiengängen gelöscht.'), $count) . '§'; - } - } - - if ($newuser['auth_user_md5.perms'] === 'admin') { - - $this->logInstUserDel($this->user_data['auth_user_md5.user_id'], "inst_perms != 'admin'"); - $query = "DELETE FROM user_inst WHERE user_id = ? AND inst_perms != 'admin'"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if ($count = $statement->rowCount()) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus MitarbeiterInnenlisten gelöscht.'), $count) . '§'; - } - } - if ($newuser['auth_user_md5.perms'] === 'root') { - $this->logInstUserDel($this->user_data['auth_user_md5.user_id']); - - $query = "DELETE FROM user_inst WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if ($count = $statement->rowCount()) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus MitarbeiterInnenlisten gelöscht.'), $count) . '§'; - } - } - - return true; - } - - private function logInstUserDel($user_id, $condition = null) - { - $query = "SELECT Institut_id FROM user_inst WHERE user_id = ?"; - if (isset($condition)) { - $query .= ' AND ' . $condition; - } - - $statement = DBManager::get()->prepare($query); - $statement->execute([$user_id]); - while ($institute_id = $statement->fetchColumn()) { - StudipLog::log('INST_USER_DEL', $institute_id, $user_id); - } - } - - /** - * Mail a password generation link to the user - * - * @return bool Password change successful? - */ - public function setPassword() - { - global $perm; - - // Do we have permission to do so? - if (!$perm->have_perm('admin')) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu verändern.') . '§'; - return false; - } - - if (!$perm->have_perm('root')) { - if ($this->user_data['auth_user_md5.perms'] === "root") { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Root-Accounts zu verändern.') . '§'; - return false; - } - if ($perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { - if (!$this->adminOK()) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu verändern.') . '§'; - return false; - } - } - } - - // Can we reach the email? - if (!$this->checkMail($this->user_data['auth_user_md5.Email'])) { - return false; - } - - self::sendPasswordMail($this->user); - - return true; - } - - /** - * Send a mail to the user denoted by the passed user-object with a link - * to reset the password. For admin, root and non-standard-auth a notification - * is sent instead. - * - * @param User $user - * - * @return void - */ - public static function sendPasswordMail($user, $new = false) - { - setTempLanguage($user->user_id); - - // always generate a token, so root, admin and all other users profit from the abuse protection - if ($new) { - $expiration_in_hours = 24; - $spoken_expiration = _('24 Stunden'); - } else { - $expiration_in_hours = 7 * 24; - $spoken_expiration = _('eine Woche'); - } - $token = Token::create($expiration_in_hours * 60 * 60, $user->id, true); - - // new users alawys receive a link to generate a password - if ($new) { - $subject = sprintf( - _("[Stud.IP - %s] Es wurde ein Zugang für sie erstellt - Setzen sie ein Passwort"), - Config::get()->UNI_NAME_CLEAN - ); - - $mailbody = sprintf( - _("Dies ist eine Bestätigungsmail des Stud.IP-Systems\n" - ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %1\$s -\n\n" - ."Es wurde für sie ein Zugang zum System erstellt, Ihr Nutzername lautet:\n\n" - ."%2\$s\n\n" - ."Um den Zugang nutzen zu können, müssen sie ein Passwort setzen.\n" - ."Öffnen Sie dafür bitte folgenden Link\n\n" - ."%3\$s\n\n" - ."in Ihrem Browser.\n\n" - ."Der Link ist %4\$s (bis %5\$s) gültig.\n\n" - ."Wahrscheinlich unterstützt Ihr E-Mail-Programm ein einfaches Anklicken des Links.\n" - ."Ansonsten müssen Sie Ihren Browser öffnen und den Link komplett in die Zeile\n" - ."\"Location\" oder \"URL\" kopieren.\n\n" - ), - Config::get()->UNI_NAME_CLEAN, - $user->username, - $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/new_password/set/'. $token->token .'?cancel_login=1', - $spoken_expiration, - strftime('%x %X', $token->expiration) - ); - } else - - // only users with auth-type standard cann reset their password - if ($user->auth_plugin !== 'standard') { - - // inform user, that their password cannot be reset via mail - $subject = sprintf( - _("[Stud.IP - %s] Passwortänderung angefordert"), - Config::get()->UNI_NAME_CLEAN - ); - - $mailbody = sprintf( - _("Dies ist eine Informationsmail des Stud.IP-Systems\n" - ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %s -\n\n" - . "Sie haben einen Link angefordert\n" - . "um das Passwort zurückzusetzen.\n" - . "Dies ist aber für den mit dieser Mail \n" - . "verknüpften Account so nicht möglich.\n\n" - . "Wenden sie sich bitte stattdessen an\n%s" - ), - Config::get()->UNI_NAME_CLEAN, - $GLOBALS['UNI_CONTACT'] - ); - - } else { - - $subject = sprintf( - _("[Stud.IP - %s] Neues Passwort setzen"), - Config::get()->UNI_NAME_CLEAN - ); - - $mailbody = sprintf( - _("Dies ist eine Bestätigungsmail des Stud.IP-Systems\n" - ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %1\$s -\n\n" - ."Sie haben um die Zurücksetzung des Passwortes zu Ihrem Benutzernamen %5\$s gebeten.\n\n" - ."Diese E-Mail wurde Ihnen zugesandt um sicherzustellen,\n" - ."dass die angegebene E-Mail-Adresse tatsächlich Ihnen gehört.\n\n" - ."Wenn Sie um die Zurücksetzung Ihres Passwortes gebeten haben,\n" - ."dann öffnen Sie bitte folgenden Link\n\n" - ."%2\$s\n\n" - ."in Ihrem Browser. Auf der Seite können Sie ein neues Passwort setzen.\n\n" - ."Der Link ist %3\$s (bis %4\$s) gültig.\n\n" - ."Wahrscheinlich unterstützt Ihr E-Mail-Programm ein einfaches Anklicken des Links.\n" - ."Ansonsten müssen Sie Ihren Browser öffnen und den Link komplett in die Zeile\n" - ."\"Location\" oder \"URL\" kopieren.\n\n" - ."Falls Sie nicht diese Mail nicht angefordert haben\n" - ."oder überhaupt nicht wissen, wovon hier die Rede ist,\n" - ."dann hat jemand Ihre E-Mail-Adresse fälschlicherweise verwendet!\n" - ."Ignorieren Sie in diesem Fall diese E-Mail. Es werden dann keine\n" - ."Änderungen an Ihren Zugangsdaten vorgenommen.\n\n" - ), - Config::get()->UNI_NAME_CLEAN, - $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/new_password/set/'. $token->token .'?cancel_login=1', - $spoken_expiration, - strftime('%x %X', $token->expiration), - $user->username - ); - } - - StudipMail::sendMessage($user->email, $subject, $mailbody); - - restoreLanguage(); - } - - /** - * Delete an existing user from the database and tidy up - * - * @param $delete_documents bool delete all documents in course context belonging to the user - * @param $delete_content_from_course bool delete all course content belonging to the user - * @param $delete_personal_documents bool delete all personal documents belonging to the user - * @param $delete_personal_content bool delete all personal content belonging to the user - * @param $delete_names bool delete all names identifying the user - * @param $delete_memberships bool delete all memberships of the user - * @param bool $send_email_notification bool send an email that the account has been deleted - * @return bool Removal successful? - */ - public function deleteUser( - bool $delete_documents = true, - bool $delete_content_from_course = true, - bool $delete_personal_documents = true, - bool $delete_personal_content = true, - bool $delete_names = true, - bool $delete_memberships = true, - bool $send_email_notification = true, - bool $delete_courseware = true - ): bool { - global $perm; - - // Do we have permission to do so? - if (!$perm->have_perm('admin')) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu löschen.') . '§'; - return FALSE; - } - - if (!$perm->have_perm('root')) { - if ($this->user_data['auth_user_md5.perms'] === 'root') { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung Root-Accounts zu löschen.') . '§'; - return false; - } - if ($this->user_data['auth_user_md5.perms'] === 'admin' && !$this->adminOK()) { - $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu löschen.') . '§'; - return false; - } - } - - // active dozent? - $query = "SELECT COUNT(*) - FROM ( - SELECT 1 - FROM `seminar_user` AS `su1` - -- JOIN seminar_user to check for other teachers - INNER JOIN `seminar_user` AS `su2` - ON (`su1`.`seminar_id` = `su2`.`seminar_id` AND `su2`.`status` = 'dozent') - -- JOIN seminare to check the status for studygroup mode - INNER JOIN `seminare` - ON (`su1`.`seminar_id` = `seminare`.`seminar_id`) - WHERE `su1`.`user_id` = :user_id - AND `su1`.`status` = 'dozent' - AND `seminare`.`status` NOT IN ( - -- Select all status ids for studygroups - SELECT `id` - FROM `sem_classes` - WHERE `studygroup_mode` = 1 - ) - GROUP BY `su1`.`seminar_id` - HAVING COUNT(*) = 1 - ORDER BY NULL - ) AS `sub`"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':user_id', $this->user_data['auth_user_md5.user_id']); - $statement->execute(); - $active_count = $statement->fetchColumn() ?: 0; - - if ($active_count && $delete_memberships) { - $this->msg .= 'error§' . sprintf(_('%s ist Lehrkraft in %s aktiven Veranstaltungen und kann daher nicht gelöscht werden.'), $this->user_data['auth_user_md5.username'], $active_count) . '§'; - return false; - //founder of studygroup? - } elseif (Config::get()->STUDYGROUPS_ENABLE) { - $status = studygroup_sem_types(); - - if (empty($status)) { - $group_ids = []; - } else { - $query = "SELECT Seminar_id - FROM seminare AS s - LEFT JOIN seminar_user AS su USING (Seminar_id) - WHERE su.status = 'dozent' AND su.user_id = ? AND s.status IN (?)"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $this->user_data['auth_user_md5.user_id'], - $status, - ]); - $group_ids = $statement->fetchAll(PDO::FETCH_COLUMN); - } - - foreach ($group_ids as $group_id) { - $sem = Seminar::GetInstance($group_id); - if (StudygroupModel::countMembers($group_id) > 1) { - // check whether there are tutors or even autors that can be promoted - $tutors = $sem->getMembers('tutor'); - $autors = $sem->getMembers('autor'); - if (count($tutors) > 0) { - $new_founder = current($tutors); - StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); - continue; - } - // if not promote an autor - elseif (count($autors) > 0) { - $new_founder = current($autors); - StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); - continue; - } - // since no suitable successor was found, we are allowed to remove the studygroup - } else { - $sem->delete(); - } - unset($sem); - } - } - - // store user preferred language for sending mail - $user_language = getUserLanguagePath($this->user_data['auth_user_md5.user_id']); - - // Load privacy plugins to ensure all event handlers can react to the - // UserDataDidRemove event - PluginEngine::getPlugins(PrivacyPlugin::class); - - // delete user from instituts - $this->logInstUserDel($this->user_data['auth_user_md5.user_id']); - - if ($delete_memberships) { - $query = "DELETE FROM user_inst WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if ($count = $statement->rowCount()) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus MitarbeiterInnenlisten gelöscht.'), $count) . '§'; - } - - // delete user from Statusgruppen - if ($count = StatusgruppeUser::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']])) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus Funktionen / Gruppen gelöscht.'), $count) . '§'; - } - - // delete user from archiv - $query = "DELETE FROM archiv_user WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if ($count = $statement->rowCount()) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus den Zugriffsberechtigungen für das Archiv gelöscht.'), $count) . '§'; - } - - // delete 'Studiengaenge' - $query = "DELETE FROM user_studiengang WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if ($count = $statement->rowCount()) { - $this->msg .= 'info§' . sprintf(_('%s Zuordnungen zu Studiengängen gelöscht.'), $count) . '§'; - } - - $this->re_sort_position_in_seminar_user(); - - // delete user from seminars (postings will be preserved) - $query = "DELETE FROM seminar_user WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if ($count = $statement->rowCount()) { - $this->msg .= 'info§' . sprintf(_('%s Einträge aus Veranstaltungen gelöscht.'), $count) . '§'; - } - - $query = "DELETE FROM `termin_related_persons` WHERE `user_id` = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if ($count = $statement->rowCount()) { - $this->msg .= 'info§' . sprintf(_('%u Terminzuordnungen gelöscht.'), $count) . '§'; - } - - // delete visibility settings - Visibility::removeUserPrivacySettings($this->user_data['auth_user_md5.user_id']); - - // delete deputy entries if necessary - $query = "DELETE FROM deputies WHERE ? IN (user_id, range_id)"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - $deputyEntries = $statement->rowCount(); - if ($deputyEntries) { - $this->msg .= 'info§' . sprintf(_('%s Einträge in den Vertretungseinstellungen gelöscht.'), $deputyEntries) . '§'; - } - - // delete all remaining user data - $queries = [ - "DELETE FROM user_userdomains WHERE user_id = ?", - ]; - foreach ($queries as $query) { - DBManager::get()->execute($query, [$this->user_data['auth_user_md5.user_id']]); - } - NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'memberships'); - } - - // delete documents of this user - if ($delete_documents) { - $db_filecount = FileRef::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - if ($db_filecount > 0) { - $this->msg .= 'info§' . sprintf(_('%s Dateien aus Veranstaltungen und Einrichtungen gelöscht.'), $db_filecount) . '§'; - } - NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'course_documents'); - } - - // always delete personal courseware elements of this user - \Courseware\Unit::deleteBySQL('range_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\UserDataField::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\UserProgress::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\Bookmark::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\Task::deleteBySQL( - '`solver_id` = ? AND `solver_type`= "autor"', - [$this->user_data['auth_user_md5.user_id']] - ); - // delete courseware elements in courses of this user - if ($delete_courseware) { - \Courseware\Unit::deleteBySQL('creator_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\StructuralElement::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\StructuralElementFeedback::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\StructuralElementComment::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\Container::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\Block::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\BlockFeedback::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - \Courseware\BlockComment::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); - } - - // delete all remaining user data in course context if option selected - if ($delete_content_from_course) { - $queries = [ - "DELETE FROM questionnaires WHERE user_id = ?", - "DELETE FROM questionnaire_answers WHERE user_id = ?", - "DELETE FROM questionnaire_assignments WHERE user_id = ?", - "DELETE FROM questionnaire_anonymous_answers WHERE user_id = ?", - "DELETE FROM etask_assignment_attempts WHERE user_id = ?", - "DELETE FROM etask_responses WHERE user_id = ?", - "DELETE FROM etask_tasks WHERE user_id = ?", - "DELETE FROM etask_tests WHERE user_id = ?", - ]; - foreach ($queries as $query) { - DBManager::get()->execute($query, [$this->user_data['auth_user_md5.user_id']]); - } - NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'course_contents'); - } - - if ($delete_personal_documents) { - $user_folder = Folder::findTopFolder($this->user->id); - if ($user_folder) { - $this->msg .= 'info§' . _('Persönlicher Dateibereich gelöscht.') . '§'; - $user_folder->delete(); - } - NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'personal_documents'); - } - - if ($delete_personal_content) { - $this->msg .= $this->deletePersonalData($this->user_data['auth_user_md5.user_id']); - NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'personal_contents'); - } - - if ($delete_names) { - $query = "UPDATE auth_user_md5 - SET username = ?, Vorname = '', Nachname = '', Email = '' - WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - md5($this->user_data['auth_user_md5.username'].uniqid('delete_user')), - $this->user_data['auth_user_md5.user_id'] - ]); - if ($statement->rowCount() > 0) { - $this->msg .= 'info§' . _('Benutzername anonymisiert.') . '§'; - } - NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'names'); - } - - if ($delete_personal_documents && $delete_personal_content && $delete_names && $delete_memberships) { - // Delete the user from resource property entries of type "user": - ResourceProperty::deleteBySQL( - "`property_id` IN ( - SELECT `property_id` - FROM `resource_property_definitions` - WHERE `type` = 'user' - ) - AND `state` = :user_id", - ['user_id' => $this->user_data['auth_user_md5.user_id']] - ); - - // delete Stud.IP account - $query = "DELETE FROM user_info WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - - $query = "DELETE FROM auth_user_md5 WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - if (!$statement->rowCount()) { - $this->msg .= 'error§' . _('Fehler:') . ' ' . $query . '§'; - return false; - } else { - $this->msg .= 'msg§' . sprintf(_('Benutzer "%s" gelöscht.'), $this->user_data['auth_user_md5.username']) . '§'; - } - StudipLog::log('USER_DEL', $this->user_data['auth_user_md5.user_id'], null, sprintf('%s %s (%s)', $this->user_data['auth_user_md5.Vorname'], $this->user_data['auth_user_md5.Nachname'], $this->user_data['auth_user_md5.username'])); //log with Vorname Nachname (username) as info string - - // Can we reach the email? - if ( - $send_email_notification - && $this->checkMail($this->user_data['auth_user_md5.Email']) - ) { - // include language-specific subject and mailbody - $Zeit = strftime('%x, %X'); - - // TODO: This should be refactored so that the included file returns an array - include "locale/$user_language/LC_MAILS/delete_mail.inc.php"; // Defines $subject and $mailbody - - // send mail - StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject ?? '', $mailbody ?? ''); - } - - // Remove plugin associations/activations - PluginManager::getInstance()->deactivateAllPluginsForRange( - 'user', - $this->user_data['auth_user_md5.user_id'] - ); - - // Trigger delete on sorm object which will fire notifications - // - // TODO: Remove everything from this method that would also be - // deleted in User::delete() (TODO!!!) - $this->user->delete(); - - unset($this->user_data); - } - - return true; - } - - /** - * Delete personal userdata - * - * @param string $user_id the user which should be retrieved - * @return string Removal messages - */ - private function deletePersonalData($user_id) - { - $msg = ''; - - // delete the datafields - $localEntries = DataFieldEntry::removeAll($user_id); - - // delete user from waiting lists - $query = "SELECT seminar_id FROM admission_seminar_user WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$user_id]); - $seminar_ids = $statement->fetchAll(PDO::FETCH_COLUMN); - - $query = "DELETE FROM admission_seminar_user WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$user_id]); - if ($count = $statement->rowCount()) { - $msg .= 'info§' . sprintf(_('%s Einträge aus Wartelisten gelöscht.'), $count) . '§'; - array_map('AdmissionApplication::addMembers', $seminar_ids); - } - - // delete all personal news from this user - if ($count = StudipNews::DeleteNewsByAuthor($user_id)) { - $msg .= 'info§' . sprintf(_('%s Einträge aus den Ankündigungen gelöscht.'), $count) . '§'; - } - if ($count = StudipNews::DeleteNewsRanges($user_id)) { - $msg .= 'info§' . sprintf(_('%s Verweise auf Ankündigungen gelöscht.'), $count) . '§'; - } - - //delete entry in news_rss_range - StudipNews::UnsetRssId($user_id); - - // delete all private appointments of this user - if (Config::get()->CALENDAR_ENABLE) { - // delete private appointments (omit group appointments) - $count = CalendarDate::deleteBySQL( - '`id` IN ( - SELECT `id` - FROM ( - SELECT `id`, COUNT(*) - FROM `calendar_dates` - JOIN `calendar_date_assignments` - ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` - WHERE `calendar_dates`.`author_id` = :user_id - GROUP BY `id` - HAVING COUNT(*) = 1 - ORDER BY NULL - ) AS `cal_date_delete` - )', - [':user_id' => $user_id] - ); - // delete assignments to group appointments - $count += CalendarDateAssignment::deleteBySQL('`range_id` = ?', [$user_id]); - if ($count) { - $msg .= 'info§' . sprintf(_('%s Einträge aus den Terminen gelöscht.'), $count) . '§'; - } - } - - // delete all messages send or received by this user - $messaging = new messaging(); - $messaging->delete_all_messages($user_id); - - // delete user from all foreign adressbooks and empty own adressbook - $count = Contact::deleteBySQL('user_id = ?', [$user_id]); - if ($count > 0) { - $msg .= 'info§' . sprintf(_('%s Einträge aus Adressbüchern gelöscht.'), $count) . '§'; - } - $count = Contact::deleteBySQL('owner_id = ?', [$user_id]); - if ($count) { - $msg .= 'info§' . sprintf(_('Adressbuch mit %d Einträgen gelöscht.'), $count) . '§'; - } - - // delete users groups - Statusgruppen::deleteBySQL('range_id = ?', [$user_id]); - - // remove user from any groups - StatusgruppeUser::deleteBySQL('user_id = ?', [$user_id]); - - // delete user config values - ConfigValue::deleteBySQL('range_id = ?', [$user_id]); - - // delete all remaining user data - $queries = [ - "DELETE FROM kategorien WHERE range_id = ?", - "DELETE FROM user_visibility WHERE user_id = ?", - "DELETE FROM user_online WHERE user_id = ?", - "DELETE FROM auto_insert_user WHERE user_id = ?", - "DELETE FROM roles_user WHERE userid = ?", - "DELETE FROM schedule WHERE user_id = ?", - "DELETE FROM schedule_seminare WHERE user_id = ?", - "DELETE FROM termin_related_persons WHERE user_id = ?", - "DELETE FROM priorities WHERE user_id = ?", - "DELETE FROM api_oauth_user_mapping WHERE user_id = ?", - "DELETE FROM api_user_permissions WHERE user_id = ?", - "DELETE FROM help_tour_user WHERE user_id = ?", - "DELETE FROM personal_notifications_user WHERE user_id = ?", - "DELETE FROM forum_abo_users WHERE user_id = ?", - "DELETE FROM forum_favorites WHERE user_id = ?", - - "DELETE FROM comments WHERE user_id = ?", - "DELETE questionnaires FROM questionnaires LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", - "DELETE questionnaire_answers FROM questionnaire_answers LEFT JOIN questionnaire_questions USING (`question_id`) LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", - "DELETE questionnaire_anonymous_answers FROM questionnaire_anonymous_answers LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", - "DELETE FROM questionnaire_assignments WHERE user_id = ?", - "DELETE etask_assignment_attempts FROM etask_assignment_attempts LEFT JOIN etask_assignments ea ON (`assignment_id` = ea.id) WHERE ea.range_type = 'user' AND user_id = ?", - "DELETE etask_responses FROM etask_responses LEFT JOIN etask_assignments ea ON (`assignment_id` = ea.id) WHERE ea.range_type = 'user' AND user_id = ?", - "DELETE etask_tasks FROM etask_tasks LEFT JOIN etask_test_tasks tt ON (etask_tasks.id = tt.task_id) LEFT JOIN etask_assignments ea ON (tt.`test_id` = ea.test_id) WHERE ea.range_type = 'user' AND user_id = ?", - "DELETE etask_tests FROM etask_tests LEFT JOIN etask_assignments ea ON (`test_id` = ea.test_id) WHERE ea.range_type = 'user' AND user_id = ?", - - "UPDATE forum_entries SET author = '' WHERE user_id = ?", - "UPDATE auth_user_md5 SET visible = 'never' WHERE user_id = ?", - - "REPLACE INTO `user_info` (`user_id`, `hobby`, `lebenslauf`, `publi`, `schwerp`, `Home`, `privatnr`, `privatcell`, `privadr`, `score`, `geschlecht`, `mkdate`, `chdate`, `title_front`, `title_rear`, `preferred_language`, `smsforward_copy`, `smsforward_rec`, `email_forward`, `motto`, `lock_rule`) VALUES(?, '', '', '', '', '', '', '', '', 0, 0, 0, 0, '', '', NULL, 1, '', 0, '', '');" - ]; - foreach ($queries as $query) { - DBManager::get()->execute($query, [$user_id]); - } - - // Clean up orphaned items - $queries = [ - "DELETE FROM personal_notifications WHERE personal_notification_id NOT IN ( - SELECT personal_notification_id FROM personal_notifications_user - )", - ]; - foreach ($queries as $query) { - DBManager::get()->exec($query); - } - - object_kill_visits($user_id); - object_kill_views($user_id); - - // delete picture - $avatar = Avatar::getAvatar($user_id); - if ($avatar->is_customized()) { - $avatar->reset(); - $msg .= 'info§' . _('Bild gelöscht.') . '§'; - } - - //delete connected users - if (Config::get()->ELEARNING_INTERFACE_ENABLE) { - if (ELearningUtils::initElearningInterfaces()) { - foreach ($GLOBALS['connected_cms'] as $cms){ - if ($cms->auth_necessary && $cms->user instanceOf ConnectedUser) { - $user_auto_create = $cms->USER_AUTO_CREATE; - $cms->USER_AUTO_CREATE = false; - $userclass = mb_strtolower(get_class($cms->user)); - $connected_user = new $userclass($cms->cms_type, $user_id); - if ($connected_user->deleteUser() && $connected_user->is_connected) { - $msg .= 'info§' . sprintf(_('Der verknüpfte Nutzer %s wurde im System %s gelöscht.'), $connected_user->login, $connected_user->cms_type) . '§'; - } - $cms->USER_AUTO_CREATE = $user_auto_create; - } - } - } - } - - return $msg; - } - - private function adminOK() - { - static $ok = null; - - if ($ok === null) { - $query = "SELECT COUNT(a.Institut_id) = COUNT(c.inst_perms) - FROM user_inst AS a - LEFT JOIN Institute b ON (a.Institut_id = b.Institut_id AND b.Institut_id != b.fakultaets_id) - LEFT JOIN user_inst AS c ON (b.fakultaets_id = c.Institut_id AND c.user_id = ? - AND c.inst_perms = 'admin') - WHERE a.user_id = ? AND a.inst_perms = 'admin'"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $GLOBALS['auth']->auth['uid'], - $this->user_data['auth_user_md5.user_id'], - ]); - $ok = $statement->fetchColumn(); - } - - return $ok; - } - - private function re_sort_position_in_seminar_user() - { - $query = "SELECT Seminar_id, position, status - FROM seminar_user - WHERE user_id = ? AND status IN ('tutor', 'dozent')"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->user_data['auth_user_md5.user_id']]); - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - if ($row['status'] === 'tutor') { - CourseMember::resortMembership($row['Seminar_id'], (int)$row['position']); - } else if ($row['status'] === 'dozent') { - CourseMember::resortMembership($row['Seminar_id'], (int)$row['position'], 'dozent'); - } - } - } - - /** - * Change an existing user password - * - * @param string $password - * @return bool change successful? - */ - public function changePassword($password) - { - $this->user_data['auth_user_md5.password'] = self::getPwdHasher()->HashPassword($password); - $this->storeToDatabase(); - - $this->msg .= 'msg§' . _('Das Passwort wurde neu gesetzt.') . '§'; - - // include language-specific subject and mailbody - setTempLanguage($this->user_data['auth_user_md5.user_id']); - - $subject = sprintf( - _("[Stud.IP - %s] Passwortänderung"), - Config::get()->UNI_NAME_CLEAN - ); - - $mailbody = sprintf( - _("Dies ist eine Informationsmail des Stud.IP-Systems\n" - ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %s -\n\n" - ."Ihr Passwort wurde soeben von Ihnen oder einem/einer Administrator/in geändert.\n" - ), - Config::get()->UNI_NAME_CLEAN - ); - - // send mail - StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject, $mailbody); - - restoreLanguage(); - - StudipLog::log('USER_NEWPWD', $this->user_data['auth_user_md5.user_id']); - - return true; - } -} diff --git a/lib/classes/UserManagement.php b/lib/classes/UserManagement.php new file mode 100644 index 0000000..015e6cd --- /dev/null +++ b/lib/classes/UserManagement.php @@ -0,0 +1,1353 @@ + + * @author Suchi & Berg GmbH + * @copyright 2009 Stud.IP + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL Licence 2 + * @category Stud.IP + */ + +// Imports +require_once 'lib/statusgruppe.inc.php'; // remove user from statusgroups +require_once 'lib/messaging.inc.php'; // remove messages send or recieved by user +require_once 'lib/object.inc.php'; + +/** + * UserManagement.class.php + * + * Management for the Stud.IP global users + * + */ +class UserManagement +{ + private $user; + private $validator; + private $user_data; + + public $msg; + + private static $pwd_hasher; + + public static function getPwdHasher() + { + if (self::$pwd_hasher === null) { + self::$pwd_hasher = new PasswordHash(8, Config::get()->PHPASS_USE_PORTABLE_HASH); + } + return self::$pwd_hasher; + } + + /** + * Constructor + * + * Pass nothing to create a new user, or the user_id from an existing user to change or delete + * @param string $user_id the user which should be retrieved + */ + public function __construct($user_id = false) + { + $this->validator = new email_validation_class(); + $this->validator->timeout = 10; // How long do we wait for response of mailservers? + $this->getFromDatabase($user_id); + } + + public function __get($attr) + { + if ($attr === 'user_data') { + return $this->user_data; + } + } + + public function __set($attr, $value) + { + if ($attr === 'user_data') { + if (!is_array($value)) { + throw new InvalidArgumentException('user_data only accepts array'); + } + return $this->user_data->setData($value, true); + } + } + + /** + * load user data from database into internal array + * + * @param string $user_id the user which should be retrieved + */ + public function getFromDatabase($user_id) + { + $this->user = User::toObject($user_id); + if (!$this->user) { + $this->user = new User(); + } + $this->user_data = new UserDataAdapter($this->user); + } + + /** + * store user data from internal array into database + * + * @access private + * @return bool all data stored? + */ + private function storeToDatabase() + { + if ($this->user->isNew()) { + if ($this->user->store()) { + StudipLog::log('USER_CREATE', $this->user->id, null, implode(';', $this->user->toArray('username vorname nachname perms email'))); + return true; + } else { + return false; + } + } + + $nperms = [ + 'user' => 0, + 'autor' => 1, + 'tutor' => 2, + 'dozent' => 3 + ]; + + if ($this->user->isDirty('perms')) { + if ($this->user->perms === 'dozent' && in_array($this->user->getPristineValue('perms'), ['user','autor','tutor'])) { + $this->logInstUserDel($this->user->id, "inst_perms = 'user'"); + $this->user->institute_memberships->unsetBy('inst_perms', 'user'); + // make user visible globally if dozent may not be invisible (StEP 00158) + if (Config::get()->DOZENT_ALWAYS_VISIBLE && $this->user->visible !== 'never') { + $this->user->visible = 'yes'; + } + if ($nperms[$this->user->perms] < $nperms[$this->user->getPristineValue('perms')]) { + $old_status = []; + foreach ($nperms as $status => $n) { + if ($n > $nperms[$this->user->perms] && $n <= $nperms[$this->user->getPristineValue('perms')]) { + $old_status[] = $status; + } + } + $new_status = $this->user->perms; + CourseMember::findEachBySQL( + function (CourseMember $cm) use ($new_status) { + $cm->status = $new_status; + $cm->store(); + }, + 'INNER JOIN seminare ON (seminare.Seminar_id = seminar_user.Seminar_id) + WHERE seminar_user.user_id = ? + AND seminar_user.status IN (?) + AND seminare.status NOT IN (?) + ', + [ + $this->user->id, + $old_status, + studygroup_sem_types() + ] + ); + } + } + } + foreach (words('username vorname nachname perms email title_front title_rear password') as $field) { + // logging + if ($this->user->isFieldDirty($field)) { + $old_value = $this->user->getPristineValue($field); + $value = $this->user->getValue($field); + switch ($field) { + case 'username': + StudipLog::log('USER_CHANGE_USERNAME', $this->user->id, null, "{$old_value} -> {$value}"); + break; + case 'vorname': + StudipLog::log('USER_CHANGE_NAME', $this->user->id, null, "Vorname: {$old_value} -> {$value}"); + break; + case 'nachname': + StudipLog::log('USER_CHANGE_NAME', $this->user->id, null, "Nachname: {$old_value} -> {$value}"); + break; + case 'perms': + StudipLog::log('USER_CHANGE_PERMS', $this->user->id, null, "{$old_value} -> {$value}"); + break; + case 'email': + StudipLog::log('USER_CHANGE_EMAIL', $this->user->id, null, "{$old_value} -> {$value}"); + break; + case 'title_front': + StudipLog::log('USER_CHANGE_TITLE', $this->user->id, null, "title_front: {$old_value} -> {$value}"); + break; + case 'title_rear': + StudipLog::log('USER_CHANGE_TITLE', $this->user->id, null, "title_rear: {$old_value} -> {$value}"); + case 'password': + StudipLog::log('USER_CHANGE_PASSWORD', $this->user->id, null, "password: {$old_value} -> {$value}"); + break; + } + } + } + + $changed = $this->user->store(); + return (bool) $changed; + } + + + /** + * generate a secure password of $length characters [a-z0-9] + * + * @param integer $length number of characters + * @return string password + */ + public function generate_password($length) + { + $pass = ""; + mt_srand((double) microtime() * 1000000); + for ($i = 1; $i <= $length; $i++) { + $temp = mt_rand() % 36; + if ($temp < 10) { + $temp += 48; // 0 = chr(48), 9 = chr(57) + } else { + $temp += 87; // a = chr(97), z = chr(122) + } + $pass .= chr($temp); + } + return $pass; + } + + + /** + * Check if Email-Adress is valid and reachable + * + * @param string Email-Adress to check + * @return bool Email-Adress valid and reachable? + */ + private function checkMail($Email) + { + // Adress correct? + if (!$this->validator->ValidateEmailAddress($Email)) { + $this->msg .= 'error§' . _('E-Mail-Adresse syntaktisch falsch!') . '§'; + return false; + } + + // E-Mail reachable? + if (!$this->validator->ValidateEmailHost($Email)) { + // Mailserver nicht erreichbar, ablehnen + $this->msg .= 'error§' . _('Mailserver ist nicht erreichbar!') . '§'; + return false; + } + + if (!$this->validator->ValidateEmailBox($Email)) { + // Nutzer unbekannt, ablehnen + $this->msg .= 'error§' . sprintf(_('E-Mail an %s ist nicht zustellbar!'), $Email) . '§'; + return false; + } + + return true; + } + + /** + * Create a new studip user with the given parameters + * + * @param array structure: array('string table_name.field_name'=>'string value') + * @return bool Creation successful? + */ + public function createNewUser($newuser) + { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts anzulegen.') . '§'; + return false; + } + + if (!$perm->is_fak_admin() && $newuser['auth_user_md5.perms'] === 'admin') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Admin-Accounts anzulegen.') . '§'; + return false; + } + + if (!$perm->have_perm('root') && $newuser['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Root-Accounts anzulegen.') . '§'; + return false; + } + + // Do we have all necessary data? + if (empty($newuser['auth_user_md5.username']) || empty($newuser['auth_user_md5.perms']) || empty($newuser['auth_user_md5.Email'])) { + $this->msg .= 'error§' . _('Bitte geben Sie Username, Status und E-Mail an!') . '§'; + return false; + } + + // Is the username correct? + if (!$this->validator->ValidateUsername($newuser['auth_user_md5.username'])) { + $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; + return false; + } + + // Can we reach the email? + if (!$this->checkMail($newuser['auth_user_md5.Email'])) { + return false; + } + + if (!$newuser['auth_user_md5.auth_plugin']) { + $newuser['auth_user_md5.auth_plugin'] = 'standard'; + } + + // Store new values in internal array + $this->getFromDatabase(null); + $this->user_data->setData($newuser); + + if ($this->user_data['auth_user_md5.auth_plugin'] === 'standard') { + $password = $this->generate_password(8); + $this->user_data['auth_user_md5.password'] = self::getPwdHasher()->HashPassword($password); + } + + // Does the user already exist? + // NOTE: This should be a transaction, but it is not... + $temp = User::findByUsername($newuser['auth_user_md5.username']); + if ($temp) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn %s ist schon vorhanden!'), $newuser['auth_user_md5.username']) . '§'; + return false; + } + + if (!$this->storeToDatabase()) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn "%s" konnte nicht angelegt werden.'), $newuser['auth_user_md5.username']) . '§'; + return false; + } + + $this->msg .= 'msg§' . sprintf(_('BenutzerIn "%s" angelegt.'), $newuser['auth_user_md5.username']) . '§'; + + // Automated entering new users, based on their status (perms) + $result = AutoInsert::instance()->saveUser($this->user_data['auth_user_md5.user_id'], $this->user_data['auth_user_md5.perms']); + + foreach ($result['added'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Eintragen in die Veranstaltung %s wurde durchgeführt.'), $item) . '§'; + } + foreach ($result['removed'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Austragen aus der Veranstaltung %s wurde durchgeführt.'), $item) . '§'; + } + + // include language-specific subject and mailbody + $user_language = $this->user_data['user_info.preferred_language'] ?: Config::get()->DEFAULT_LANGUAGE; + + // send mail with password generation link + self::sendPasswordMail($this->user, true); + $this->msg .= 'msg§' . _('Es wurde eine Mail mit Anweisungen zum Setzen des Passworts durch die/den Nutzer/in verschickt.') . '§'; + + // add default visibility settings + Visibility::createDefaultCategories($this->user_data['auth_user_md5.user_id']); + + return true; + } + + /** + * Create a new preliminary studip user with the given parameters + * + * @param array structure: array('string table_name.field_name'=>'string value') + * @return bool Creation successful? + */ + public function createPreliminaryUser($newuser) + { + global $perm; + + $this->getFromDatabase(null); + $this->user_data->setData($newuser); + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts anzulegen.') . '§'; + return false; + } + if (in_array($this->user->perms, words('root admin'))) { + $this->msg .= 'error§' . _('Es können keine vorläufigen Administrationsaccounts angelegt werden.') . '§'; + return false; + } + if (!$this->user->id) { + $this->user->setId($this->user->getNewId()); + } + if (!$this->user->username) { + $this->user->username = $this->user->id; + } + $this->user->auth_plugin = null; + $this->user->visible = 'never'; + + // Do we have all necessary data? + if (empty ($this->user->perms) || empty ($this->user->vorname) || empty ($this->user->nachname)) { + $this->msg .= 'error§' . _('Bitte geben Sie Status, Vorname und Nachname an!') . '§'; + return false; + } + + // Is the username correct? + if (!$this->validator->ValidateUsername($this->user->username)) { + $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; + return false; + } + + // Does the user already exist? + // NOTE: This should be a transaction, but it is not... + $temp = User::findByUsername($this->user->username); + if ($temp) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn %s ist schon vorhanden!'), $this->user->username) . '§'; + return false; + } + + if (!$this->storeToDatabase()) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn "%s" konnte nicht angelegt werden.'), $this->user->username) . '§'; + return false; + } + + $this->msg .= 'msg§' . sprintf(_('BenutzerIn "%s" (vorläufig) angelegt.'), $this->user->username) . '§'; + + // add default visibility settings + Visibility::createDefaultCategories($this->user->id); + + return true; + } + + /** + * Change an existing studip user according to the given parameters + * + * @param array structure: array('string table_name.field_name'=>'string value') + * @return bool Change successful? + */ + public function changeUser($newuser) + { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu verändern.') . '§'; + return false; + } + + if (!$perm->is_fak_admin() && $newuser['auth_user_md5.perms'] === 'admin') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Admin-Accounts anzulegen.') . '§'; + return false; + } + + if (!$perm->have_perm('root') && $newuser['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, Root-Accounts anzulegen.') . '§'; + return false; + } + + if (!$perm->have_perm('root')) { + if (!$perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Admin-Accounts zu verändern.') . '§'; + return false; + } + + if ($this->user_data['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Root-Accounts zu verändern.') . '§'; + return false; + } + + if ($perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { + if (!$this->adminOK()) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu verändern.') . '§'; + return false; + } + } + } + + // active dozent? (ignore the studygroup guys) + $status = studygroup_sem_types(); + + if (empty($status)) { + $count = 0; + } else { + $query = "SELECT COUNT(*) + FROM seminar_user AS su + LEFT JOIN seminare AS s USING (Seminar_id) + WHERE su.user_id = ? + AND s.status NOT IN (?) + AND su.status = 'dozent' + AND (SELECT COUNT(*) FROM seminar_user su2 WHERE Seminar_id = su.Seminar_id AND su2.status = 'dozent') = 1 + GROUP BY user_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $this->user_data['auth_user_md5.user_id'], + $status, + ]); + $count = $statement->fetchColumn(); + } + if ($count && isset($newuser['auth_user_md5.perms']) && $newuser['auth_user_md5.perms'] !== 'dozent') { + $this->msg .= 'error§' . sprintf(_('Der Benutzer %s ist alleiniger Lehrperson in %s aktiven Veranstaltungen und kann daher nicht in einen anderen Status versetzt werden!'), $this->user_data['auth_user_md5.username'], $count) . '§'; + return false; + } + + // active admin? + if ($this->user_data['auth_user_md5.perms'] === 'admin' && $newuser['auth_user_md5.perms'] !== 'admin') { + // count number of institutes where the user is admin + $query = "SELECT COUNT(*) + FROM user_inst + WHERE user_id = ? AND inst_perms = 'admin' + GROUP BY Institut_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + + // if there are institutes with admin-perms, add error-message and deny change + if ($count = $statement->fetchColumn()) { + $this->msg .= 'error§'. sprintf(_('Der Benutzer %s ist Admin in %s Einrichtungen und kann daher nicht in einen anderen Status versetzt werden!'), $this->user_data['auth_user_md5.username'], $count) . '§'; + return false; + } + } + + // Is the username correct? + if (isset($newuser['auth_user_md5.username'])) { + if ($this->user_data['auth_user_md5.username'] != $newuser['auth_user_md5.username']) { + if (!$this->validator->ValidateUsername($newuser['auth_user_md5.username'])) { + $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; + return false; + } + $check_uname = StudipAuthAbstract::CheckUsername($newuser['auth_user_md5.username']); + if ($check_uname['found']) { + $this->msg .= 'error§' . _('Der Benutzername wird bereits von einem anderen Benutzer verwendet. Bitte wählen Sie einen anderen Benutzernamen!') . '§'; + return false; + } else { + //$this->msg .= "info§" . $check_uname['error'] ."§"; + } + } else + unset($newuser['auth_user_md5.username']); + } + + // Can we reach the email? + if (isset($newuser['auth_user_md5.Email'])) { + if (!$this->checkMail($newuser['auth_user_md5.Email'])) { + return false; + } + } + + // Store changed values in internal array if allowed + $old_perms = $this->user_data['auth_user_md5.perms']; + $auth_plugin = $this->user_data['auth_user_md5.auth_plugin']; + foreach ($newuser as $key => $value) { + if (!StudipAuthAbstract::CheckField($key, $auth_plugin)) { + $this->user_data[$key] = $value; + } else if ($this->user_data[$key] !== $value) { + $this->msg .= 'error§' . sprintf(_('Das Feld %s können Sie nicht ändern!'), $key) . '§'; + return false; + } + } + + if (!$this->storeToDatabase()) { + $this->msg .= 'info§' . _('Es wurden keine Veränderungen der Grunddaten vorgenommen.') . '§'; + return true; + } + + $this->msg .= 'msg§' . sprintf(_('Benutzer "%s" verändert.'), $this->user_data['auth_user_md5.username']) . '§'; + if ($auth_plugin !== null) { + // Automated entering new users, based on their status (perms) + $result = AutoInsert::instance()->saveUser($this->user_data['auth_user_md5.user_id'], $newuser['auth_user_md5.perms']); + foreach ($result['added'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Eintragen in die Veranstaltung %s wurde durchgeführt.'), $item) . '§'; + } + foreach ($result['removed'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Austragen aus der Veranstaltung %s wurde durchgeführt.'), $item) . '§'; + } + // include language-specific subject and mailbody + $user_language = getUserLanguagePath($this->user_data['auth_user_md5.user_id']); + $Zeit = strftime('%x, %X'); + + // TODO: This should be refactored so that the included file returns an array + include "locale/{$user_language}/LC_MAILS/change_mail.inc.php"; // Defines $subject and $mailbody + + // send mail + StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject ?? '', $mailbody ?? ''); + } + // Upgrade to admin or root? + if (in_array($newuser['auth_user_md5.perms'], ['admin', 'root'])) { + $this->re_sort_position_in_seminar_user(); + + // delete all seminar entries + $course_member = SimpleCollection::createFromArray( + CourseMember::findByUser($this->user_data['auth_user_md5.user_id']) + ); + $seminar_ids = $course_member->pluck('seminar_id'); + $count = 0; + foreach($course_member as $member) { + $member->delete(); + $count++; + } + if ($count) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus Veranstaltungen gelöscht.'), $count) . '§'; + array_map('AdmissionApplication::addMembers', $seminar_ids); + } + // delete all entries from waiting lists + $admission_members = SimpleCollection::createFromArray( + AdmissionApplication::findByUser($this->user_data['auth_user_md5.user_id']) + ); + $seminar_ids = $admission_members->pluck('seminar_id'); + $count = 0; + foreach ($admission_members as $admission_member) { + $admission_member->delete(); + $count++; + } + if ($count) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus Wartelisten gelöscht.'), $count) . '§'; + array_map('AdmissionApplication::addMembers', $seminar_ids); + } + // delete 'Studiengaenge' + if ($count = UserStudyCourse::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']])) { + $this->msg .= 'info§' . sprintf(_('%s Zuordnungen zu Studiengängen gelöscht.'), $count) . '§'; + } + } + + if ($newuser['auth_user_md5.perms'] === 'admin') { + + $this->logInstUserDel($this->user_data['auth_user_md5.user_id'], "inst_perms != 'admin'"); + $query = "DELETE FROM user_inst WHERE user_id = ? AND inst_perms != 'admin'"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus MitarbeiterInnenlisten gelöscht.'), $count) . '§'; + } + } + if ($newuser['auth_user_md5.perms'] === 'root') { + $this->logInstUserDel($this->user_data['auth_user_md5.user_id']); + + $query = "DELETE FROM user_inst WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus MitarbeiterInnenlisten gelöscht.'), $count) . '§'; + } + } + + return true; + } + + private function logInstUserDel($user_id, $condition = null) + { + $query = "SELECT Institut_id FROM user_inst WHERE user_id = ?"; + if (isset($condition)) { + $query .= ' AND ' . $condition; + } + + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_id]); + while ($institute_id = $statement->fetchColumn()) { + StudipLog::log('INST_USER_DEL', $institute_id, $user_id); + } + } + + /** + * Mail a password generation link to the user + * + * @return bool Password change successful? + */ + public function setPassword() + { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu verändern.') . '§'; + return false; + } + + if (!$perm->have_perm('root')) { + if ($this->user_data['auth_user_md5.perms'] === "root") { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Root-Accounts zu verändern.') . '§'; + return false; + } + if ($perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { + if (!$this->adminOK()) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu verändern.') . '§'; + return false; + } + } + } + + // Can we reach the email? + if (!$this->checkMail($this->user_data['auth_user_md5.Email'])) { + return false; + } + + self::sendPasswordMail($this->user); + + return true; + } + + /** + * Send a mail to the user denoted by the passed user-object with a link + * to reset the password. For admin, root and non-standard-auth a notification + * is sent instead. + * + * @param User $user + * + * @return void + */ + public static function sendPasswordMail($user, $new = false) + { + setTempLanguage($user->user_id); + + // always generate a token, so root, admin and all other users profit from the abuse protection + if ($new) { + $expiration_in_hours = 24; + $spoken_expiration = _('24 Stunden'); + } else { + $expiration_in_hours = 7 * 24; + $spoken_expiration = _('eine Woche'); + } + $token = Token::create($expiration_in_hours * 60 * 60, $user->id, true); + + // new users alawys receive a link to generate a password + if ($new) { + $subject = sprintf( + _("[Stud.IP - %s] Es wurde ein Zugang für sie erstellt - Setzen sie ein Passwort"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Bestätigungsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %1\$s -\n\n" + ."Es wurde für sie ein Zugang zum System erstellt, Ihr Nutzername lautet:\n\n" + ."%2\$s\n\n" + ."Um den Zugang nutzen zu können, müssen sie ein Passwort setzen.\n" + ."Öffnen Sie dafür bitte folgenden Link\n\n" + ."%3\$s\n\n" + ."in Ihrem Browser.\n\n" + ."Der Link ist %4\$s (bis %5\$s) gültig.\n\n" + ."Wahrscheinlich unterstützt Ihr E-Mail-Programm ein einfaches Anklicken des Links.\n" + ."Ansonsten müssen Sie Ihren Browser öffnen und den Link komplett in die Zeile\n" + ."\"Location\" oder \"URL\" kopieren.\n\n" + ), + Config::get()->UNI_NAME_CLEAN, + $user->username, + $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/new_password/set/'. $token->token .'?cancel_login=1', + $spoken_expiration, + strftime('%x %X', $token->expiration) + ); + } else + + // only users with auth-type standard cann reset their password + if ($user->auth_plugin !== 'standard') { + + // inform user, that their password cannot be reset via mail + $subject = sprintf( + _("[Stud.IP - %s] Passwortänderung angefordert"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Informationsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %s -\n\n" + . "Sie haben einen Link angefordert\n" + . "um das Passwort zurückzusetzen.\n" + . "Dies ist aber für den mit dieser Mail \n" + . "verknüpften Account so nicht möglich.\n\n" + . "Wenden sie sich bitte stattdessen an\n%s" + ), + Config::get()->UNI_NAME_CLEAN, + $GLOBALS['UNI_CONTACT'] + ); + + } else { + + $subject = sprintf( + _("[Stud.IP - %s] Neues Passwort setzen"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Bestätigungsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %1\$s -\n\n" + ."Sie haben um die Zurücksetzung des Passwortes zu Ihrem Benutzernamen %5\$s gebeten.\n\n" + ."Diese E-Mail wurde Ihnen zugesandt um sicherzustellen,\n" + ."dass die angegebene E-Mail-Adresse tatsächlich Ihnen gehört.\n\n" + ."Wenn Sie um die Zurücksetzung Ihres Passwortes gebeten haben,\n" + ."dann öffnen Sie bitte folgenden Link\n\n" + ."%2\$s\n\n" + ."in Ihrem Browser. Auf der Seite können Sie ein neues Passwort setzen.\n\n" + ."Der Link ist %3\$s (bis %4\$s) gültig.\n\n" + ."Wahrscheinlich unterstützt Ihr E-Mail-Programm ein einfaches Anklicken des Links.\n" + ."Ansonsten müssen Sie Ihren Browser öffnen und den Link komplett in die Zeile\n" + ."\"Location\" oder \"URL\" kopieren.\n\n" + ."Falls Sie nicht diese Mail nicht angefordert haben\n" + ."oder überhaupt nicht wissen, wovon hier die Rede ist,\n" + ."dann hat jemand Ihre E-Mail-Adresse fälschlicherweise verwendet!\n" + ."Ignorieren Sie in diesem Fall diese E-Mail. Es werden dann keine\n" + ."Änderungen an Ihren Zugangsdaten vorgenommen.\n\n" + ), + Config::get()->UNI_NAME_CLEAN, + $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/new_password/set/'. $token->token .'?cancel_login=1', + $spoken_expiration, + strftime('%x %X', $token->expiration), + $user->username + ); + } + + StudipMail::sendMessage($user->email, $subject, $mailbody); + + restoreLanguage(); + } + + /** + * Delete an existing user from the database and tidy up + * + * @param $delete_documents bool delete all documents in course context belonging to the user + * @param $delete_content_from_course bool delete all course content belonging to the user + * @param $delete_personal_documents bool delete all personal documents belonging to the user + * @param $delete_personal_content bool delete all personal content belonging to the user + * @param $delete_names bool delete all names identifying the user + * @param $delete_memberships bool delete all memberships of the user + * @param bool $send_email_notification bool send an email that the account has been deleted + * @return bool Removal successful? + */ + public function deleteUser( + bool $delete_documents = true, + bool $delete_content_from_course = true, + bool $delete_personal_documents = true, + bool $delete_personal_content = true, + bool $delete_names = true, + bool $delete_memberships = true, + bool $send_email_notification = true, + bool $delete_courseware = true + ): bool { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu löschen.') . '§'; + return FALSE; + } + + if (!$perm->have_perm('root')) { + if ($this->user_data['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Root-Accounts zu löschen.') . '§'; + return false; + } + if ($this->user_data['auth_user_md5.perms'] === 'admin' && !$this->adminOK()) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu löschen.') . '§'; + return false; + } + } + + // active dozent? + $query = "SELECT COUNT(*) + FROM ( + SELECT 1 + FROM `seminar_user` AS `su1` + -- JOIN seminar_user to check for other teachers + INNER JOIN `seminar_user` AS `su2` + ON (`su1`.`seminar_id` = `su2`.`seminar_id` AND `su2`.`status` = 'dozent') + -- JOIN seminare to check the status for studygroup mode + INNER JOIN `seminare` + ON (`su1`.`seminar_id` = `seminare`.`seminar_id`) + WHERE `su1`.`user_id` = :user_id + AND `su1`.`status` = 'dozent' + AND `seminare`.`status` NOT IN ( + -- Select all status ids for studygroups + SELECT `id` + FROM `sem_classes` + WHERE `studygroup_mode` = 1 + ) + GROUP BY `su1`.`seminar_id` + HAVING COUNT(*) = 1 + ORDER BY NULL + ) AS `sub`"; + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':user_id', $this->user_data['auth_user_md5.user_id']); + $statement->execute(); + $active_count = $statement->fetchColumn() ?: 0; + + if ($active_count && $delete_memberships) { + $this->msg .= 'error§' . sprintf(_('%s ist Lehrkraft in %s aktiven Veranstaltungen und kann daher nicht gelöscht werden.'), $this->user_data['auth_user_md5.username'], $active_count) . '§'; + return false; + //founder of studygroup? + } elseif (Config::get()->STUDYGROUPS_ENABLE) { + $status = studygroup_sem_types(); + + if (empty($status)) { + $group_ids = []; + } else { + $query = "SELECT Seminar_id + FROM seminare AS s + LEFT JOIN seminar_user AS su USING (Seminar_id) + WHERE su.status = 'dozent' AND su.user_id = ? AND s.status IN (?)"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $this->user_data['auth_user_md5.user_id'], + $status, + ]); + $group_ids = $statement->fetchAll(PDO::FETCH_COLUMN); + } + + foreach ($group_ids as $group_id) { + $sem = Seminar::GetInstance($group_id); + if (StudygroupModel::countMembers($group_id) > 1) { + // check whether there are tutors or even autors that can be promoted + $tutors = $sem->getMembers('tutor'); + $autors = $sem->getMembers('autor'); + if (count($tutors) > 0) { + $new_founder = current($tutors); + StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); + continue; + } + // if not promote an autor + elseif (count($autors) > 0) { + $new_founder = current($autors); + StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); + continue; + } + // since no suitable successor was found, we are allowed to remove the studygroup + } else { + $sem->delete(); + } + unset($sem); + } + } + + // store user preferred language for sending mail + $user_language = getUserLanguagePath($this->user_data['auth_user_md5.user_id']); + + // Load privacy plugins to ensure all event handlers can react to the + // UserDataDidRemove event + PluginEngine::getPlugins(PrivacyPlugin::class); + + // delete user from instituts + $this->logInstUserDel($this->user_data['auth_user_md5.user_id']); + + if ($delete_memberships) { + $query = "DELETE FROM user_inst WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus MitarbeiterInnenlisten gelöscht.'), $count) . '§'; + } + + // delete user from Statusgruppen + if ($count = StatusgruppeUser::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']])) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus Funktionen / Gruppen gelöscht.'), $count) . '§'; + } + + // delete user from archiv + $query = "DELETE FROM archiv_user WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus den Zugriffsberechtigungen für das Archiv gelöscht.'), $count) . '§'; + } + + // delete 'Studiengaenge' + $query = "DELETE FROM user_studiengang WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%s Zuordnungen zu Studiengängen gelöscht.'), $count) . '§'; + } + + $this->re_sort_position_in_seminar_user(); + + // delete user from seminars (postings will be preserved) + $query = "DELETE FROM seminar_user WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus Veranstaltungen gelöscht.'), $count) . '§'; + } + + $query = "DELETE FROM `termin_related_persons` WHERE `user_id` = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%u Terminzuordnungen gelöscht.'), $count) . '§'; + } + + // delete visibility settings + Visibility::removeUserPrivacySettings($this->user_data['auth_user_md5.user_id']); + + // delete deputy entries if necessary + $query = "DELETE FROM deputies WHERE ? IN (user_id, range_id)"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + $deputyEntries = $statement->rowCount(); + if ($deputyEntries) { + $this->msg .= 'info§' . sprintf(_('%s Einträge in den Vertretungseinstellungen gelöscht.'), $deputyEntries) . '§'; + } + + // delete all remaining user data + $queries = [ + "DELETE FROM user_userdomains WHERE user_id = ?", + ]; + foreach ($queries as $query) { + DBManager::get()->execute($query, [$this->user_data['auth_user_md5.user_id']]); + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'memberships'); + } + + // delete documents of this user + if ($delete_documents) { + $db_filecount = FileRef::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + if ($db_filecount > 0) { + $this->msg .= 'info§' . sprintf(_('%s Dateien aus Veranstaltungen und Einrichtungen gelöscht.'), $db_filecount) . '§'; + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'course_documents'); + } + + // always delete personal courseware elements of this user + \Courseware\Unit::deleteBySQL('range_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\UserDataField::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\UserProgress::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Bookmark::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Task::deleteBySQL( + '`solver_id` = ? AND `solver_type`= "autor"', + [$this->user_data['auth_user_md5.user_id']] + ); + // delete courseware elements in courses of this user + if ($delete_courseware) { + \Courseware\Unit::deleteBySQL('creator_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\StructuralElement::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\StructuralElementFeedback::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\StructuralElementComment::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Container::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Block::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\BlockFeedback::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\BlockComment::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + } + + // delete all remaining user data in course context if option selected + if ($delete_content_from_course) { + $queries = [ + "DELETE FROM questionnaires WHERE user_id = ?", + "DELETE FROM questionnaire_answers WHERE user_id = ?", + "DELETE FROM questionnaire_assignments WHERE user_id = ?", + "DELETE FROM questionnaire_anonymous_answers WHERE user_id = ?", + "DELETE FROM etask_assignment_attempts WHERE user_id = ?", + "DELETE FROM etask_responses WHERE user_id = ?", + "DELETE FROM etask_tasks WHERE user_id = ?", + "DELETE FROM etask_tests WHERE user_id = ?", + ]; + foreach ($queries as $query) { + DBManager::get()->execute($query, [$this->user_data['auth_user_md5.user_id']]); + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'course_contents'); + } + + if ($delete_personal_documents) { + $user_folder = Folder::findTopFolder($this->user->id); + if ($user_folder) { + $this->msg .= 'info§' . _('Persönlicher Dateibereich gelöscht.') . '§'; + $user_folder->delete(); + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'personal_documents'); + } + + if ($delete_personal_content) { + $this->msg .= $this->deletePersonalData($this->user_data['auth_user_md5.user_id']); + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'personal_contents'); + } + + if ($delete_names) { + $query = "UPDATE auth_user_md5 + SET username = ?, Vorname = '', Nachname = '', Email = '' + WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + md5($this->user_data['auth_user_md5.username'].uniqid('delete_user')), + $this->user_data['auth_user_md5.user_id'] + ]); + if ($statement->rowCount() > 0) { + $this->msg .= 'info§' . _('Benutzername anonymisiert.') . '§'; + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'names'); + } + + if ($delete_personal_documents && $delete_personal_content && $delete_names && $delete_memberships) { + // Delete the user from resource property entries of type "user": + ResourceProperty::deleteBySQL( + "`property_id` IN ( + SELECT `property_id` + FROM `resource_property_definitions` + WHERE `type` = 'user' + ) + AND `state` = :user_id", + ['user_id' => $this->user_data['auth_user_md5.user_id']] + ); + + // delete Stud.IP account + $query = "DELETE FROM user_info WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + + $query = "DELETE FROM auth_user_md5 WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if (!$statement->rowCount()) { + $this->msg .= 'error§' . _('Fehler:') . ' ' . $query . '§'; + return false; + } else { + $this->msg .= 'msg§' . sprintf(_('Benutzer "%s" gelöscht.'), $this->user_data['auth_user_md5.username']) . '§'; + } + StudipLog::log('USER_DEL', $this->user_data['auth_user_md5.user_id'], null, sprintf('%s %s (%s)', $this->user_data['auth_user_md5.Vorname'], $this->user_data['auth_user_md5.Nachname'], $this->user_data['auth_user_md5.username'])); //log with Vorname Nachname (username) as info string + + // Can we reach the email? + if ( + $send_email_notification + && $this->checkMail($this->user_data['auth_user_md5.Email']) + ) { + // include language-specific subject and mailbody + $Zeit = strftime('%x, %X'); + + // TODO: This should be refactored so that the included file returns an array + include "locale/$user_language/LC_MAILS/delete_mail.inc.php"; // Defines $subject and $mailbody + + // send mail + StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject ?? '', $mailbody ?? ''); + } + + // Remove plugin associations/activations + PluginManager::getInstance()->deactivateAllPluginsForRange( + 'user', + $this->user_data['auth_user_md5.user_id'] + ); + + // Trigger delete on sorm object which will fire notifications + // + // TODO: Remove everything from this method that would also be + // deleted in User::delete() (TODO!!!) + $this->user->delete(); + + unset($this->user_data); + } + + return true; + } + + /** + * Delete personal userdata + * + * @param string $user_id the user which should be retrieved + * @return string Removal messages + */ + private function deletePersonalData($user_id) + { + $msg = ''; + + // delete the datafields + $localEntries = DataFieldEntry::removeAll($user_id); + + // delete user from waiting lists + $query = "SELECT seminar_id FROM admission_seminar_user WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_id]); + $seminar_ids = $statement->fetchAll(PDO::FETCH_COLUMN); + + $query = "DELETE FROM admission_seminar_user WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_id]); + if ($count = $statement->rowCount()) { + $msg .= 'info§' . sprintf(_('%s Einträge aus Wartelisten gelöscht.'), $count) . '§'; + array_map('AdmissionApplication::addMembers', $seminar_ids); + } + + // delete all personal news from this user + if ($count = StudipNews::DeleteNewsByAuthor($user_id)) { + $msg .= 'info§' . sprintf(_('%s Einträge aus den Ankündigungen gelöscht.'), $count) . '§'; + } + if ($count = StudipNews::DeleteNewsRanges($user_id)) { + $msg .= 'info§' . sprintf(_('%s Verweise auf Ankündigungen gelöscht.'), $count) . '§'; + } + + //delete entry in news_rss_range + StudipNews::UnsetRssId($user_id); + + // delete all private appointments of this user + if (Config::get()->CALENDAR_ENABLE) { + // delete private appointments (omit group appointments) + $count = CalendarDate::deleteBySQL( + '`id` IN ( + SELECT `id` + FROM ( + SELECT `id`, COUNT(*) + FROM `calendar_dates` + JOIN `calendar_date_assignments` + ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` + WHERE `calendar_dates`.`author_id` = :user_id + GROUP BY `id` + HAVING COUNT(*) = 1 + ORDER BY NULL + ) AS `cal_date_delete` + )', + [':user_id' => $user_id] + ); + // delete assignments to group appointments + $count += CalendarDateAssignment::deleteBySQL('`range_id` = ?', [$user_id]); + if ($count) { + $msg .= 'info§' . sprintf(_('%s Einträge aus den Terminen gelöscht.'), $count) . '§'; + } + } + + // delete all messages send or received by this user + $messaging = new messaging(); + $messaging->delete_all_messages($user_id); + + // delete user from all foreign adressbooks and empty own adressbook + $count = Contact::deleteBySQL('user_id = ?', [$user_id]); + if ($count > 0) { + $msg .= 'info§' . sprintf(_('%s Einträge aus Adressbüchern gelöscht.'), $count) . '§'; + } + $count = Contact::deleteBySQL('owner_id = ?', [$user_id]); + if ($count) { + $msg .= 'info§' . sprintf(_('Adressbuch mit %d Einträgen gelöscht.'), $count) . '§'; + } + + // delete users groups + Statusgruppen::deleteBySQL('range_id = ?', [$user_id]); + + // remove user from any groups + StatusgruppeUser::deleteBySQL('user_id = ?', [$user_id]); + + // delete user config values + ConfigValue::deleteBySQL('range_id = ?', [$user_id]); + + // delete all remaining user data + $queries = [ + "DELETE FROM kategorien WHERE range_id = ?", + "DELETE FROM user_visibility WHERE user_id = ?", + "DELETE FROM user_online WHERE user_id = ?", + "DELETE FROM auto_insert_user WHERE user_id = ?", + "DELETE FROM roles_user WHERE userid = ?", + "DELETE FROM schedule WHERE user_id = ?", + "DELETE FROM schedule_seminare WHERE user_id = ?", + "DELETE FROM termin_related_persons WHERE user_id = ?", + "DELETE FROM priorities WHERE user_id = ?", + "DELETE FROM api_oauth_user_mapping WHERE user_id = ?", + "DELETE FROM api_user_permissions WHERE user_id = ?", + "DELETE FROM help_tour_user WHERE user_id = ?", + "DELETE FROM personal_notifications_user WHERE user_id = ?", + "DELETE FROM forum_abo_users WHERE user_id = ?", + "DELETE FROM forum_favorites WHERE user_id = ?", + + "DELETE FROM comments WHERE user_id = ?", + "DELETE questionnaires FROM questionnaires LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", + "DELETE questionnaire_answers FROM questionnaire_answers LEFT JOIN questionnaire_questions USING (`question_id`) LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", + "DELETE questionnaire_anonymous_answers FROM questionnaire_anonymous_answers LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", + "DELETE FROM questionnaire_assignments WHERE user_id = ?", + "DELETE etask_assignment_attempts FROM etask_assignment_attempts LEFT JOIN etask_assignments ea ON (`assignment_id` = ea.id) WHERE ea.range_type = 'user' AND user_id = ?", + "DELETE etask_responses FROM etask_responses LEFT JOIN etask_assignments ea ON (`assignment_id` = ea.id) WHERE ea.range_type = 'user' AND user_id = ?", + "DELETE etask_tasks FROM etask_tasks LEFT JOIN etask_test_tasks tt ON (etask_tasks.id = tt.task_id) LEFT JOIN etask_assignments ea ON (tt.`test_id` = ea.test_id) WHERE ea.range_type = 'user' AND user_id = ?", + "DELETE etask_tests FROM etask_tests LEFT JOIN etask_assignments ea ON (`test_id` = ea.test_id) WHERE ea.range_type = 'user' AND user_id = ?", + + "UPDATE forum_entries SET author = '' WHERE user_id = ?", + "UPDATE auth_user_md5 SET visible = 'never' WHERE user_id = ?", + + "REPLACE INTO `user_info` (`user_id`, `hobby`, `lebenslauf`, `publi`, `schwerp`, `Home`, `privatnr`, `privatcell`, `privadr`, `score`, `geschlecht`, `mkdate`, `chdate`, `title_front`, `title_rear`, `preferred_language`, `smsforward_copy`, `smsforward_rec`, `email_forward`, `motto`, `lock_rule`) VALUES(?, '', '', '', '', '', '', '', '', 0, 0, 0, 0, '', '', NULL, 1, '', 0, '', '');" + ]; + foreach ($queries as $query) { + DBManager::get()->execute($query, [$user_id]); + } + + // Clean up orphaned items + $queries = [ + "DELETE FROM personal_notifications WHERE personal_notification_id NOT IN ( + SELECT personal_notification_id FROM personal_notifications_user + )", + ]; + foreach ($queries as $query) { + DBManager::get()->exec($query); + } + + object_kill_visits($user_id); + object_kill_views($user_id); + + // delete picture + $avatar = Avatar::getAvatar($user_id); + if ($avatar->is_customized()) { + $avatar->reset(); + $msg .= 'info§' . _('Bild gelöscht.') . '§'; + } + + //delete connected users + if (Config::get()->ELEARNING_INTERFACE_ENABLE) { + if (ELearningUtils::initElearningInterfaces()) { + foreach ($GLOBALS['connected_cms'] as $cms){ + if ($cms->auth_necessary && $cms->user instanceOf ConnectedUser) { + $user_auto_create = $cms->USER_AUTO_CREATE; + $cms->USER_AUTO_CREATE = false; + $userclass = mb_strtolower(get_class($cms->user)); + $connected_user = new $userclass($cms->cms_type, $user_id); + if ($connected_user->deleteUser() && $connected_user->is_connected) { + $msg .= 'info§' . sprintf(_('Der verknüpfte Nutzer %s wurde im System %s gelöscht.'), $connected_user->login, $connected_user->cms_type) . '§'; + } + $cms->USER_AUTO_CREATE = $user_auto_create; + } + } + } + } + + return $msg; + } + + private function adminOK() + { + static $ok = null; + + if ($ok === null) { + $query = "SELECT COUNT(a.Institut_id) = COUNT(c.inst_perms) + FROM user_inst AS a + LEFT JOIN Institute b ON (a.Institut_id = b.Institut_id AND b.Institut_id != b.fakultaets_id) + LEFT JOIN user_inst AS c ON (b.fakultaets_id = c.Institut_id AND c.user_id = ? + AND c.inst_perms = 'admin') + WHERE a.user_id = ? AND a.inst_perms = 'admin'"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $GLOBALS['auth']->auth['uid'], + $this->user_data['auth_user_md5.user_id'], + ]); + $ok = $statement->fetchColumn(); + } + + return $ok; + } + + private function re_sort_position_in_seminar_user() + { + $query = "SELECT Seminar_id, position, status + FROM seminar_user + WHERE user_id = ? AND status IN ('tutor', 'dozent')"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + if ($row['status'] === 'tutor') { + CourseMember::resortMembership($row['Seminar_id'], (int)$row['position']); + } else if ($row['status'] === 'dozent') { + CourseMember::resortMembership($row['Seminar_id'], (int)$row['position'], 'dozent'); + } + } + } + + /** + * Change an existing user password + * + * @param string $password + * @return bool change successful? + */ + public function changePassword($password) + { + $this->user_data['auth_user_md5.password'] = self::getPwdHasher()->HashPassword($password); + $this->storeToDatabase(); + + $this->msg .= 'msg§' . _('Das Passwort wurde neu gesetzt.') . '§'; + + // include language-specific subject and mailbody + setTempLanguage($this->user_data['auth_user_md5.user_id']); + + $subject = sprintf( + _("[Stud.IP - %s] Passwortänderung"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Informationsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %s -\n\n" + ."Ihr Passwort wurde soeben von Ihnen oder einem/einer Administrator/in geändert.\n" + ), + Config::get()->UNI_NAME_CLEAN + ); + + // send mail + StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject, $mailbody); + + restoreLanguage(); + + StudipLog::log('USER_NEWPWD', $this->user_data['auth_user_md5.user_id']); + + return true; + } +} diff --git a/lib/classes/admission/AdmissionAlgorithm.class.php b/lib/classes/admission/AdmissionAlgorithm.class.php deleted file mode 100644 index 7e9df92..0000000 --- a/lib/classes/admission/AdmissionAlgorithm.class.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -abstract class AdmissionAlgorithm -{ - - /** - * Runs an algorithm that distributes all seats in the given CourseSet. - * - * @param CourseSet course set object - * @return boolean Did the algorithm run successfully? - */ - public function run($courseSet) - { - return true; - } - -} /* end of class AdmissionAlgorithm */ - -?> \ No newline at end of file diff --git a/lib/classes/admission/AdmissionAlgorithm.php b/lib/classes/admission/AdmissionAlgorithm.php new file mode 100644 index 0000000..7e9df92 --- /dev/null +++ b/lib/classes/admission/AdmissionAlgorithm.php @@ -0,0 +1,35 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +abstract class AdmissionAlgorithm +{ + + /** + * Runs an algorithm that distributes all seats in the given CourseSet. + * + * @param CourseSet course set object + * @return boolean Did the algorithm run successfully? + */ + public function run($courseSet) + { + return true; + } + +} /* end of class AdmissionAlgorithm */ + +?> \ No newline at end of file diff --git a/lib/classes/admission/AdmissionPriority.class.php b/lib/classes/admission/AdmissionPriority.class.php deleted file mode 100644 index 47e1564..0000000 --- a/lib/classes/admission/AdmissionPriority.class.php +++ /dev/null @@ -1,253 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class AdmissionPriority -{ - /** - * Get all priorities for the given course set. - * The priorities are stored in a 2-dimensional array in the form - * priority[user_id][course_id] = x. - * - * @param String $courseSetId - * @return A 2-dimensional array containing all priorities. - */ - public static function getPriorities($courseSetId) - { - $query = "SELECT p.`user_id`, p.`seminar_id`, p.`priority` - FROM `priorities` AS p - JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) - WHERE p.`set_id` = ?"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute([$courseSetId]); - - $priorities = []; - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $priorities[$current['user_id']][$current['seminar_id']] = $current['priority']; - } - return $priorities; - } - - /** - * Get all priorities for the given course in the given course set. - * The priorities are stored in an array in the form - * priority[user_id] = x. - * - * @param String $courseSetId - * @param String $courseId - * @return An array containing all priorities. - */ - public static function getPrioritiesByCourse($courseSetId, $courseId) - { - $query = "SELECT p.`user_id`, p.`priority` - FROM `priorities` AS p - JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) - WHERE p.`set_id` = ? AND p.`seminar_id` = ?"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute([$courseSetId, $courseId]); - - $priorities = []; - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $priorities[$current['user_id']] = $current['priority']; - } - return $priorities; - } - - /** - * Get all priorities the given user has set in the given course set. - * The priorities are stored in an array in the form - * priority[course_id] = x. - * - * @param String $courseSetId - * @param String $userId - * @return An array containing all priorities. - */ - public static function getPrioritiesByUser($courseSetId, $userId) - { - $query = "SELECT p.`seminar_id`, p.`priority` - FROM `priorities` AS p - JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) - WHERE p.`set_id` = ? AND p.`user_id` = ? - ORDER BY p.`priority`"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute([$courseSetId, $userId]); - - $priorities = []; - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $priorities[$current['seminar_id']] = $current['priority']; - } - return $priorities; - } - - /** - * The given user sets a course in the given course set to priority x. - * - * @param String $courseSetId - * @param String $userId - * @param String $courseId - * @param int $priority - * @return int Number of affected rows, if any. - */ - public static function setPriority($courseSetId, $userId, $courseId, $priority) - { - $query = "INSERT INTO `priorities` ( - `user_id`, `set_id`, `seminar_id`, `priority`, `mkdate`, `chdate` - ) - SELECT ?, ?, `seminare`.`seminar_id`, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() - FROM `seminare` INNER JOIN `seminar_courseset` USING(`seminar_id`) - WHERE `seminare`.`seminar_id` = ? AND `set_id` = ? - ON DUPLICATE KEY - UPDATE `priority` = VALUES(`priority`), - `chdate` = VALUES(`chdate`)"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute([(string)$userId, (string)$courseSetId, (int)$priority, (string)$courseId, (string)$courseSetId]); - - $ok = $stmt->rowCount(); - if ($ok) { - StudipLog::log( - 'SEM_USER_ADD', $courseId, $userId, - 'Anmeldung zur Platzvergabe', - sprintf('Prio: %s Anmeldeset: %s', $priority, $courseSetId) - ); - NotificationCenter::postNotification( - 'UserAdmissionPriorityDidCreate', $courseId, $userId - ); - } - return $ok; - } - - /** - * unset priority for given user,set and course - * reorder remaining priorities - * - * @param String $courseSetId - * @param String $userId - * @param String $courseId - * @return int Number of affected rows, if any. - */ - public static function unsetPriority($courseSetId, $userId, $courseId) - { - $query = "DELETE FROM `priorities` - WHERE `user_id` = ? AND `seminar_id` = ? AND `set_id` = ? - LIMIT 1"; - $deleted = DBManager::get()->execute($query, [$userId, $courseId, $courseSetId]); - if (!$deleted) { - return 0; - } - - $priovar = md5($courseSetId . $userId); - DBManager::get()->exec("SET @{$priovar} := 0"); - - $query = "UPDATE `priorities` - SET `priority` = (@{$priovar} := @{$priovar} + 1) - WHERE `user_id` = ? AND `set_id` = ? - ORDER BY `priority`"; - DBManager::get()->execute($query, [$userId, $courseSetId]); - - StudipLog::log( - 'SEM_USER_DEL', $courseId, $userId, - 'Anmeldung zur Platzvergabe zurückgezogen', - sprintf('Anmeldeset: %s', $courseSetId) - ); - NotificationCenter::postNotification( - 'UserAdmissionPriorityDidDelete', $courseId, $userId - ); - - return $deleted; - } - - /** - * delete all priorities for one set - * - * @param String $courseSetId - * @return int Number of affected rows, if any. - */ - public static function unsetAllPriorities($courseSetId) - { - $query = "DELETE FROM `priorities` WHERE `set_id` = ?"; - return DBManager::get()->execute($query, [$courseSetId]); - } - - /** - * delete all priorities for one set and one user - * - * @param String $courseSetId - * @param String $userId - * @return int Number of affected rows, if any. - */ - public static function unsetAllPrioritiesForUser($courseSetId, $userId) - { - $query = "DELETE FROM `priorities` - WHERE `user_id` = ? AND `set_id` = ?"; - return DBManager::get()->execute($query, [$userId, $courseSetId]); - } - - /** - * returns statistics of priority selection for a set - * - * @param String $courseSetId - * @return array stats grouped by course id - */ - public static function getPrioritiesStats($courseSetId) - { - $query = "SELECT p.`seminar_id`, - COUNT(*) AS c, - AVG(p.`priority`) AS a, - COUNT(IF(p.`priority` = 1, 1, NULL)) AS h - FROM `priorities` AS p - JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) - WHERE p.`set_id` = ? - GROUP BY p.`seminar_id`"; - return DBManager::get()->fetchGrouped($query, [$courseSetId]); - } - - /** - * returns number of users with priorities for a set - * - * @param String $courseSetId - * @return integer - */ - public static function getPrioritiesCount($courseSetId) - { - $query = "SELECT COUNT(DISTINCT `user_id`) - FROM `priorities` - WHERE `set_id` = ?"; - return (int) DBManager::get()->fetchColumn($query, [$courseSetId]); - } - - /** - * return max chosen priority in set - * - * @param String $courseSetId - * @return integer - */ - public static function getPrioritiesMax($courseSetId) - { - $query = "SELECT MAX(`priority`) FROM `priorities` WHERE `set_id` = ?"; - return (int) DBManager::get()->fetchColumn($query, [$courseSetId]); - } - - /** - * delete all priorities for one course - * - * @param String $course_id - * @return int Number of affected rows, if any. - */ - public static function unsetAllPrioritiesForCourse($course_id) - { - $query = "DELETE FROM `priorities` WHERE `seminar_id` = ?"; - return DBManager::get()->execute($query, [$course_id]); - } -} diff --git a/lib/classes/admission/AdmissionPriority.php b/lib/classes/admission/AdmissionPriority.php new file mode 100644 index 0000000..47e1564 --- /dev/null +++ b/lib/classes/admission/AdmissionPriority.php @@ -0,0 +1,253 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class AdmissionPriority +{ + /** + * Get all priorities for the given course set. + * The priorities are stored in a 2-dimensional array in the form + * priority[user_id][course_id] = x. + * + * @param String $courseSetId + * @return A 2-dimensional array containing all priorities. + */ + public static function getPriorities($courseSetId) + { + $query = "SELECT p.`user_id`, p.`seminar_id`, p.`priority` + FROM `priorities` AS p + JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) + WHERE p.`set_id` = ?"; + $stmt = DBManager::get()->prepare($query); + $stmt->execute([$courseSetId]); + + $priorities = []; + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $priorities[$current['user_id']][$current['seminar_id']] = $current['priority']; + } + return $priorities; + } + + /** + * Get all priorities for the given course in the given course set. + * The priorities are stored in an array in the form + * priority[user_id] = x. + * + * @param String $courseSetId + * @param String $courseId + * @return An array containing all priorities. + */ + public static function getPrioritiesByCourse($courseSetId, $courseId) + { + $query = "SELECT p.`user_id`, p.`priority` + FROM `priorities` AS p + JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) + WHERE p.`set_id` = ? AND p.`seminar_id` = ?"; + $stmt = DBManager::get()->prepare($query); + $stmt->execute([$courseSetId, $courseId]); + + $priorities = []; + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $priorities[$current['user_id']] = $current['priority']; + } + return $priorities; + } + + /** + * Get all priorities the given user has set in the given course set. + * The priorities are stored in an array in the form + * priority[course_id] = x. + * + * @param String $courseSetId + * @param String $userId + * @return An array containing all priorities. + */ + public static function getPrioritiesByUser($courseSetId, $userId) + { + $query = "SELECT p.`seminar_id`, p.`priority` + FROM `priorities` AS p + JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) + WHERE p.`set_id` = ? AND p.`user_id` = ? + ORDER BY p.`priority`"; + $stmt = DBManager::get()->prepare($query); + $stmt->execute([$courseSetId, $userId]); + + $priorities = []; + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $priorities[$current['seminar_id']] = $current['priority']; + } + return $priorities; + } + + /** + * The given user sets a course in the given course set to priority x. + * + * @param String $courseSetId + * @param String $userId + * @param String $courseId + * @param int $priority + * @return int Number of affected rows, if any. + */ + public static function setPriority($courseSetId, $userId, $courseId, $priority) + { + $query = "INSERT INTO `priorities` ( + `user_id`, `set_id`, `seminar_id`, `priority`, `mkdate`, `chdate` + ) + SELECT ?, ?, `seminare`.`seminar_id`, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + FROM `seminare` INNER JOIN `seminar_courseset` USING(`seminar_id`) + WHERE `seminare`.`seminar_id` = ? AND `set_id` = ? + ON DUPLICATE KEY + UPDATE `priority` = VALUES(`priority`), + `chdate` = VALUES(`chdate`)"; + $stmt = DBManager::get()->prepare($query); + $stmt->execute([(string)$userId, (string)$courseSetId, (int)$priority, (string)$courseId, (string)$courseSetId]); + + $ok = $stmt->rowCount(); + if ($ok) { + StudipLog::log( + 'SEM_USER_ADD', $courseId, $userId, + 'Anmeldung zur Platzvergabe', + sprintf('Prio: %s Anmeldeset: %s', $priority, $courseSetId) + ); + NotificationCenter::postNotification( + 'UserAdmissionPriorityDidCreate', $courseId, $userId + ); + } + return $ok; + } + + /** + * unset priority for given user,set and course + * reorder remaining priorities + * + * @param String $courseSetId + * @param String $userId + * @param String $courseId + * @return int Number of affected rows, if any. + */ + public static function unsetPriority($courseSetId, $userId, $courseId) + { + $query = "DELETE FROM `priorities` + WHERE `user_id` = ? AND `seminar_id` = ? AND `set_id` = ? + LIMIT 1"; + $deleted = DBManager::get()->execute($query, [$userId, $courseId, $courseSetId]); + if (!$deleted) { + return 0; + } + + $priovar = md5($courseSetId . $userId); + DBManager::get()->exec("SET @{$priovar} := 0"); + + $query = "UPDATE `priorities` + SET `priority` = (@{$priovar} := @{$priovar} + 1) + WHERE `user_id` = ? AND `set_id` = ? + ORDER BY `priority`"; + DBManager::get()->execute($query, [$userId, $courseSetId]); + + StudipLog::log( + 'SEM_USER_DEL', $courseId, $userId, + 'Anmeldung zur Platzvergabe zurückgezogen', + sprintf('Anmeldeset: %s', $courseSetId) + ); + NotificationCenter::postNotification( + 'UserAdmissionPriorityDidDelete', $courseId, $userId + ); + + return $deleted; + } + + /** + * delete all priorities for one set + * + * @param String $courseSetId + * @return int Number of affected rows, if any. + */ + public static function unsetAllPriorities($courseSetId) + { + $query = "DELETE FROM `priorities` WHERE `set_id` = ?"; + return DBManager::get()->execute($query, [$courseSetId]); + } + + /** + * delete all priorities for one set and one user + * + * @param String $courseSetId + * @param String $userId + * @return int Number of affected rows, if any. + */ + public static function unsetAllPrioritiesForUser($courseSetId, $userId) + { + $query = "DELETE FROM `priorities` + WHERE `user_id` = ? AND `set_id` = ?"; + return DBManager::get()->execute($query, [$userId, $courseSetId]); + } + + /** + * returns statistics of priority selection for a set + * + * @param String $courseSetId + * @return array stats grouped by course id + */ + public static function getPrioritiesStats($courseSetId) + { + $query = "SELECT p.`seminar_id`, + COUNT(*) AS c, + AVG(p.`priority`) AS a, + COUNT(IF(p.`priority` = 1, 1, NULL)) AS h + FROM `priorities` AS p + JOIN `seminare` AS s ON (p.`seminar_id` = s.`Seminar_id`) + WHERE p.`set_id` = ? + GROUP BY p.`seminar_id`"; + return DBManager::get()->fetchGrouped($query, [$courseSetId]); + } + + /** + * returns number of users with priorities for a set + * + * @param String $courseSetId + * @return integer + */ + public static function getPrioritiesCount($courseSetId) + { + $query = "SELECT COUNT(DISTINCT `user_id`) + FROM `priorities` + WHERE `set_id` = ?"; + return (int) DBManager::get()->fetchColumn($query, [$courseSetId]); + } + + /** + * return max chosen priority in set + * + * @param String $courseSetId + * @return integer + */ + public static function getPrioritiesMax($courseSetId) + { + $query = "SELECT MAX(`priority`) FROM `priorities` WHERE `set_id` = ?"; + return (int) DBManager::get()->fetchColumn($query, [$courseSetId]); + } + + /** + * delete all priorities for one course + * + * @param String $course_id + * @return int Number of affected rows, if any. + */ + public static function unsetAllPrioritiesForCourse($course_id) + { + $query = "DELETE FROM `priorities` WHERE `seminar_id` = ?"; + return DBManager::get()->execute($query, [$course_id]); + } +} diff --git a/lib/classes/admission/AdmissionRule.class.php b/lib/classes/admission/AdmissionRule.class.php deleted file mode 100644 index fe5e5bb..0000000 --- a/lib/classes/admission/AdmissionRule.class.php +++ /dev/null @@ -1,456 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -abstract class AdmissionRule -{ - // --- ATTRIBUTES --- - - /** - * When does the validity end? - */ - public $endTime = 0; - - /** - * A unique identifier for this rule. - */ - public $id = ''; - - /** - * A customizable message that is shown to users that are rejected for admission - * because of the current rule. - */ - public $message = ''; - - /** - * default message that is shown to users that are rejected for admission - * because of the current rule. - */ - public $default_message = ''; - - /** - * When does the validity start? - */ - public $startTime = 0; - - /** - * ID of the CourseSet this admission rule belongs to (is stored here for - * performance reasons). - */ - public $courseSetId = ''; - - /** - * courseset siblings of this rule - */ - public $siblings = []; - - /** - * Are siblings set manually? - */ - public $siblings_override = false; - - // --- OPERATIONS --- - - public function __construct($ruleId = '', $courseSetId = '') - { - $this->id = $ruleId; - $this->courseSetId = $courseSetId; - } - - /** - * Hook that can be called after the seat distribution on the courseset - * has completed. - * - * @param CourseSet $courseset Current courseset. - */ - public function afterSeatDistribution($courseset) - { - return true; - } - - /** - * Checks if we are in the rule validity time frame. - * - * @return bool True if the rule is valid because the time frame applies, - * otherwise false. - */ - public function checkTimeFrame() - { - $valid = true; - // Start time given, but still in the future. - if ($this->startTime && $this->startTime > time()) { - $valid = false; - } - // End time given, but already past. - if ($this->endTime && $this->endTime < time()) { - $valid = false; - } - return $valid; - } - - /** - * Deletes the admission rule and all associated data. - */ - public function delete() - { - // Delete rule assignment to coursesets. - $stmt = DBManager::get()->prepare("DELETE FROM `courseset_rule` - WHERE `rule_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Generate a new unique ID. - * - * @param String $tableName - */ - public function generateId($tableName) - { - do { - $newid = md5(uniqid(get_class($this).microtime(), true)); - $db = DBManager::get()->query("SELECT `rule_id` - FROM `".$tableName."` WHERE `rule_id`=" . DBManager::get()->quote($newid)); - } while ($db->fetch()); - return $newid; - } - - /** - * Gets all users that are matched by thís rule. - * - * @return Array An array containing IDs of users who are matched by - * this rule. - */ - public function getAffectedUsers() - { - return []; - } - - /** - * Reads all available AdmissionRule subclasses and loads their definitions. - * - * @param bool $activeOnly Show only active rules. - * @return Array - */ - public static function getAvailableAdmissionRules($activeOnly = true) - { - $rules = []; - $where = ($activeOnly ? " WHERE `active`=1" : ""); - $data = DBManager::get()->query("SELECT * FROM `admissionrules`".$where. - " ORDER BY `id` ASC"); - while ($current = $data->fetch(PDO::FETCH_ASSOC)) { - $className = $current['ruletype']; - if (is_dir($GLOBALS['STUDIP_BASE_PATH'] . DIRECTORY_SEPARATOR . $current['path'])) { - StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'] . DIRECTORY_SEPARATOR . $current['path']); - try { - $rule = new $className(); - $rules[$className] = [ - 'id' => $current['id'], - 'name' => $className::getName(), - 'description' => $className::getDescription(), - 'active' => $current['active'] - ]; - } catch (Exception $e) { - } - } - } - return $rules; - } - - /** - * Get end of validity. - * - * @return Integer - */ - public function getEndTime() - { - return $this->endTime; - } - - /** - * Subclasses of AdmissionRule can require additional data to be entered on - * admission (like PasswordAdmission which needs a password for course - * access). Their corresponding method getInput only returns a HTML form - * fragment as the output can be concatenated with output from other - * rules. - * This static method provides the frame for rendering a full HTML form - * around the fragments from subclasses. - * - * @return Array Start and end templates which wrap input form fragments - * from subclasses. - */ - public static final function getInputFrame() - { - return [ - $GLOBALS['template_factory']->open('admission/rules/input_start')->render(), - $GLOBALS['template_factory']->open('admission/rules/input_end')->render() - ]; - } - - /** - * Gets some text that describes what this AdmissionRule (or respective - * subclass) does. - */ - public static function getDescription() - { - return _("Legt eine Regel fest, die erfüllt sein muss, um sich ". - "erfolgreich zu einer Menge von Veranstaltungen anmelden zu ". - "können."); - } - - public function getInput() - { - return ''; - } - /** - * Gets the rule ID. - * - * @return String This rule's ID. - */ - public function getId() - { - return $this->id; - } - - /** - * Gets the message that is shown to users rejected by this rule. - * - * @return String The message. - */ - public function getMessage() - { - return $this->message ?: $this->default_message; - } - - /** - * Return this rule's name. - */ - public static function getName() - { - return _("Anmelderegel"); - } - - /** - * Gets start of validity. - * - * @return Integer - */ - public function getStartTime() - { - return $this->startTime; - } - - /** - * Gets the template that provides a configuration GUI for this rule. - * - * @return String - */ - public function getTemplate() - { - return ''; - } - - /** - * Internal helper function for loading rule definition from database. - */ - public function load() - { - } - - /** - * Hook that can be called when the seat distribution on the courseset - * starts. - * - * @param CourseSet $courseset The courseset this rule belongs to. - */ - public function beforeSeatDistribution($courseset) - { - return true; - } - - /** - * Does the current rule allow the given user to register as participant - * in the given course? - * - * @param String $userId - * @param String $courseId - * @return Array - */ - public function ruleApplies($userId, $courseId) - { - return []; - } - - /** - * Uses the given data to fill the object values. This can be used - * as a generic function for storing data if the concrete rule type - * isn't known in advance. - * - * @param Array $data - * @return AdmissionRule This object. - */ - public function setAllData($data) - { - if (!empty($data['start_date']) && empty($data['start_time'])) { - $data['start_time'] = strtotime($data['start_date']); - } - if (!empty($data['end_date']) && empty($data['end_time'])) { - $data['end_time'] = strtotime($data['end_date'] . ' 23:59:59'); - } - $this->message = $data['message'] ?? ''; - $this->startTime = $data['start_time'] ?? null; - $this->endTime = $data['end_time'] ?? null; - return $this; - } - - /** - * Sets a new end time for condition validity. - * - * @param Integer $newEndTime - * @return AdmissionRule - */ - public function setEndTime($newEndTime) - { - $this->endTime = $newEndTime; - return $this; - } - - /** - * Sets a new message to show to users. - * - * @param String $newMessage A new message text. - * @return AdmissionRule This object - */ - public function setMessage($newMessage) - { - $this->message = $newMessage; - return $this; - } - - /** - * Sets a new start time for condition validity. - * - * @param Integer $newStartTime - * @return AdmissionRule - */ - public function setStartTime($newStartTime) - { - $this->startTime = $newStartTime; - return $this; - } - - /** - * Helper function for storing rule definition to database. - */ - public function store() - { - } - - /** - * A textual description of the current rule. - * - * @return String - */ - public function toString() - { - return ''; - } - - /** - * Validates if the given request data is sufficient to configure this rule - * (e.g. if required values are present). - * - * @param Array $data Request data - * @return Array Error messages. - */ - public function validate($data) - { - $errors = []; - if (!empty($data['start_date']) && !empty($data['end_date']) && strtotime($data['end_date']) < strtotime($data['start_date'])) { - $errors[] = _('Das Enddatum darf nicht vor dem Startdatum liegen.'); - } - return $errors; - } - - /** - * Standard string representation of this object. - * - * @return String - */ - public function __toString() - { - return $this->toString(); - } - - /** - * load sibling rules - * - */ - public function loadSiblings() - { - if ($this->siblings_override) { - return false; - } - $this->siblings = []; - if ($this->courseSetId != '') { - $cs = new CourseSet($this->courseSetId); - foreach ($cs->getAdmissionRules() as $rule_id => $rule) { - if ($rule->getId() != $this->id) { - $this->siblings[$rule_id] = $rule; - } - } - } - } - - /** - * get sibling rules - * - */ - public function getSiblings() - { - $this->loadSiblings(); - return $this->siblings; - } - - /** - * set sibling rules - * - */ - public function setSiblings($siblings = []) - { - $this->siblings_override = true; - $this->siblings = $siblings; - } - - /** - * checks if given admission rule is allowed to be combined with this rule - * - * @param AdmissionRule|string $admission_rule - * @return boolean - */ - public function isCombinationAllowed($admission_rule) - { - if (is_object($admission_rule)) { - $admission_rule = get_class($admission_rule); - } - return AdmissionRuleCompatibility::exists([get_class($this), $admission_rule]); - } - - public function __clone() - { - $this->id = md5(uniqid(get_class($this))); - $this->courseSetId = null; - } -} diff --git a/lib/classes/admission/AdmissionRule.php b/lib/classes/admission/AdmissionRule.php new file mode 100644 index 0000000..fe5e5bb --- /dev/null +++ b/lib/classes/admission/AdmissionRule.php @@ -0,0 +1,456 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +abstract class AdmissionRule +{ + // --- ATTRIBUTES --- + + /** + * When does the validity end? + */ + public $endTime = 0; + + /** + * A unique identifier for this rule. + */ + public $id = ''; + + /** + * A customizable message that is shown to users that are rejected for admission + * because of the current rule. + */ + public $message = ''; + + /** + * default message that is shown to users that are rejected for admission + * because of the current rule. + */ + public $default_message = ''; + + /** + * When does the validity start? + */ + public $startTime = 0; + + /** + * ID of the CourseSet this admission rule belongs to (is stored here for + * performance reasons). + */ + public $courseSetId = ''; + + /** + * courseset siblings of this rule + */ + public $siblings = []; + + /** + * Are siblings set manually? + */ + public $siblings_override = false; + + // --- OPERATIONS --- + + public function __construct($ruleId = '', $courseSetId = '') + { + $this->id = $ruleId; + $this->courseSetId = $courseSetId; + } + + /** + * Hook that can be called after the seat distribution on the courseset + * has completed. + * + * @param CourseSet $courseset Current courseset. + */ + public function afterSeatDistribution($courseset) + { + return true; + } + + /** + * Checks if we are in the rule validity time frame. + * + * @return bool True if the rule is valid because the time frame applies, + * otherwise false. + */ + public function checkTimeFrame() + { + $valid = true; + // Start time given, but still in the future. + if ($this->startTime && $this->startTime > time()) { + $valid = false; + } + // End time given, but already past. + if ($this->endTime && $this->endTime < time()) { + $valid = false; + } + return $valid; + } + + /** + * Deletes the admission rule and all associated data. + */ + public function delete() + { + // Delete rule assignment to coursesets. + $stmt = DBManager::get()->prepare("DELETE FROM `courseset_rule` + WHERE `rule_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Generate a new unique ID. + * + * @param String $tableName + */ + public function generateId($tableName) + { + do { + $newid = md5(uniqid(get_class($this).microtime(), true)); + $db = DBManager::get()->query("SELECT `rule_id` + FROM `".$tableName."` WHERE `rule_id`=" . DBManager::get()->quote($newid)); + } while ($db->fetch()); + return $newid; + } + + /** + * Gets all users that are matched by thís rule. + * + * @return Array An array containing IDs of users who are matched by + * this rule. + */ + public function getAffectedUsers() + { + return []; + } + + /** + * Reads all available AdmissionRule subclasses and loads their definitions. + * + * @param bool $activeOnly Show only active rules. + * @return Array + */ + public static function getAvailableAdmissionRules($activeOnly = true) + { + $rules = []; + $where = ($activeOnly ? " WHERE `active`=1" : ""); + $data = DBManager::get()->query("SELECT * FROM `admissionrules`".$where. + " ORDER BY `id` ASC"); + while ($current = $data->fetch(PDO::FETCH_ASSOC)) { + $className = $current['ruletype']; + if (is_dir($GLOBALS['STUDIP_BASE_PATH'] . DIRECTORY_SEPARATOR . $current['path'])) { + StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'] . DIRECTORY_SEPARATOR . $current['path']); + try { + $rule = new $className(); + $rules[$className] = [ + 'id' => $current['id'], + 'name' => $className::getName(), + 'description' => $className::getDescription(), + 'active' => $current['active'] + ]; + } catch (Exception $e) { + } + } + } + return $rules; + } + + /** + * Get end of validity. + * + * @return Integer + */ + public function getEndTime() + { + return $this->endTime; + } + + /** + * Subclasses of AdmissionRule can require additional data to be entered on + * admission (like PasswordAdmission which needs a password for course + * access). Their corresponding method getInput only returns a HTML form + * fragment as the output can be concatenated with output from other + * rules. + * This static method provides the frame for rendering a full HTML form + * around the fragments from subclasses. + * + * @return Array Start and end templates which wrap input form fragments + * from subclasses. + */ + public static final function getInputFrame() + { + return [ + $GLOBALS['template_factory']->open('admission/rules/input_start')->render(), + $GLOBALS['template_factory']->open('admission/rules/input_end')->render() + ]; + } + + /** + * Gets some text that describes what this AdmissionRule (or respective + * subclass) does. + */ + public static function getDescription() + { + return _("Legt eine Regel fest, die erfüllt sein muss, um sich ". + "erfolgreich zu einer Menge von Veranstaltungen anmelden zu ". + "können."); + } + + public function getInput() + { + return ''; + } + /** + * Gets the rule ID. + * + * @return String This rule's ID. + */ + public function getId() + { + return $this->id; + } + + /** + * Gets the message that is shown to users rejected by this rule. + * + * @return String The message. + */ + public function getMessage() + { + return $this->message ?: $this->default_message; + } + + /** + * Return this rule's name. + */ + public static function getName() + { + return _("Anmelderegel"); + } + + /** + * Gets start of validity. + * + * @return Integer + */ + public function getStartTime() + { + return $this->startTime; + } + + /** + * Gets the template that provides a configuration GUI for this rule. + * + * @return String + */ + public function getTemplate() + { + return ''; + } + + /** + * Internal helper function for loading rule definition from database. + */ + public function load() + { + } + + /** + * Hook that can be called when the seat distribution on the courseset + * starts. + * + * @param CourseSet $courseset The courseset this rule belongs to. + */ + public function beforeSeatDistribution($courseset) + { + return true; + } + + /** + * Does the current rule allow the given user to register as participant + * in the given course? + * + * @param String $userId + * @param String $courseId + * @return Array + */ + public function ruleApplies($userId, $courseId) + { + return []; + } + + /** + * Uses the given data to fill the object values. This can be used + * as a generic function for storing data if the concrete rule type + * isn't known in advance. + * + * @param Array $data + * @return AdmissionRule This object. + */ + public function setAllData($data) + { + if (!empty($data['start_date']) && empty($data['start_time'])) { + $data['start_time'] = strtotime($data['start_date']); + } + if (!empty($data['end_date']) && empty($data['end_time'])) { + $data['end_time'] = strtotime($data['end_date'] . ' 23:59:59'); + } + $this->message = $data['message'] ?? ''; + $this->startTime = $data['start_time'] ?? null; + $this->endTime = $data['end_time'] ?? null; + return $this; + } + + /** + * Sets a new end time for condition validity. + * + * @param Integer $newEndTime + * @return AdmissionRule + */ + public function setEndTime($newEndTime) + { + $this->endTime = $newEndTime; + return $this; + } + + /** + * Sets a new message to show to users. + * + * @param String $newMessage A new message text. + * @return AdmissionRule This object + */ + public function setMessage($newMessage) + { + $this->message = $newMessage; + return $this; + } + + /** + * Sets a new start time for condition validity. + * + * @param Integer $newStartTime + * @return AdmissionRule + */ + public function setStartTime($newStartTime) + { + $this->startTime = $newStartTime; + return $this; + } + + /** + * Helper function for storing rule definition to database. + */ + public function store() + { + } + + /** + * A textual description of the current rule. + * + * @return String + */ + public function toString() + { + return ''; + } + + /** + * Validates if the given request data is sufficient to configure this rule + * (e.g. if required values are present). + * + * @param Array $data Request data + * @return Array Error messages. + */ + public function validate($data) + { + $errors = []; + if (!empty($data['start_date']) && !empty($data['end_date']) && strtotime($data['end_date']) < strtotime($data['start_date'])) { + $errors[] = _('Das Enddatum darf nicht vor dem Startdatum liegen.'); + } + return $errors; + } + + /** + * Standard string representation of this object. + * + * @return String + */ + public function __toString() + { + return $this->toString(); + } + + /** + * load sibling rules + * + */ + public function loadSiblings() + { + if ($this->siblings_override) { + return false; + } + $this->siblings = []; + if ($this->courseSetId != '') { + $cs = new CourseSet($this->courseSetId); + foreach ($cs->getAdmissionRules() as $rule_id => $rule) { + if ($rule->getId() != $this->id) { + $this->siblings[$rule_id] = $rule; + } + } + } + } + + /** + * get sibling rules + * + */ + public function getSiblings() + { + $this->loadSiblings(); + return $this->siblings; + } + + /** + * set sibling rules + * + */ + public function setSiblings($siblings = []) + { + $this->siblings_override = true; + $this->siblings = $siblings; + } + + /** + * checks if given admission rule is allowed to be combined with this rule + * + * @param AdmissionRule|string $admission_rule + * @return boolean + */ + public function isCombinationAllowed($admission_rule) + { + if (is_object($admission_rule)) { + $admission_rule = get_class($admission_rule); + } + return AdmissionRuleCompatibility::exists([get_class($this), $admission_rule]); + } + + public function __clone() + { + $this->id = md5(uniqid(get_class($this))); + $this->courseSetId = null; + } +} diff --git a/lib/classes/admission/AdmissionUserList.class.php b/lib/classes/admission/AdmissionUserList.class.php deleted file mode 100644 index 570bc62..0000000 --- a/lib/classes/admission/AdmissionUserList.class.php +++ /dev/null @@ -1,405 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class AdmissionUserList -{ - // --- ATTRIBUTES --- - - /** - * Unique identifier of this list. - */ - public $id = ''; - - /** - * Conditions for automatic user selection. - */ - public $conditions = []; - - /** - * A factor for seat distribution algorithm ("1" means normal algorithm, - * everything between 0 and 1 decreases the chance to get a seat, - * everything above 1 increases it.) - */ - public $factor = 1; - - /** - * Some name to display for this list. - */ - public $name = ''; - - /** - * ID of the user who created this list. - */ - public $ownerId = ''; - - /** - * All user IDs that are on this list. - */ - public $users = []; - - // --- OPERATIONS --- - - /** - * Standard constructor. - * - * @param String id If this is an existing list, here is its ID. - * @return This object. - */ - public function __construct($id='') { - if ($id) { - $this->id = $id; - $this->load(); - } - return $this; - } - - /** - * Adds the given condition to the list. - * - * @param UserFilter condition - * @return AdmissionUserList - */ - public function addCondition($condition) - { - $this->conditions[$condition->getId()] = $condition; - return $this; - } - - /** - * Adds the given user to the list. - * - * @param String userId - * @return AdmissionUserList - */ - public function addUser($userId) - { - $this->users[$userId] = true; - return $this; - } - - /** - * Deletes this list. - */ - public function delete() { - // Remove user assignments to this list. - DBManager::get()->exec("DELETE FROM `user_factorlist` WHERE `list_id`='". - $this->id."'"); - // Remove assigned conditions. - foreach ($this->conditions as $condition) { - $condition->delete(); - } - DBManager::get()->exec("DELETE FROM `user_factorlist` WHERE `list_id`='". - $this->id."'"); - // Delete list data. - DBManager::get()->exec("DELETE FROM `admissionfactor` WHERE `list_id`='". - $this->id."'"); - } - - /** - * Gets the currently set conditions for automatic user selection. - * - * @return Integer - */ - public function getConditions() - { - return $this->conditions; - } - - /** - * Gets the currently set manipulation factor for this list. - * - * @return Float - */ - public function getFactor() - { - return $this->factor; - } - - /** - * Gets the list ID. - * - * @return String - */ - public function getId() - { - return $this->id; - } - - /** - * Gets the list name. - * - * @return String - */ - public function getName() - { - return $this->name; - } - - /** - * Gets the owner ID. - * - * @return String - */ - public function getOwnerId() - { - return $this->ownerId; - } - - /** - * Gets all user lists the given user has created. - * - * @param String userId - * @return array - */ - public static function getUserLists($userId) { - $result = []; - $stmt = DBManager::get()->prepare("SELECT `list_id` FROM `admissionfactor` WHERE ". - "`owner_id`=? ORDER BY `name` ASC"); - $stmt->execute([$userId]); - $lists = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($lists as $list) { - $result[$list['list_id']] = new AdmissionUserList($list['list_id']); - } - return $result; - } - - /** - * Gets all assigned user IDs. - * - * @param bool $as_objects Whether the users should be returned as objects - * @return array|User[] - */ - public function getUsers(bool $as_objects = false) - { - if (!$as_objects) { - return $this->users; - } - - $result = $this->users; - User::findEachMany( - function (User $user) use (&$result) { - $result[$user->id] = $user; - }, - array_keys($this->users) - ); - return array_values($result); - } - - /** - * Helper function for loading data from DB. - */ - public function load() - { - // Load basic data. - $stmt = DBManager::get()->prepare("SELECT `list_id`, `name`, - CAST(`factor` AS UNSIGNED) AS factor, `owner_id`, `mkdate`, `chdate` - FROM `admissionfactor` WHERE `list_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->factor = $current['factor']; - $this->name = $current['name']; - $this->ownerId = $current['owner_id']; - // Load user IDs. - $stmt2 = DBManager::get()->prepare("SELECT uf.* - FROM `user_factorlist` AS uf - JOIN `auth_user_md5` AS a ON (uf.`user_id`=a.`user_id`) - WHERE uf.`list_id`=? - ORDER BY a.`Nachname` ASC, a.`Vorname` ASC, a.`username` ASC"); - $stmt2->execute([$this->id]); - while ($user = $stmt2->fetch(PDO::FETCH_ASSOC)) { - $this->users[$user['user_id']] = true; - } - - // Load selection conditions, if applicable. - // $stmt2 = DBManager::get()->prepare("SELECT `condition_id` FROM ". - // "`condition_factorlist` WHERE `list_id`=? ORDER BY `mkdate` ASC"); - //$stmt2->execute(array($this->id)); - //while ($current = $stmt2->fetch(PDO::FETCH_ASSOC)) { - // $this->conditions[$current['condition_id']] = - // new UserFilter($current['condition_id']); - //} - } - } - - /** - * Removes the given condition from the list. - * - * @param String conditionId - * @return AdmissionUserList - */ - public function removeCondition($conditionId) - { - unset($this->conditions[$conditionId]); - return $this; - } - - /** - * Removes the given user from the list. - * - * @param String userId - * @return AdmissionUserList - */ - public function removeUser($userId) - { - unset($this->users[$userId]); - return $this; - } - - /** - * Set the conditions to the given set. - * - * @param Array conditions - * @return AdmissionUserList - */ - public function setConditions($conditions) { - $this->conditions = []; - foreach ($conditions as $condition) { - $this->addCondition($condition); - } - return $this; - } - - /** - * Sets a factor. - * - * @param float $newFactor The new factor to be set. - * @return AdmissionUserList - */ - public function setFactor($newFactor) - { - $this->factor = $newFactor; - return $this; - } - - /** - * Sets a name. - * - * @param String $newName New list name. - * @return AdmissionUserList - */ - public function setName($newName) - { - $this->name = $newName; - return $this; - } - - /** - * Sets a new owner. - * - * @param String $newOwnerId New owner Id. - * @return AdmissionUserList - */ - public function setOwnerId($newOwnerId) - { - $this->ownerId = $newOwnerId; - return $this; - } - - /** - * Sets a set of new list members, replacing previous entries. - * - * @param Array $newUsers New member list. - * @return AdmissionUserList - */ - public function setUsers($newUsers) - { - $this->users = []; - foreach ($newUsers as $userId) { - $this->addUser($userId); - } - return $this; - } - - public function describe(array $wrapper = ['', '']): string - { - if ($this->getFactor() == 0) { - return _('Bei der Platzverteilung zu Veranstaltungen werden die ' - . 'betreffenden Personen nur nachrangig berücksichtigt.'); - } - - if ($this->getFactor() == PHP_INT_MAX) { - return _('Bei der Platzverteilung zu Veranstaltungen werden die ' - . 'betreffenden Personen vor allen anderen einen Platz erhalten.'); - } - - return sprintf( - _('Bei der Platzverteilung zu Veranstaltungen haben die betreffenden ' - . 'Personen gegenüber Anderen eine %s-fache Chance darauf, einen Platz zu ' - . 'erhalten.'), - $wrapper[0] . $this->getFactor() . $wrapper[1] - ); - } - - /** - * Function for storing the data to DB. Is not called automatically on - * changing object values. - */ - public function store() { - // Generate new ID if list doesn't exist in DB yet. - if (!$this->id) { - do { - $newid = md5(uniqid('AdmissionUserList', true)); - $db = DBManager::get()->query("SELECT `list_id` - FROM `admissionfactor` WHERE `list_id`='.$newid.'"); - } while ($db->fetch()); - $this->id = $newid; - } - // Store basic list data. - $stmt = DBManager::get()->prepare("INSERT INTO `admissionfactor` - (`list_id`, `name`, `factor`, `owner_id`, `mkdate`, `chdate`) - VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `name`=VALUES(`name`), `factor`=VALUES(`factor`), - `owner_id`=VALUES(`owner_id`), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->name, $this->factor, - $this->ownerId, time(), time()]); - // Clear all old user assignments to this list. - DBManager::get()->exec("DELETE FROM `user_factorlist` WHERE `list_id`='". - $this->id."' AND `user_id` NOT IN ('". - implode("', '", array_keys($this->users))."')"); - // Store assigned users. - foreach ($this->users as $userId => $assigned) { - $stmt = DBManager::get()->prepare("INSERT INTO `user_factorlist` - (`list_id`, `user_id`, `mkdate`) - VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE - `user_id`=VALUES(`user_id`)"); - $stmt->execute([$this->id, $userId, time()]); - } - return $this; - } - - /** - * String representation of this object. - */ - public function toString() - { - $tpl = $GLOBALS['template_factory']->open('admission/userlist'); - $tpl->set_attribute('userlist', $this); - return $tpl->render(); - } - - /** - * Standard string representation of this object. - * - * @return String - */ - public function __toString() - { - return $this->toString(); - } - -} diff --git a/lib/classes/admission/AdmissionUserList.php b/lib/classes/admission/AdmissionUserList.php new file mode 100644 index 0000000..570bc62 --- /dev/null +++ b/lib/classes/admission/AdmissionUserList.php @@ -0,0 +1,405 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class AdmissionUserList +{ + // --- ATTRIBUTES --- + + /** + * Unique identifier of this list. + */ + public $id = ''; + + /** + * Conditions for automatic user selection. + */ + public $conditions = []; + + /** + * A factor for seat distribution algorithm ("1" means normal algorithm, + * everything between 0 and 1 decreases the chance to get a seat, + * everything above 1 increases it.) + */ + public $factor = 1; + + /** + * Some name to display for this list. + */ + public $name = ''; + + /** + * ID of the user who created this list. + */ + public $ownerId = ''; + + /** + * All user IDs that are on this list. + */ + public $users = []; + + // --- OPERATIONS --- + + /** + * Standard constructor. + * + * @param String id If this is an existing list, here is its ID. + * @return This object. + */ + public function __construct($id='') { + if ($id) { + $this->id = $id; + $this->load(); + } + return $this; + } + + /** + * Adds the given condition to the list. + * + * @param UserFilter condition + * @return AdmissionUserList + */ + public function addCondition($condition) + { + $this->conditions[$condition->getId()] = $condition; + return $this; + } + + /** + * Adds the given user to the list. + * + * @param String userId + * @return AdmissionUserList + */ + public function addUser($userId) + { + $this->users[$userId] = true; + return $this; + } + + /** + * Deletes this list. + */ + public function delete() { + // Remove user assignments to this list. + DBManager::get()->exec("DELETE FROM `user_factorlist` WHERE `list_id`='". + $this->id."'"); + // Remove assigned conditions. + foreach ($this->conditions as $condition) { + $condition->delete(); + } + DBManager::get()->exec("DELETE FROM `user_factorlist` WHERE `list_id`='". + $this->id."'"); + // Delete list data. + DBManager::get()->exec("DELETE FROM `admissionfactor` WHERE `list_id`='". + $this->id."'"); + } + + /** + * Gets the currently set conditions for automatic user selection. + * + * @return Integer + */ + public function getConditions() + { + return $this->conditions; + } + + /** + * Gets the currently set manipulation factor for this list. + * + * @return Float + */ + public function getFactor() + { + return $this->factor; + } + + /** + * Gets the list ID. + * + * @return String + */ + public function getId() + { + return $this->id; + } + + /** + * Gets the list name. + * + * @return String + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the owner ID. + * + * @return String + */ + public function getOwnerId() + { + return $this->ownerId; + } + + /** + * Gets all user lists the given user has created. + * + * @param String userId + * @return array + */ + public static function getUserLists($userId) { + $result = []; + $stmt = DBManager::get()->prepare("SELECT `list_id` FROM `admissionfactor` WHERE ". + "`owner_id`=? ORDER BY `name` ASC"); + $stmt->execute([$userId]); + $lists = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($lists as $list) { + $result[$list['list_id']] = new AdmissionUserList($list['list_id']); + } + return $result; + } + + /** + * Gets all assigned user IDs. + * + * @param bool $as_objects Whether the users should be returned as objects + * @return array|User[] + */ + public function getUsers(bool $as_objects = false) + { + if (!$as_objects) { + return $this->users; + } + + $result = $this->users; + User::findEachMany( + function (User $user) use (&$result) { + $result[$user->id] = $user; + }, + array_keys($this->users) + ); + return array_values($result); + } + + /** + * Helper function for loading data from DB. + */ + public function load() + { + // Load basic data. + $stmt = DBManager::get()->prepare("SELECT `list_id`, `name`, + CAST(`factor` AS UNSIGNED) AS factor, `owner_id`, `mkdate`, `chdate` + FROM `admissionfactor` WHERE `list_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->factor = $current['factor']; + $this->name = $current['name']; + $this->ownerId = $current['owner_id']; + // Load user IDs. + $stmt2 = DBManager::get()->prepare("SELECT uf.* + FROM `user_factorlist` AS uf + JOIN `auth_user_md5` AS a ON (uf.`user_id`=a.`user_id`) + WHERE uf.`list_id`=? + ORDER BY a.`Nachname` ASC, a.`Vorname` ASC, a.`username` ASC"); + $stmt2->execute([$this->id]); + while ($user = $stmt2->fetch(PDO::FETCH_ASSOC)) { + $this->users[$user['user_id']] = true; + } + + // Load selection conditions, if applicable. + // $stmt2 = DBManager::get()->prepare("SELECT `condition_id` FROM ". + // "`condition_factorlist` WHERE `list_id`=? ORDER BY `mkdate` ASC"); + //$stmt2->execute(array($this->id)); + //while ($current = $stmt2->fetch(PDO::FETCH_ASSOC)) { + // $this->conditions[$current['condition_id']] = + // new UserFilter($current['condition_id']); + //} + } + } + + /** + * Removes the given condition from the list. + * + * @param String conditionId + * @return AdmissionUserList + */ + public function removeCondition($conditionId) + { + unset($this->conditions[$conditionId]); + return $this; + } + + /** + * Removes the given user from the list. + * + * @param String userId + * @return AdmissionUserList + */ + public function removeUser($userId) + { + unset($this->users[$userId]); + return $this; + } + + /** + * Set the conditions to the given set. + * + * @param Array conditions + * @return AdmissionUserList + */ + public function setConditions($conditions) { + $this->conditions = []; + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + return $this; + } + + /** + * Sets a factor. + * + * @param float $newFactor The new factor to be set. + * @return AdmissionUserList + */ + public function setFactor($newFactor) + { + $this->factor = $newFactor; + return $this; + } + + /** + * Sets a name. + * + * @param String $newName New list name. + * @return AdmissionUserList + */ + public function setName($newName) + { + $this->name = $newName; + return $this; + } + + /** + * Sets a new owner. + * + * @param String $newOwnerId New owner Id. + * @return AdmissionUserList + */ + public function setOwnerId($newOwnerId) + { + $this->ownerId = $newOwnerId; + return $this; + } + + /** + * Sets a set of new list members, replacing previous entries. + * + * @param Array $newUsers New member list. + * @return AdmissionUserList + */ + public function setUsers($newUsers) + { + $this->users = []; + foreach ($newUsers as $userId) { + $this->addUser($userId); + } + return $this; + } + + public function describe(array $wrapper = ['', '']): string + { + if ($this->getFactor() == 0) { + return _('Bei der Platzverteilung zu Veranstaltungen werden die ' + . 'betreffenden Personen nur nachrangig berücksichtigt.'); + } + + if ($this->getFactor() == PHP_INT_MAX) { + return _('Bei der Platzverteilung zu Veranstaltungen werden die ' + . 'betreffenden Personen vor allen anderen einen Platz erhalten.'); + } + + return sprintf( + _('Bei der Platzverteilung zu Veranstaltungen haben die betreffenden ' + . 'Personen gegenüber Anderen eine %s-fache Chance darauf, einen Platz zu ' + . 'erhalten.'), + $wrapper[0] . $this->getFactor() . $wrapper[1] + ); + } + + /** + * Function for storing the data to DB. Is not called automatically on + * changing object values. + */ + public function store() { + // Generate new ID if list doesn't exist in DB yet. + if (!$this->id) { + do { + $newid = md5(uniqid('AdmissionUserList', true)); + $db = DBManager::get()->query("SELECT `list_id` + FROM `admissionfactor` WHERE `list_id`='.$newid.'"); + } while ($db->fetch()); + $this->id = $newid; + } + // Store basic list data. + $stmt = DBManager::get()->prepare("INSERT INTO `admissionfactor` + (`list_id`, `name`, `factor`, `owner_id`, `mkdate`, `chdate`) + VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `name`=VALUES(`name`), `factor`=VALUES(`factor`), + `owner_id`=VALUES(`owner_id`), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->name, $this->factor, + $this->ownerId, time(), time()]); + // Clear all old user assignments to this list. + DBManager::get()->exec("DELETE FROM `user_factorlist` WHERE `list_id`='". + $this->id."' AND `user_id` NOT IN ('". + implode("', '", array_keys($this->users))."')"); + // Store assigned users. + foreach ($this->users as $userId => $assigned) { + $stmt = DBManager::get()->prepare("INSERT INTO `user_factorlist` + (`list_id`, `user_id`, `mkdate`) + VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE + `user_id`=VALUES(`user_id`)"); + $stmt->execute([$this->id, $userId, time()]); + } + return $this; + } + + /** + * String representation of this object. + */ + public function toString() + { + $tpl = $GLOBALS['template_factory']->open('admission/userlist'); + $tpl->set_attribute('userlist', $this); + return $tpl->render(); + } + + /** + * Standard string representation of this object. + * + * @return String + */ + public function __toString() + { + return $this->toString(); + } + +} diff --git a/lib/classes/admission/CourseSet.class.php b/lib/classes/admission/CourseSet.class.php deleted file mode 100644 index d94bc05..0000000 --- a/lib/classes/admission/CourseSet.class.php +++ /dev/null @@ -1,1185 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class CourseSet -{ - // --- ATTRIBUTES --- - - /** - * Admission rules that are applied to the courses belonging to this set. - */ - protected $admissionRules = []; - - /** - * Seat distribution algorithm. - */ - protected $algorithm = null; - - /** - * IDs of courses that are aggregated into this set. The array is in the - * form ($courseId1 => true, $courseId2 => true). - */ - protected $courses = []; - - /** - * Has the seat distribution algorithm already been executed? - */ - protected $hasAlgorithmRun = false; - - /** - * Unique identifier for this set. - */ - protected $id = ''; - - /** - * Some extensive descriptional text for informing confused students. - */ - protected $infoText = ''; - - /** - * Which Stud.IP institute does the course set belong to? - */ - protected $institutes = []; - - /** - * Some display name for this course set. - */ - protected $name = ''; - - /** - * Is the course set only visible for the creator? - */ - protected $private = false; - - /** - * Who owns this course set? - */ - protected $user_id = false; - - /** - * When was the course set changed? - */ - protected $chdate = null; - - /* - * Lists of users who are treated differently on seat distribution - */ - protected $userlists = []; - - // --- OPERATIONS --- - - public function __construct($setId='') { - $this->id = $setId; - $this->name = _("Anmeldeset"); - $this->algorithm = new RandomAlgorithm(); - // Autoload admission rules. - AdmissionRule::getAvailableAdmissionRules(); - // Define autoload function for admission rules. - spl_autoload_register(['UserFilterField', 'getAvailableFilterFields']); - if ($setId) { - $this->load(); - } - } - - /** - * Adds the given admission rule to the list of rules for the course set. - * - * @param AdmissionRule rule - * @return CourseSet - */ - public function addAdmissionRule($rule) - { - $this->admissionRules[$rule->getId()] = $rule; - return $this; - } - - /** - * Adds the course with the given ID to the course set. - * - * @param String courseId - * @return CourseSet - */ - public function addCourse($courseId) - { - $this->courses[$courseId] = true; - return $this; - } - - /** - * Adds a new institute ID. - * - * @param String newId - * @return CourseSet - */ - public function addInstitute($newId) { - $this->institutes[$newId] = true; - return $this; - } - - /** - * Adds several institute IDs to the existing institute assignments. - * - * @param Array newIds - * @return CourseSet - */ - public function addInstitutes($newIds) { - foreach ($newIds as $newId) { - $this->addInstitute($newId); - } - return $this; - } - - /** - * Adds a UserList to the course set. The list contains several users and a - * factor that changes seat distribution chances for these users; - * - * @param String listId - * @return CourseSet - */ - public function addUserList($listId) - { - $this->userlists[$listId] = true; - return $this; - } - - /** - * Is the given user allowed to register as participant in the given - * course according to the rules of this course set? - * - * @param String userId - * @param String courseId - * @return Array Optional error messages from rules if something went wrong. - */ - public function checkAdmission($userId, $courseId) { - $errors = []; - foreach ($this->admissionRules as $rule) { - // All rules must be fulfilled. - $ruleCheck = $rule->ruleApplies($userId, $courseId); - if ($ruleCheck) { - $errors = array_merge($errors, $ruleCheck); - } - } - return $errors; - } - - /** - * Removes all admission rules at once. - * - * @return CourseSet - */ - public function clearAdmissionRules() { - $this->admissionRules = []; - return $this; - } - - /** - * Deletes the course set and all associated data. - */ - public function delete() { - NotificationCenter::postNotification('CourseSetWillDelete', $this->id, $GLOBALS['user']->id); - // Delete institute associations. - $stmt = DBManager::get()->prepare("DELETE FROM `courseset_institute` - WHERE `set_id`=?"); - $stmt->execute([$this->id]); - // Delete course associations. - $stmt = DBManager::get()->prepare("DELETE FROM `seminar_courseset` - WHERE `set_id`=?"); - $stmt->execute([$this->id]); - // Delete all rules... - foreach ($this->admissionRules as $rule) { - $rule->delete(); - } - // ... and their association to the current course set. - $stmt = DBManager::get()->prepare("DELETE FROM `courseset_rule` - WHERE `set_id`=?"); - $stmt->execute([$this->id]); - // Delete associations to user lists. - $stmt = DBManager::get()->prepare("DELETE FROM `courseset_factorlist` - WHERE `set_id`=?"); - $stmt->execute([$this->id]); - // Delete course set data. - $stmt = DBManager::get()->prepare("DELETE FROM `coursesets` - WHERE `set_id`=?"); - $stmt->execute([$this->id]); - /* - * Delete waiting lists - */ - foreach ($this->courses as $id => $assigned) { - AdmissionApplication::deleteBySQL("status='awaiting' AND seminar_id=?", [$id]); - } - //Delete priorities - AdmissionPriority::unsetAllPriorities($this->getId()); - NotificationCenter::postNotification('CourseSetDidDelete', $this->id, $GLOBALS['user']->id); - } - - /** - * Starts the seat distribution algorithm. - */ - public function distributeSeats() { - if ($this->algorithm) { - // Call pre-distribution hooks on all assigned rules. - foreach ($this->admissionRules as &$rule) { - $rule->beforeSeatDistribution($this); - } - $this->algorithm->run($this); - // Mark as "seats distributed". - $this->setAlgorithmRun(true); - // Call post-distribution hooks on all assigned rules. - foreach ($this->admissionRules as &$rule) { - $rule->afterSeatDistribution($this); - } - AdmissionPriority::unsetAllPriorities($this->getId()); - } - } - - public function setAlgorithmRun($state) - { - NotificationCenter::postNotification('CourseSetAlgorithmWillStart', $state, $this->getId()); - $this->hasAlgorithmRun = (bool)$state; - $db = DBManager::get(); - $ok = $db->execute("UPDATE coursesets SET algorithm_run = ? WHERE set_id = ?", [$this->hasAlgorithmRun, $this->getId()]); - if ($ok) { - NotificationCenter::postNotification('CourseSetAlgorithmDidStart', $state, $this->getId()); - } - return $ok; - } - - /** - * returns true if the set allows only a limited number of places - * - * @return boolean - */ - public function isSeatDistributionEnabled() - { - return $this->getSeatDistributionTime() !== null; - } - - /** - * returns timestamp of distribution time or null if no distribution time available - * may return 0 if first-come-first-serve is enabled - * - * @return integer|null timestamp of distribution - */ - public function getSeatDistributionTime() - { - $pr_admission = $this->getAdmissionRule('ParticipantRestrictedAdmission'); - if ($pr_admission) { - return $pr_admission->getDistributionTime(); - } - } - - /** - * Get all admission rules belonging to the course set. - * - * @return AdmissionRule[] - */ - public function getAdmissionRules() - { - return $this->admissionRules; - } - - public function getAdmissionRule($class_name) - { - $result = array_filter($this->getAdmissionRules(), function($r) use ($class_name) { - return $r instanceof $class_name;} - ); - return array_pop($result); - } - /** - * check if course set has given admission rule - * - * @param string $rule name of AdmissionRule class - * @return boolean - */ - public function hasAdmissionRule($rule) - { - return is_object($this->getAdmissionRule($rule)); - } - - /** - * Get the currently used distribution algorithm. - * - * @return AdmissionAlgorithm - */ - public function getAlgorithm() - { - return $this->algorithm; - } - - /** - * How many users will be allowed to register according to the defined - * rules? This can help in estimating whether the combination of the - * defined rules makes sense. - * - * @return int - */ - public function getAllowedUserCount() - { - $users = []; - foreach ($this->admissionRules as $rule) { - $users = array_merge($users, $rule->getAffectedUsers()); - } - return sizeof($users); - } - - /** - * Gets the course IDs belonging to the course set. - * - * @return Array - */ - public function getCourses() - { - return array_keys($this->courses); - } - - /** - * Gets all courses belonging to the given course set ID. - * - * @param String $courseSetId - * @return Array - */ - public static function getCoursesByCourseSetId($courseSetId) - { - $query = "SELECT `seminar_id` - FROM `seminar_courseset` - WHERE `set_id` = ?"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute([$courseSetId]); - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } - - /** - * Gets all course sets belonging to the given institute ID. - * - * @param String $instituteId - * @return Array - */ - public static function getCoursesetsByInstituteId($instituteId, $filter = []) { - $query = "SELECT DISTINCT ci.* - FROM `courseset_institute` ci - JOIN `coursesets` c ON (ci.`set_id`=c.`set_id`) - LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id - LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id - LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id - WHERE ci.`institute_id`=?"; - $parameters = [$instituteId]; - if (!$GLOBALS['perm']->have_perm('admin')) { - $query .= " AND (c.`private`=0 OR c.`user_id`=?)"; - $parameters[] = $GLOBALS['user']->id; - } - if (!empty($filter['course_set_name'])) { - $query .= " AND c.name LIKE ?"; - $parameters[] = $filter['course_set_name'] . '%'; - } - if (!empty($filter['rule_types']) && is_array($filter['rule_types']) && count($filter['rule_types'])) { - $query .= " AND cr.type IN (?)"; - $parameters[] = $filter['rule_types']; - } - if (!empty($filter['semester_id'])) { - $query .= " AND s.start_time = ?"; - $parameters[] = Semester::find($filter['semester_id'])->beginn; - } - if (!empty($filter['course_set_chdate'])) { - $query .= " AND c.chdate > ?"; - $parameters[] = $filter['course_set_chdate']; - } - $query .= " ORDER BY c.name"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute($parameters); - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } - - /** - * Gets all global course sets - * - * @param array $filter - * @return Array - */ - public static function getGlobalCoursesets($filter = []) { - $query = "SELECT DISTINCT c.set_id - FROM coursesets c - LEFT JOIN courseset_institute ci ON ci.`set_id`=c.`set_id` - LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id - LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id - LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id - WHERE ci.institute_id IS NULL"; - $parameters = []; - $query .= " AND (c.`private`=0 OR c.`user_id`=?)"; - $parameters[] = $GLOBALS['user']->id; - if (!empty($filter['course_set_name'])) { - $query .= " AND c.name LIKE ?"; - $parameters[] = $filter['course_set_name'] . '%'; - } - if (!empty($filter['rule_types']) && is_array($filter['rule_types']) && count($filter['rule_types'])) { - $query .= " AND cr.type IN (?)"; - $parameters[] = $filter['rule_types']; - } - if (!empty($filter['semester_id'])) { - $query .= " AND s.start_time = ?"; - $parameters[] = Semester::find($filter['semester_id'])->beginn; - } - if (!empty($filter['course_set_chdate'])) { - $query .= " AND c.chdate > ?"; - $parameters[] = $filter['course_set_chdate']; - } - $query .= " ORDER BY c.name"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute($parameters); - return $stmt->fetchAll(PDO::FETCH_ASSOC); - } - - /** - * Get the identifier of the course set. - * - * @return String - */ - public function getId() - { - return $this->id; - } - - /** - * Get the course set's info text. - * - * @return String - */ - public function getInfoText() - { - return $this->infoText; - } - - /** - * Which institutes does the rule belong to? - * - * @return Array - */ - public function getInstituteIds() { - return $this->institutes; - } - - public function isGlobal() - { - return count($this->institutes) == 0; - } - /** - * Gets this course set's display name. - */ - public function getName() { - return $this->name; - } - - /** - * Returns the date of the last change. - * @return int - */ - public function getChdate() - { - return $this->chdate; - } - - /** - * Retrieves the priorities given to the courses in this set. - * - * @return Array - */ - public function getPriorities() - { - return AdmissionPriority::getPriorities($this->id); - } - - public function getNumApplicants() - { - return AdmissionPriority::getPrioritiesCount($this->id); - } - - /** - * Is the current courseset private? - * - * @return bool - */ - public function getPrivate() { - return $this->private; - } - - - /** - * returns latest semester id from assigned courses - * if no courses are assigned current semester - * - * @return string id of semester - */ - public function getSemester() { - $db = DBManager::get(); - $data = $db->fetchOne(" - SELECT semester_data.* - FROM semester_data - INNER JOIN semester_courses ON (semester_data.semester_id = semester_courses.semester_id) - WHERE semester_courses.course_id IN (?) - ORDER BY semester_data.ende DESC - LIMIT 1 - ", [$this->getCourses()]); - if ($data) { - $semester = Semester::buildExisting($data); - } else { - $semester = $_SESSION['_default_sem'] ? Semester::find($_SESSION['_default_sem']) : Semester::findCurrent(); - } - return $semester->id; - } - - /** - * Gets the owner of this course set. - */ - public function getUserId() { - return $this->user_id; - } - - public function setUserId($user_id) { - $this->user_id = $user_id; - return $this; - } - - /** - * Gets the course sets the given course belongs to. - * - * @param String courseId - * @return CourseSet - */ - public static function getSetForCourse($courseId) - { - $stmt = DBManager::get()->prepare("SELECT `set_id` - FROM `seminar_courseset` WHERE `seminar_id`=?"); - $stmt->execute([$courseId]); - $set_id = $stmt->fetchColumn(); - if ($set_id) { - return new CourseSet($set_id); - } - return null; - } - - /** - * Gets the course sets the given rule belongs to. - * - * @param String $rule_id - * @return CourseSet - */ - public static function getSetForRule($rule_id) - { - $set_id = DBManager::get()->fetchColumn("SELECT `set_id` - FROM `courseset_rule` WHERE `rule_id`=?", [$rule_id]); - if ($set_id) { - return new CourseSet($set_id); - } - return null; - } - - /** - * Retrieves the lists of users that are considered specially in - * seat distribution. - * - * @return Array - */ - public function getUserLists() - { - return array_keys($this->userlists); - } - - public function getUserFactorList() - { - $factored_users = []; - - foreach ($this->getUserLists() as $ul_id) { - $user_list = new AdmissionUserList($ul_id); - - // Iterate through user list. - foreach ($user_list->getUsers() as $user => $assigned) { - switch ($user_list->getFactor()) { - // Maximum factor, just set it and stop further processing of user lists. - case PHP_INT_MAX: - $factored_users[$user] = PHP_INT_MAX; - break; - // Backlist, set malus and stop further processing of user lists. - case 0: - $factored_users[$user] = 0; - break; - default: - // Add up current bonus if it isn't already at 0 or PHP_INT_MAX. - if ($factored_users[$user] !== 0 && $factored_users[$user] != PHP_INT_MAX) { - $factored_users[$user] = isset($factored_users[$user]) ? - $factored_users[$user] + $user_list->getFactor() : - $user_list->getFactor(); - } - } - } - } - - return $factored_users; - } - - /** - * Evaluates whether the seat distribution algorithm has already been - * executed on this course set. - * - * @return boolean True if algorithm has already run, otherwise false. - */ - public function hasAlgorithmRun() { - return $this->hasAlgorithmRun; - } - - /** - * Helper function for loading data from DB. - */ - public function load() { - // Load basic data. - $stmt = DBManager::get()->prepare( - "SELECT * FROM `coursesets` WHERE set_id=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->name = $data['name']; - $this->infoText = $data['infotext']; - $this->hasAlgorithmRun = (bool)$data['algorithm_run']; - if ($data['algorithm']) { - if (class_exists($data['algorithm'])) { - $this->algorithm = new $data['algorithm'](); - } - } - $this->private = (bool) $data['private']; - $this->user_id = $data['user_id']; - $this->chdate = $data['chdate']; - } - // Load institute assigments. - $stmt = DBManager::get()->prepare(" - SELECT courseset_institute.institute_id - FROM `courseset_institute` - INNER JOIN Institute ON (courseset_institute.institute_id = Institute.Institut_id) - WHERE courseset_institute.set_id = ? - ORDER BY Institute.Name ASC - "); - $stmt->execute([$this->id]); - $this->institutes = []; - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->institutes[$data['institute_id']] = true; - } - // Load courses. - $stmt = DBManager::get()->prepare( - "SELECT seminar_id FROM `seminar_courseset` WHERE set_id=?"); - $stmt->execute([$this->id]); - $this->courses = []; - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->courses[$data['seminar_id']] = true; - } - // Load admission rules. - $stmt = DBManager::get()->prepare( - "SELECT * FROM `courseset_rule` WHERE set_id=?"); - $stmt->execute([$this->id]); - $this->admissionRules = []; - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - if (class_exists($data['type'])) { - $this->admissionRules[$data['rule_id']] = - new $data['type']($data['rule_id'], $this->id); - } - } - // Load assigned user lists. - $stmt = DBManager::get()->prepare("SELECT `factorlist_id` - FROM `courseset_factorlist` WHERE `set_id`=?"); - $stmt->execute([$this->id]); - $this->userlists = []; - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->userlists[$current['factorlist_id']] = true; - } - return $this; - } - - /** - * Removes the course with the given ID from the set. - * - * @param String courseId - * @return CourseSet - */ - public function removeCourse($courseId) - { - unset($this->courses[$courseId]); - return $this; - } - - /** - * Removes the rule with the given ID from the set. - * - * @param String ruleId - * @return CourseSet - */ - public function removeAdmissionRule($ruleId) - { - unset($this->admissionRules[$ruleId]); - return $this; - } - - /** - * Removes the institute with the given ID from the set. - * - * @param String instituteId - * @return CourseSet - */ - public function removeInstitute($instituteId) - { - unset($this->institutes[$instituteId]); - return $this; - } - - /** - * Removes the user list with the given ID from the set. - * - * @param String listId - * @return CourseSet - */ - public function removeUserlist($listId) - { - unset($this->userlists[$listId]); - return $this; - } - - /** - * Adds several admission rules after clearing the existing rule - * assignments. - * - * @param Array newIds - * @return CourseSet - */ - public function setAdmissionRules($newRules) { - $this->admissionRules = []; - foreach ($newRules as $newRule) { - $rule = ObjectBuilder::build($newRule, 'AdmissionRule'); - $this->addAdmissionRule($rule); - } - return $this; - } - - /** - * Sets a seat distribution algorithm for this course set. This will only - * have an effect in conjunction with a TimedAdmission, as the algorithm - * needs a defined point in time where it will start. - * - * @param String newAlgorithm - * @return CourseSet - */ - public function setAlgorithm($newAlgorithm) - { - try { - $this->algorithm = new $newAlgorithm(); - } catch (Exception $e) { - } - return $this; - } - - /** - * Adds several course IDs after clearing the existing course - * assignments. - * - * @param Array newIds - * @return CourseSet - */ - public function setCourses($newIds) { - $this->courses = array_fill_keys($newIds, true); - return $this; - } - - /** - * Adds several institute IDs after clearing the existing institute - * assignments. - * - * @param Array newIds - * @return CourseSet - */ - public function setInstitutes($newIds) { - $this->institutes = []; - $this->addInstitutes($newIds); - return $this; - } - - /** - * Set the course set's info text. - * - * @return CourseSet - */ - public function setInfoText($newText) - { - $this->infoText = $newText; - return $this; - } - - /* Sets a new name for this course set. - * - * @param String newName - * @return CourseSet - */ - public function setName($newName) { - $this->name = $newName; - return $this; - } - - /** - * Set a new value for courseset privacy. - * - * @param bool $newPrivate - * @return CourseSet - */ - public function setPrivate($newPrivate) { - $this->private = $newPrivate; - return $this; - } - - /** - * Adds several user list IDs after clearing the existing user list - * assignments. - * - * @param Array newIds - * @return CourseSet - */ - public function setUserlists($newIds) { - $this->userlists = []; - foreach ($newIds as $newId) { - $this->addUserlist($newId); - } - return $this; - } - - public function store() { - // Generate new ID if course set doesn't exist in DB yet. - if (!$this->id) { - do { - $newid = md5(uniqid(get_class($this), true)); - $db = DBManager::get()->query("SELECT `set_id` - FROM `coursesets` WHERE `set_id`='$newid'"); - } while ($db->fetch()); - $this->id = $newid; - } - if (!$this->user_id) { - $this->user_id = $GLOBALS['user']->id; - } - if ($this->isSeatDistributionEnabled()) { - if (!$this->getAlgorithm()) { - $algorithm = new RandomAlgorithm(); - $this->setAlgorithm($algorithm); - } - if (!$this->getSeatDistributionTime()) { - $this->setAlgorithmRun(true); - //Delete priorities - AdmissionPriority::unsetAllPriorities($this->getId()); - } - if ($this->getSeatDistributionTime() > time()) { - $this->setAlgorithmRun(false); - } - } - // Store basic data. - $stmt = DBManager::get()->prepare("INSERT INTO `coursesets` - (`set_id`, `user_id`, `name`, `infotext`, `algorithm`, `algorithm_run`, - `private`, `mkdate`, `chdate`) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `name`=VALUES(`name`), `infotext`=VALUES(`infotext`), - `algorithm`=VALUES(`algorithm`), `algorithm_run`=VALUES(`algorithm_run`), `private`=VALUES(`private`), - `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->user_id, $this->name, $this->infoText, - get_class($this->algorithm), $this->hasAlgorithmRun(), intval($this->private), time(), time()]); - // Delete removed institute assignments from database. - DBManager::get()->exec("DELETE FROM `courseset_institute` - WHERE `set_id`='".$this->id."' AND `institute_id` NOT IN ('". - implode("', '", array_keys($this->institutes))."')"); - // Store associated institute IDs. - foreach ($this->institutes as $institute => $associated) { - $stmt = DBManager::get()->prepare("INSERT IGNORE INTO `courseset_institute` - (`set_id`, `institute_id`, `mkdate`) - VALUES (?, ?, ?)"); - $stmt->execute([$this->id, $institute, time()]); - } - // log removed course assignments. - DBManager::get()->fetchAll("SELECT seminar_id,set_id FROM `seminar_courseset` - WHERE `set_id` = ? AND `seminar_id` NOT IN (?)", [$this->id, count($this->courses) ? array_keys($this->courses) : ''], - function ($row) { - StudipLog::log('SEM_CHANGED_ACCESS', $row['seminar_id'], - null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $row['set_id'])); - //Delete priorities - AdmissionPriority::unsetAllPrioritiesForCourse($row['seminar_id']); - Course::buildExisting(['seminar_id' => $row['seminar_id']])->triggerChdate(); - }); - //removed course assignments - DBManager::get()->execute("DELETE FROM `seminar_courseset` - WHERE `set_id` = ? AND `seminar_id` NOT IN (?)", [$this->id, count($this->courses) ? array_keys($this->courses) : '']); - //log removing other associations - DBManager::get()->execute("SELECT seminar_id,set_id FROM `seminar_courseset` - WHERE `set_id` <> ? AND `seminar_id` IN (?)", [$this->id, array_keys($this->courses)], - function ($row) { - StudipLog::log('SEM_CHANGED_ACCESS', $row['seminar_id'], - null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $row['set_id'])); - //Delete priorities - AdmissionPriority::unsetAllPrioritiesForCourse($row['seminar_id']); - Course::buildExisting(['seminar_id' => $row['seminar_id']])->triggerChdate(); - }); - //Delete other associations, only one set possible - DBManager::get()->execute("DELETE FROM `seminar_courseset` - WHERE `set_id` <> ? AND `seminar_id` IN (?)", [$this->id, array_keys($this->courses)]); - // Store associated course IDs. - foreach ($this->courses as $course => $associated) { - $stmt = DBManager::get()->prepare("INSERT IGNORE INTO `seminar_courseset` - (`set_id`, `seminar_id`, `mkdate`) - VALUES (?, ?, ?)"); - $stmt->execute([$this->id, $course, time()]); - if ($stmt->rowCount()) { - StudipLog::log('SEM_CHANGED_ACCESS', $course, - null, 'Zuordnung zu Anmeldeset', sprintf('Anmeldeset: %s', $this->id)); - Course::buildExisting(['seminar_id' => $course])->triggerChdate(); - } - } - - // Delete removed user list assignments from database. - DBManager::get()->exec("DELETE FROM `courseset_factorlist` - WHERE `set_id`='".$this->id."' AND `factorlist_id` NOT IN ('". - implode("', '", array_keys($this->userlists))."')"); - // Store associated user list IDs. - foreach ($this->userlists as $list => $associated) { - $stmt = DBManager::get()->prepare("INSERT IGNORE INTO `courseset_factorlist` - (`set_id`, `factorlist_id`, `mkdate`) - VALUES (?, ?, ?)"); - $stmt->execute([$this->id, $list, time()]); - } - // Delete removed admission rules from database. - $stmt = DBManager::get()->query("SELECT `rule_id`, `type` FROM `courseset_rule` - WHERE `set_id`='".$this->id."' AND `rule_id` NOT IN ('". - implode("', '", array_keys($this->admissionRules))."')"); - $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($data as $ruleData) { - $rule = new $ruleData['type']($ruleData['rule_id']); - $rule->delete(); - } - // Store all rules. - foreach ($this->admissionRules as $rule) { - // Store each rule... - $rule->store(); - // ... and its connection to the current course set. - $stmt = DBManager::get()->prepare("INSERT INTO `courseset_rule` - (`set_id`, `rule_id`, `type`, `mkdate`) - VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE - `type`=VALUES(`type`)"); - $stmt->execute([$this->id, $rule->getId(), get_class($rule), time()]); - } - //fix free access courses - if (count($this->courses)) { - DBManager::get()->execute("UPDATE seminare SET Lesezugriff=1,Schreibzugriff=1 WHERE seminar_id IN(?)", [array_keys($this->courses)]); - } - //create general log - $this->log_store(); - - } - - /** - * Generates a general log entry if the CourseSet rules were changed. - */ - private function log_store() - { - $text = ''; - $rule_counter = 1; - foreach ($this->getAdmissionRules() as $rule) { - $rule_text = strip_tags(kill_format($rule->toString())); - $semicolon = ($rule_counter < count($this->getAdmissionRules()) ? '; ' : ''); - $text .= '#' . $rule_counter . ' => ' . $rule_text . $semicolon; - $rule_counter++; - } - - $courses = $this->getCourses(); - foreach ($courses as $course_id) { - StudipLog::log( - 'SEM_CHANGED_ACCESS', - $course_id, - NULL, - $text, - sprintf('Anmeldeset: %s (%s)', strip_tags(kill_format(($this->name))), $this->id) - ); - } - } - - /** - * A textual description of the current rule. - * - * @param bool short Show only short info without overview of assigned - * courses and institutes. - * @return String - */ - public function toString($short=false) { - $tpl = $GLOBALS['template_factory']->open('admission/courseset/info'); - $tpl->set_attribute('courseset', $this); - $tpl->set_attribute('is_limited', false); - $institutes = []; - if (!$short) { - $institutes = Institute::findAndMapMany(function($i) {return $i->name;}, array_keys($this->institutes), 'ORDER BY Name'); - $tpl->set_attribute('institutes', $institutes); - } - if (!$short || $this->hasAdmissionRule('LimitedAdmission')) { - $courses = Course::findAndMapMany(function($c) { - return [ - 'id' => $c->id, - 'name' => $c->getFullName('number-name-semester'), - 'visible' => $c->visible - ]; - }, - array_keys($this->courses), - 'ORDER BY start_time,VeranstaltungsNummer,Name'); - if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { - $courses = array_filter($courses, - function ($c) { - return $c['visible']; - } - ); - } - $tpl->set_attribute('is_limited', $this->hasAdmissionRule('LimitedAdmission')); - $tpl->set_attribute('courses', $courses); - } - $tpl->set_attribute('short', $short); - return $tpl->render(); - } - - public function __toString() { - return $this->toString(); - } - - /** - * is user with given user id allowed to assign/unassign given course to courseset - * - * @param string $user_id - * @param string $course_id - * @return boolean - */ - public function isUserAllowedToAssignCourse($user_id, $course_id) - { - global $perm; - $is_dozent = $perm->have_studip_perm('tutor', $course_id, $user_id); - $is_admin = $perm->have_perm('admin', $user_id) || ($perm->have_perm('dozent', $user_id) && Config::get()->ALLOW_DOZENT_COURSESET_ADMIN); - $is_private = $this->getPrivate(); - $is_my_own = $this->getUserId() == $user_id; - $is_correct_institute = $this->isGlobal() || isset($this->institutes[Course::find($course_id)->institut_id]); - return $is_dozent && $is_correct_institute && ($is_my_own || $is_admin || !$is_private || $this->isGlobal()) || $perm->have_perm('root', $user_id); - } - - /** - * is user with given user id allowed to edit or delete the courseset - * - * @param string $user_id - * @return boolean - */ - public function isUserAllowedToEdit($user_id) - { - global $perm; - - if ($this->getUserId() == '') { - return false; - } - if ($perm->have_perm('root', $user_id) || $this->getUserId() == $user_id) { - return true; - } - if (count($this->institutes) == 0 && count($this->courses) == 1 && $perm->have_studip_perm('tutor', current($this->getCourses()), $user_id)) { - return true; - } - if ($perm->have_perm('admin', $user_id) || ($perm->have_perm('dozent', $user_id) && Config::get()->ALLOW_DOZENT_COURSESET_ADMIN)) { - foreach (array_keys($this->getInstituteIds()) as $one) { - if ($perm->have_studip_perm('dozent', $one, $user_id)) { - return true; - } - } - } - return false; - } - - /** - * checks if given rule is allowed to be added to current set of rules - * - * @param AdmissionRule|string $admission_rule - * @return boolean - */ - public function isAdmissionRuleAllowed($admission_rule) - { - foreach ($this->getAdmissionRules() as $one) { - if (!$one->isCombinationAllowed($admission_rule)) { - return false; - } - } - return true; - } - - public static function getGlobalLockedAdmissionSetId() - { - $db = DBManager::get(); - $locked_set_id = $db->fetchColumn("SELECT cr.set_id FROM courseset_rule cr - INNER JOIN coursesets USING(set_id) - WHERE type='LockedAdmission' and private=1 and user_id='' LIMIT 1"); - if (!$locked_set_id) { - $cs_insert = $db->prepare("INSERT INTO coursesets (set_id,user_id,name,infotext,algorithm,private,mkdate,chdate) - VALUES (?,?,?,?,'',?,?,?)"); - $cs_r_insert = $db->prepare("INSERT INTO courseset_rule (set_id,rule_id,type,mkdate) VALUES (?,?,?,UNIX_TIMESTAMP())"); - $locked_insert = $db->prepare("INSERT INTO lockedadmissions (rule_id,message,mkdate,chdate) VALUES (?,'Die Anmeldung ist gesperrt',UNIX_TIMESTAMP(),UNIX_TIMESTAMP())"); - $locked_set_id = md5(uniqid('coursesets',1)); - $name = 'Anmeldung gesperrt (global)'; - $cs_insert->execute([$locked_set_id,'',$name,'',1,time(),time()]); - $locked_rule_id = md5(uniqid('lockedadmissions',1)); - $locked_insert->execute([$locked_rule_id]); - $cs_r_insert->execute([$locked_set_id,$locked_rule_id,'LockedAdmission']); - } - return $locked_set_id; - } - - public static function addCourseToSet($set_id, $course_id) - { - $db = DBManager::get(); - $ok = $db->execute("INSERT IGNORE INTO seminar_courseset (set_id,seminar_id,mkdate) VALUES (?,?,UNIX_TIMESTAMP())", [$set_id, $course_id]); - if ($ok) { - StudipLog::log('SEM_CHANGED_ACCESS', $course_id, - null, 'Zuordnung zu Anmeldeset', sprintf('Anmeldeset: %s', $set_id)); - $course = Course::find($course_id); - if ($course) { - $course->chdate = time(); - $course->lesezugriff = 1; - $course->schreibzugriff = 1; - $course->store(); - } - } - return $ok; - } - - public static function removeCourseFromSet($set_id, $course_id) - { - $db = DBManager::get(); - $ok = $db->execute("DELETE FROM seminar_courseset WHERE set_id=? AND seminar_id=? LIMIT 1", [$set_id, $course_id]); - if ($ok) { - StudipLog::log('SEM_CHANGED_ACCESS', $course_id, - null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $set_id)); - //Delete priorities - AdmissionPriority::unsetAllPrioritiesForCourse($course_id); - Course::buildExisting(['seminar_id' => $course_id])->triggerChdate(); - } - return $ok; - } - - public function __clone() - { - $this->courses = []; - $this->id = null; - $this->user_id = null; - $cloned_rules = []; - foreach ($this->admissionRules as $key => $rule) { - $dolly = clone $rule; - $cloned_rules[$dolly->id] = $dolly; - } - $this->admissionRules = $cloned_rules; - } - -} /* end of class CourseSet */ diff --git a/lib/classes/admission/CourseSet.php b/lib/classes/admission/CourseSet.php new file mode 100644 index 0000000..d94bc05 --- /dev/null +++ b/lib/classes/admission/CourseSet.php @@ -0,0 +1,1185 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class CourseSet +{ + // --- ATTRIBUTES --- + + /** + * Admission rules that are applied to the courses belonging to this set. + */ + protected $admissionRules = []; + + /** + * Seat distribution algorithm. + */ + protected $algorithm = null; + + /** + * IDs of courses that are aggregated into this set. The array is in the + * form ($courseId1 => true, $courseId2 => true). + */ + protected $courses = []; + + /** + * Has the seat distribution algorithm already been executed? + */ + protected $hasAlgorithmRun = false; + + /** + * Unique identifier for this set. + */ + protected $id = ''; + + /** + * Some extensive descriptional text for informing confused students. + */ + protected $infoText = ''; + + /** + * Which Stud.IP institute does the course set belong to? + */ + protected $institutes = []; + + /** + * Some display name for this course set. + */ + protected $name = ''; + + /** + * Is the course set only visible for the creator? + */ + protected $private = false; + + /** + * Who owns this course set? + */ + protected $user_id = false; + + /** + * When was the course set changed? + */ + protected $chdate = null; + + /* + * Lists of users who are treated differently on seat distribution + */ + protected $userlists = []; + + // --- OPERATIONS --- + + public function __construct($setId='') { + $this->id = $setId; + $this->name = _("Anmeldeset"); + $this->algorithm = new RandomAlgorithm(); + // Autoload admission rules. + AdmissionRule::getAvailableAdmissionRules(); + // Define autoload function for admission rules. + spl_autoload_register(['UserFilterField', 'getAvailableFilterFields']); + if ($setId) { + $this->load(); + } + } + + /** + * Adds the given admission rule to the list of rules for the course set. + * + * @param AdmissionRule rule + * @return CourseSet + */ + public function addAdmissionRule($rule) + { + $this->admissionRules[$rule->getId()] = $rule; + return $this; + } + + /** + * Adds the course with the given ID to the course set. + * + * @param String courseId + * @return CourseSet + */ + public function addCourse($courseId) + { + $this->courses[$courseId] = true; + return $this; + } + + /** + * Adds a new institute ID. + * + * @param String newId + * @return CourseSet + */ + public function addInstitute($newId) { + $this->institutes[$newId] = true; + return $this; + } + + /** + * Adds several institute IDs to the existing institute assignments. + * + * @param Array newIds + * @return CourseSet + */ + public function addInstitutes($newIds) { + foreach ($newIds as $newId) { + $this->addInstitute($newId); + } + return $this; + } + + /** + * Adds a UserList to the course set. The list contains several users and a + * factor that changes seat distribution chances for these users; + * + * @param String listId + * @return CourseSet + */ + public function addUserList($listId) + { + $this->userlists[$listId] = true; + return $this; + } + + /** + * Is the given user allowed to register as participant in the given + * course according to the rules of this course set? + * + * @param String userId + * @param String courseId + * @return Array Optional error messages from rules if something went wrong. + */ + public function checkAdmission($userId, $courseId) { + $errors = []; + foreach ($this->admissionRules as $rule) { + // All rules must be fulfilled. + $ruleCheck = $rule->ruleApplies($userId, $courseId); + if ($ruleCheck) { + $errors = array_merge($errors, $ruleCheck); + } + } + return $errors; + } + + /** + * Removes all admission rules at once. + * + * @return CourseSet + */ + public function clearAdmissionRules() { + $this->admissionRules = []; + return $this; + } + + /** + * Deletes the course set and all associated data. + */ + public function delete() { + NotificationCenter::postNotification('CourseSetWillDelete', $this->id, $GLOBALS['user']->id); + // Delete institute associations. + $stmt = DBManager::get()->prepare("DELETE FROM `courseset_institute` + WHERE `set_id`=?"); + $stmt->execute([$this->id]); + // Delete course associations. + $stmt = DBManager::get()->prepare("DELETE FROM `seminar_courseset` + WHERE `set_id`=?"); + $stmt->execute([$this->id]); + // Delete all rules... + foreach ($this->admissionRules as $rule) { + $rule->delete(); + } + // ... and their association to the current course set. + $stmt = DBManager::get()->prepare("DELETE FROM `courseset_rule` + WHERE `set_id`=?"); + $stmt->execute([$this->id]); + // Delete associations to user lists. + $stmt = DBManager::get()->prepare("DELETE FROM `courseset_factorlist` + WHERE `set_id`=?"); + $stmt->execute([$this->id]); + // Delete course set data. + $stmt = DBManager::get()->prepare("DELETE FROM `coursesets` + WHERE `set_id`=?"); + $stmt->execute([$this->id]); + /* + * Delete waiting lists + */ + foreach ($this->courses as $id => $assigned) { + AdmissionApplication::deleteBySQL("status='awaiting' AND seminar_id=?", [$id]); + } + //Delete priorities + AdmissionPriority::unsetAllPriorities($this->getId()); + NotificationCenter::postNotification('CourseSetDidDelete', $this->id, $GLOBALS['user']->id); + } + + /** + * Starts the seat distribution algorithm. + */ + public function distributeSeats() { + if ($this->algorithm) { + // Call pre-distribution hooks on all assigned rules. + foreach ($this->admissionRules as &$rule) { + $rule->beforeSeatDistribution($this); + } + $this->algorithm->run($this); + // Mark as "seats distributed". + $this->setAlgorithmRun(true); + // Call post-distribution hooks on all assigned rules. + foreach ($this->admissionRules as &$rule) { + $rule->afterSeatDistribution($this); + } + AdmissionPriority::unsetAllPriorities($this->getId()); + } + } + + public function setAlgorithmRun($state) + { + NotificationCenter::postNotification('CourseSetAlgorithmWillStart', $state, $this->getId()); + $this->hasAlgorithmRun = (bool)$state; + $db = DBManager::get(); + $ok = $db->execute("UPDATE coursesets SET algorithm_run = ? WHERE set_id = ?", [$this->hasAlgorithmRun, $this->getId()]); + if ($ok) { + NotificationCenter::postNotification('CourseSetAlgorithmDidStart', $state, $this->getId()); + } + return $ok; + } + + /** + * returns true if the set allows only a limited number of places + * + * @return boolean + */ + public function isSeatDistributionEnabled() + { + return $this->getSeatDistributionTime() !== null; + } + + /** + * returns timestamp of distribution time or null if no distribution time available + * may return 0 if first-come-first-serve is enabled + * + * @return integer|null timestamp of distribution + */ + public function getSeatDistributionTime() + { + $pr_admission = $this->getAdmissionRule('ParticipantRestrictedAdmission'); + if ($pr_admission) { + return $pr_admission->getDistributionTime(); + } + } + + /** + * Get all admission rules belonging to the course set. + * + * @return AdmissionRule[] + */ + public function getAdmissionRules() + { + return $this->admissionRules; + } + + public function getAdmissionRule($class_name) + { + $result = array_filter($this->getAdmissionRules(), function($r) use ($class_name) { + return $r instanceof $class_name;} + ); + return array_pop($result); + } + /** + * check if course set has given admission rule + * + * @param string $rule name of AdmissionRule class + * @return boolean + */ + public function hasAdmissionRule($rule) + { + return is_object($this->getAdmissionRule($rule)); + } + + /** + * Get the currently used distribution algorithm. + * + * @return AdmissionAlgorithm + */ + public function getAlgorithm() + { + return $this->algorithm; + } + + /** + * How many users will be allowed to register according to the defined + * rules? This can help in estimating whether the combination of the + * defined rules makes sense. + * + * @return int + */ + public function getAllowedUserCount() + { + $users = []; + foreach ($this->admissionRules as $rule) { + $users = array_merge($users, $rule->getAffectedUsers()); + } + return sizeof($users); + } + + /** + * Gets the course IDs belonging to the course set. + * + * @return Array + */ + public function getCourses() + { + return array_keys($this->courses); + } + + /** + * Gets all courses belonging to the given course set ID. + * + * @param String $courseSetId + * @return Array + */ + public static function getCoursesByCourseSetId($courseSetId) + { + $query = "SELECT `seminar_id` + FROM `seminar_courseset` + WHERE `set_id` = ?"; + $stmt = DBManager::get()->prepare($query); + $stmt->execute([$courseSetId]); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Gets all course sets belonging to the given institute ID. + * + * @param String $instituteId + * @return Array + */ + public static function getCoursesetsByInstituteId($instituteId, $filter = []) { + $query = "SELECT DISTINCT ci.* + FROM `courseset_institute` ci + JOIN `coursesets` c ON (ci.`set_id`=c.`set_id`) + LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id + LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id + LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id + WHERE ci.`institute_id`=?"; + $parameters = [$instituteId]; + if (!$GLOBALS['perm']->have_perm('admin')) { + $query .= " AND (c.`private`=0 OR c.`user_id`=?)"; + $parameters[] = $GLOBALS['user']->id; + } + if (!empty($filter['course_set_name'])) { + $query .= " AND c.name LIKE ?"; + $parameters[] = $filter['course_set_name'] . '%'; + } + if (!empty($filter['rule_types']) && is_array($filter['rule_types']) && count($filter['rule_types'])) { + $query .= " AND cr.type IN (?)"; + $parameters[] = $filter['rule_types']; + } + if (!empty($filter['semester_id'])) { + $query .= " AND s.start_time = ?"; + $parameters[] = Semester::find($filter['semester_id'])->beginn; + } + if (!empty($filter['course_set_chdate'])) { + $query .= " AND c.chdate > ?"; + $parameters[] = $filter['course_set_chdate']; + } + $query .= " ORDER BY c.name"; + $stmt = DBManager::get()->prepare($query); + $stmt->execute($parameters); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Gets all global course sets + * + * @param array $filter + * @return Array + */ + public static function getGlobalCoursesets($filter = []) { + $query = "SELECT DISTINCT c.set_id + FROM coursesets c + LEFT JOIN courseset_institute ci ON ci.`set_id`=c.`set_id` + LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id + LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id + LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id + WHERE ci.institute_id IS NULL"; + $parameters = []; + $query .= " AND (c.`private`=0 OR c.`user_id`=?)"; + $parameters[] = $GLOBALS['user']->id; + if (!empty($filter['course_set_name'])) { + $query .= " AND c.name LIKE ?"; + $parameters[] = $filter['course_set_name'] . '%'; + } + if (!empty($filter['rule_types']) && is_array($filter['rule_types']) && count($filter['rule_types'])) { + $query .= " AND cr.type IN (?)"; + $parameters[] = $filter['rule_types']; + } + if (!empty($filter['semester_id'])) { + $query .= " AND s.start_time = ?"; + $parameters[] = Semester::find($filter['semester_id'])->beginn; + } + if (!empty($filter['course_set_chdate'])) { + $query .= " AND c.chdate > ?"; + $parameters[] = $filter['course_set_chdate']; + } + $query .= " ORDER BY c.name"; + $stmt = DBManager::get()->prepare($query); + $stmt->execute($parameters); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Get the identifier of the course set. + * + * @return String + */ + public function getId() + { + return $this->id; + } + + /** + * Get the course set's info text. + * + * @return String + */ + public function getInfoText() + { + return $this->infoText; + } + + /** + * Which institutes does the rule belong to? + * + * @return Array + */ + public function getInstituteIds() { + return $this->institutes; + } + + public function isGlobal() + { + return count($this->institutes) == 0; + } + /** + * Gets this course set's display name. + */ + public function getName() { + return $this->name; + } + + /** + * Returns the date of the last change. + * @return int + */ + public function getChdate() + { + return $this->chdate; + } + + /** + * Retrieves the priorities given to the courses in this set. + * + * @return Array + */ + public function getPriorities() + { + return AdmissionPriority::getPriorities($this->id); + } + + public function getNumApplicants() + { + return AdmissionPriority::getPrioritiesCount($this->id); + } + + /** + * Is the current courseset private? + * + * @return bool + */ + public function getPrivate() { + return $this->private; + } + + + /** + * returns latest semester id from assigned courses + * if no courses are assigned current semester + * + * @return string id of semester + */ + public function getSemester() { + $db = DBManager::get(); + $data = $db->fetchOne(" + SELECT semester_data.* + FROM semester_data + INNER JOIN semester_courses ON (semester_data.semester_id = semester_courses.semester_id) + WHERE semester_courses.course_id IN (?) + ORDER BY semester_data.ende DESC + LIMIT 1 + ", [$this->getCourses()]); + if ($data) { + $semester = Semester::buildExisting($data); + } else { + $semester = $_SESSION['_default_sem'] ? Semester::find($_SESSION['_default_sem']) : Semester::findCurrent(); + } + return $semester->id; + } + + /** + * Gets the owner of this course set. + */ + public function getUserId() { + return $this->user_id; + } + + public function setUserId($user_id) { + $this->user_id = $user_id; + return $this; + } + + /** + * Gets the course sets the given course belongs to. + * + * @param String courseId + * @return CourseSet + */ + public static function getSetForCourse($courseId) + { + $stmt = DBManager::get()->prepare("SELECT `set_id` + FROM `seminar_courseset` WHERE `seminar_id`=?"); + $stmt->execute([$courseId]); + $set_id = $stmt->fetchColumn(); + if ($set_id) { + return new CourseSet($set_id); + } + return null; + } + + /** + * Gets the course sets the given rule belongs to. + * + * @param String $rule_id + * @return CourseSet + */ + public static function getSetForRule($rule_id) + { + $set_id = DBManager::get()->fetchColumn("SELECT `set_id` + FROM `courseset_rule` WHERE `rule_id`=?", [$rule_id]); + if ($set_id) { + return new CourseSet($set_id); + } + return null; + } + + /** + * Retrieves the lists of users that are considered specially in + * seat distribution. + * + * @return Array + */ + public function getUserLists() + { + return array_keys($this->userlists); + } + + public function getUserFactorList() + { + $factored_users = []; + + foreach ($this->getUserLists() as $ul_id) { + $user_list = new AdmissionUserList($ul_id); + + // Iterate through user list. + foreach ($user_list->getUsers() as $user => $assigned) { + switch ($user_list->getFactor()) { + // Maximum factor, just set it and stop further processing of user lists. + case PHP_INT_MAX: + $factored_users[$user] = PHP_INT_MAX; + break; + // Backlist, set malus and stop further processing of user lists. + case 0: + $factored_users[$user] = 0; + break; + default: + // Add up current bonus if it isn't already at 0 or PHP_INT_MAX. + if ($factored_users[$user] !== 0 && $factored_users[$user] != PHP_INT_MAX) { + $factored_users[$user] = isset($factored_users[$user]) ? + $factored_users[$user] + $user_list->getFactor() : + $user_list->getFactor(); + } + } + } + } + + return $factored_users; + } + + /** + * Evaluates whether the seat distribution algorithm has already been + * executed on this course set. + * + * @return boolean True if algorithm has already run, otherwise false. + */ + public function hasAlgorithmRun() { + return $this->hasAlgorithmRun; + } + + /** + * Helper function for loading data from DB. + */ + public function load() { + // Load basic data. + $stmt = DBManager::get()->prepare( + "SELECT * FROM `coursesets` WHERE set_id=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->name = $data['name']; + $this->infoText = $data['infotext']; + $this->hasAlgorithmRun = (bool)$data['algorithm_run']; + if ($data['algorithm']) { + if (class_exists($data['algorithm'])) { + $this->algorithm = new $data['algorithm'](); + } + } + $this->private = (bool) $data['private']; + $this->user_id = $data['user_id']; + $this->chdate = $data['chdate']; + } + // Load institute assigments. + $stmt = DBManager::get()->prepare(" + SELECT courseset_institute.institute_id + FROM `courseset_institute` + INNER JOIN Institute ON (courseset_institute.institute_id = Institute.Institut_id) + WHERE courseset_institute.set_id = ? + ORDER BY Institute.Name ASC + "); + $stmt->execute([$this->id]); + $this->institutes = []; + while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->institutes[$data['institute_id']] = true; + } + // Load courses. + $stmt = DBManager::get()->prepare( + "SELECT seminar_id FROM `seminar_courseset` WHERE set_id=?"); + $stmt->execute([$this->id]); + $this->courses = []; + while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->courses[$data['seminar_id']] = true; + } + // Load admission rules. + $stmt = DBManager::get()->prepare( + "SELECT * FROM `courseset_rule` WHERE set_id=?"); + $stmt->execute([$this->id]); + $this->admissionRules = []; + while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + if (class_exists($data['type'])) { + $this->admissionRules[$data['rule_id']] = + new $data['type']($data['rule_id'], $this->id); + } + } + // Load assigned user lists. + $stmt = DBManager::get()->prepare("SELECT `factorlist_id` + FROM `courseset_factorlist` WHERE `set_id`=?"); + $stmt->execute([$this->id]); + $this->userlists = []; + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->userlists[$current['factorlist_id']] = true; + } + return $this; + } + + /** + * Removes the course with the given ID from the set. + * + * @param String courseId + * @return CourseSet + */ + public function removeCourse($courseId) + { + unset($this->courses[$courseId]); + return $this; + } + + /** + * Removes the rule with the given ID from the set. + * + * @param String ruleId + * @return CourseSet + */ + public function removeAdmissionRule($ruleId) + { + unset($this->admissionRules[$ruleId]); + return $this; + } + + /** + * Removes the institute with the given ID from the set. + * + * @param String instituteId + * @return CourseSet + */ + public function removeInstitute($instituteId) + { + unset($this->institutes[$instituteId]); + return $this; + } + + /** + * Removes the user list with the given ID from the set. + * + * @param String listId + * @return CourseSet + */ + public function removeUserlist($listId) + { + unset($this->userlists[$listId]); + return $this; + } + + /** + * Adds several admission rules after clearing the existing rule + * assignments. + * + * @param Array newIds + * @return CourseSet + */ + public function setAdmissionRules($newRules) { + $this->admissionRules = []; + foreach ($newRules as $newRule) { + $rule = ObjectBuilder::build($newRule, 'AdmissionRule'); + $this->addAdmissionRule($rule); + } + return $this; + } + + /** + * Sets a seat distribution algorithm for this course set. This will only + * have an effect in conjunction with a TimedAdmission, as the algorithm + * needs a defined point in time where it will start. + * + * @param String newAlgorithm + * @return CourseSet + */ + public function setAlgorithm($newAlgorithm) + { + try { + $this->algorithm = new $newAlgorithm(); + } catch (Exception $e) { + } + return $this; + } + + /** + * Adds several course IDs after clearing the existing course + * assignments. + * + * @param Array newIds + * @return CourseSet + */ + public function setCourses($newIds) { + $this->courses = array_fill_keys($newIds, true); + return $this; + } + + /** + * Adds several institute IDs after clearing the existing institute + * assignments. + * + * @param Array newIds + * @return CourseSet + */ + public function setInstitutes($newIds) { + $this->institutes = []; + $this->addInstitutes($newIds); + return $this; + } + + /** + * Set the course set's info text. + * + * @return CourseSet + */ + public function setInfoText($newText) + { + $this->infoText = $newText; + return $this; + } + + /* Sets a new name for this course set. + * + * @param String newName + * @return CourseSet + */ + public function setName($newName) { + $this->name = $newName; + return $this; + } + + /** + * Set a new value for courseset privacy. + * + * @param bool $newPrivate + * @return CourseSet + */ + public function setPrivate($newPrivate) { + $this->private = $newPrivate; + return $this; + } + + /** + * Adds several user list IDs after clearing the existing user list + * assignments. + * + * @param Array newIds + * @return CourseSet + */ + public function setUserlists($newIds) { + $this->userlists = []; + foreach ($newIds as $newId) { + $this->addUserlist($newId); + } + return $this; + } + + public function store() { + // Generate new ID if course set doesn't exist in DB yet. + if (!$this->id) { + do { + $newid = md5(uniqid(get_class($this), true)); + $db = DBManager::get()->query("SELECT `set_id` + FROM `coursesets` WHERE `set_id`='$newid'"); + } while ($db->fetch()); + $this->id = $newid; + } + if (!$this->user_id) { + $this->user_id = $GLOBALS['user']->id; + } + if ($this->isSeatDistributionEnabled()) { + if (!$this->getAlgorithm()) { + $algorithm = new RandomAlgorithm(); + $this->setAlgorithm($algorithm); + } + if (!$this->getSeatDistributionTime()) { + $this->setAlgorithmRun(true); + //Delete priorities + AdmissionPriority::unsetAllPriorities($this->getId()); + } + if ($this->getSeatDistributionTime() > time()) { + $this->setAlgorithmRun(false); + } + } + // Store basic data. + $stmt = DBManager::get()->prepare("INSERT INTO `coursesets` + (`set_id`, `user_id`, `name`, `infotext`, `algorithm`, `algorithm_run`, + `private`, `mkdate`, `chdate`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `name`=VALUES(`name`), `infotext`=VALUES(`infotext`), + `algorithm`=VALUES(`algorithm`), `algorithm_run`=VALUES(`algorithm_run`), `private`=VALUES(`private`), + `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->user_id, $this->name, $this->infoText, + get_class($this->algorithm), $this->hasAlgorithmRun(), intval($this->private), time(), time()]); + // Delete removed institute assignments from database. + DBManager::get()->exec("DELETE FROM `courseset_institute` + WHERE `set_id`='".$this->id."' AND `institute_id` NOT IN ('". + implode("', '", array_keys($this->institutes))."')"); + // Store associated institute IDs. + foreach ($this->institutes as $institute => $associated) { + $stmt = DBManager::get()->prepare("INSERT IGNORE INTO `courseset_institute` + (`set_id`, `institute_id`, `mkdate`) + VALUES (?, ?, ?)"); + $stmt->execute([$this->id, $institute, time()]); + } + // log removed course assignments. + DBManager::get()->fetchAll("SELECT seminar_id,set_id FROM `seminar_courseset` + WHERE `set_id` = ? AND `seminar_id` NOT IN (?)", [$this->id, count($this->courses) ? array_keys($this->courses) : ''], + function ($row) { + StudipLog::log('SEM_CHANGED_ACCESS', $row['seminar_id'], + null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $row['set_id'])); + //Delete priorities + AdmissionPriority::unsetAllPrioritiesForCourse($row['seminar_id']); + Course::buildExisting(['seminar_id' => $row['seminar_id']])->triggerChdate(); + }); + //removed course assignments + DBManager::get()->execute("DELETE FROM `seminar_courseset` + WHERE `set_id` = ? AND `seminar_id` NOT IN (?)", [$this->id, count($this->courses) ? array_keys($this->courses) : '']); + //log removing other associations + DBManager::get()->execute("SELECT seminar_id,set_id FROM `seminar_courseset` + WHERE `set_id` <> ? AND `seminar_id` IN (?)", [$this->id, array_keys($this->courses)], + function ($row) { + StudipLog::log('SEM_CHANGED_ACCESS', $row['seminar_id'], + null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $row['set_id'])); + //Delete priorities + AdmissionPriority::unsetAllPrioritiesForCourse($row['seminar_id']); + Course::buildExisting(['seminar_id' => $row['seminar_id']])->triggerChdate(); + }); + //Delete other associations, only one set possible + DBManager::get()->execute("DELETE FROM `seminar_courseset` + WHERE `set_id` <> ? AND `seminar_id` IN (?)", [$this->id, array_keys($this->courses)]); + // Store associated course IDs. + foreach ($this->courses as $course => $associated) { + $stmt = DBManager::get()->prepare("INSERT IGNORE INTO `seminar_courseset` + (`set_id`, `seminar_id`, `mkdate`) + VALUES (?, ?, ?)"); + $stmt->execute([$this->id, $course, time()]); + if ($stmt->rowCount()) { + StudipLog::log('SEM_CHANGED_ACCESS', $course, + null, 'Zuordnung zu Anmeldeset', sprintf('Anmeldeset: %s', $this->id)); + Course::buildExisting(['seminar_id' => $course])->triggerChdate(); + } + } + + // Delete removed user list assignments from database. + DBManager::get()->exec("DELETE FROM `courseset_factorlist` + WHERE `set_id`='".$this->id."' AND `factorlist_id` NOT IN ('". + implode("', '", array_keys($this->userlists))."')"); + // Store associated user list IDs. + foreach ($this->userlists as $list => $associated) { + $stmt = DBManager::get()->prepare("INSERT IGNORE INTO `courseset_factorlist` + (`set_id`, `factorlist_id`, `mkdate`) + VALUES (?, ?, ?)"); + $stmt->execute([$this->id, $list, time()]); + } + // Delete removed admission rules from database. + $stmt = DBManager::get()->query("SELECT `rule_id`, `type` FROM `courseset_rule` + WHERE `set_id`='".$this->id."' AND `rule_id` NOT IN ('". + implode("', '", array_keys($this->admissionRules))."')"); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($data as $ruleData) { + $rule = new $ruleData['type']($ruleData['rule_id']); + $rule->delete(); + } + // Store all rules. + foreach ($this->admissionRules as $rule) { + // Store each rule... + $rule->store(); + // ... and its connection to the current course set. + $stmt = DBManager::get()->prepare("INSERT INTO `courseset_rule` + (`set_id`, `rule_id`, `type`, `mkdate`) + VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE + `type`=VALUES(`type`)"); + $stmt->execute([$this->id, $rule->getId(), get_class($rule), time()]); + } + //fix free access courses + if (count($this->courses)) { + DBManager::get()->execute("UPDATE seminare SET Lesezugriff=1,Schreibzugriff=1 WHERE seminar_id IN(?)", [array_keys($this->courses)]); + } + //create general log + $this->log_store(); + + } + + /** + * Generates a general log entry if the CourseSet rules were changed. + */ + private function log_store() + { + $text = ''; + $rule_counter = 1; + foreach ($this->getAdmissionRules() as $rule) { + $rule_text = strip_tags(kill_format($rule->toString())); + $semicolon = ($rule_counter < count($this->getAdmissionRules()) ? '; ' : ''); + $text .= '#' . $rule_counter . ' => ' . $rule_text . $semicolon; + $rule_counter++; + } + + $courses = $this->getCourses(); + foreach ($courses as $course_id) { + StudipLog::log( + 'SEM_CHANGED_ACCESS', + $course_id, + NULL, + $text, + sprintf('Anmeldeset: %s (%s)', strip_tags(kill_format(($this->name))), $this->id) + ); + } + } + + /** + * A textual description of the current rule. + * + * @param bool short Show only short info without overview of assigned + * courses and institutes. + * @return String + */ + public function toString($short=false) { + $tpl = $GLOBALS['template_factory']->open('admission/courseset/info'); + $tpl->set_attribute('courseset', $this); + $tpl->set_attribute('is_limited', false); + $institutes = []; + if (!$short) { + $institutes = Institute::findAndMapMany(function($i) {return $i->name;}, array_keys($this->institutes), 'ORDER BY Name'); + $tpl->set_attribute('institutes', $institutes); + } + if (!$short || $this->hasAdmissionRule('LimitedAdmission')) { + $courses = Course::findAndMapMany(function($c) { + return [ + 'id' => $c->id, + 'name' => $c->getFullName('number-name-semester'), + 'visible' => $c->visible + ]; + }, + array_keys($this->courses), + 'ORDER BY start_time,VeranstaltungsNummer,Name'); + if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { + $courses = array_filter($courses, + function ($c) { + return $c['visible']; + } + ); + } + $tpl->set_attribute('is_limited', $this->hasAdmissionRule('LimitedAdmission')); + $tpl->set_attribute('courses', $courses); + } + $tpl->set_attribute('short', $short); + return $tpl->render(); + } + + public function __toString() { + return $this->toString(); + } + + /** + * is user with given user id allowed to assign/unassign given course to courseset + * + * @param string $user_id + * @param string $course_id + * @return boolean + */ + public function isUserAllowedToAssignCourse($user_id, $course_id) + { + global $perm; + $is_dozent = $perm->have_studip_perm('tutor', $course_id, $user_id); + $is_admin = $perm->have_perm('admin', $user_id) || ($perm->have_perm('dozent', $user_id) && Config::get()->ALLOW_DOZENT_COURSESET_ADMIN); + $is_private = $this->getPrivate(); + $is_my_own = $this->getUserId() == $user_id; + $is_correct_institute = $this->isGlobal() || isset($this->institutes[Course::find($course_id)->institut_id]); + return $is_dozent && $is_correct_institute && ($is_my_own || $is_admin || !$is_private || $this->isGlobal()) || $perm->have_perm('root', $user_id); + } + + /** + * is user with given user id allowed to edit or delete the courseset + * + * @param string $user_id + * @return boolean + */ + public function isUserAllowedToEdit($user_id) + { + global $perm; + + if ($this->getUserId() == '') { + return false; + } + if ($perm->have_perm('root', $user_id) || $this->getUserId() == $user_id) { + return true; + } + if (count($this->institutes) == 0 && count($this->courses) == 1 && $perm->have_studip_perm('tutor', current($this->getCourses()), $user_id)) { + return true; + } + if ($perm->have_perm('admin', $user_id) || ($perm->have_perm('dozent', $user_id) && Config::get()->ALLOW_DOZENT_COURSESET_ADMIN)) { + foreach (array_keys($this->getInstituteIds()) as $one) { + if ($perm->have_studip_perm('dozent', $one, $user_id)) { + return true; + } + } + } + return false; + } + + /** + * checks if given rule is allowed to be added to current set of rules + * + * @param AdmissionRule|string $admission_rule + * @return boolean + */ + public function isAdmissionRuleAllowed($admission_rule) + { + foreach ($this->getAdmissionRules() as $one) { + if (!$one->isCombinationAllowed($admission_rule)) { + return false; + } + } + return true; + } + + public static function getGlobalLockedAdmissionSetId() + { + $db = DBManager::get(); + $locked_set_id = $db->fetchColumn("SELECT cr.set_id FROM courseset_rule cr + INNER JOIN coursesets USING(set_id) + WHERE type='LockedAdmission' and private=1 and user_id='' LIMIT 1"); + if (!$locked_set_id) { + $cs_insert = $db->prepare("INSERT INTO coursesets (set_id,user_id,name,infotext,algorithm,private,mkdate,chdate) + VALUES (?,?,?,?,'',?,?,?)"); + $cs_r_insert = $db->prepare("INSERT INTO courseset_rule (set_id,rule_id,type,mkdate) VALUES (?,?,?,UNIX_TIMESTAMP())"); + $locked_insert = $db->prepare("INSERT INTO lockedadmissions (rule_id,message,mkdate,chdate) VALUES (?,'Die Anmeldung ist gesperrt',UNIX_TIMESTAMP(),UNIX_TIMESTAMP())"); + $locked_set_id = md5(uniqid('coursesets',1)); + $name = 'Anmeldung gesperrt (global)'; + $cs_insert->execute([$locked_set_id,'',$name,'',1,time(),time()]); + $locked_rule_id = md5(uniqid('lockedadmissions',1)); + $locked_insert->execute([$locked_rule_id]); + $cs_r_insert->execute([$locked_set_id,$locked_rule_id,'LockedAdmission']); + } + return $locked_set_id; + } + + public static function addCourseToSet($set_id, $course_id) + { + $db = DBManager::get(); + $ok = $db->execute("INSERT IGNORE INTO seminar_courseset (set_id,seminar_id,mkdate) VALUES (?,?,UNIX_TIMESTAMP())", [$set_id, $course_id]); + if ($ok) { + StudipLog::log('SEM_CHANGED_ACCESS', $course_id, + null, 'Zuordnung zu Anmeldeset', sprintf('Anmeldeset: %s', $set_id)); + $course = Course::find($course_id); + if ($course) { + $course->chdate = time(); + $course->lesezugriff = 1; + $course->schreibzugriff = 1; + $course->store(); + } + } + return $ok; + } + + public static function removeCourseFromSet($set_id, $course_id) + { + $db = DBManager::get(); + $ok = $db->execute("DELETE FROM seminar_courseset WHERE set_id=? AND seminar_id=? LIMIT 1", [$set_id, $course_id]); + if ($ok) { + StudipLog::log('SEM_CHANGED_ACCESS', $course_id, + null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $set_id)); + //Delete priorities + AdmissionPriority::unsetAllPrioritiesForCourse($course_id); + Course::buildExisting(['seminar_id' => $course_id])->triggerChdate(); + } + return $ok; + } + + public function __clone() + { + $this->courses = []; + $this->id = null; + $this->user_id = null; + $cloned_rules = []; + foreach ($this->admissionRules as $key => $rule) { + $dolly = clone $rule; + $cloned_rules[$dolly->id] = $dolly; + } + $this->admissionRules = $cloned_rules; + } + +} /* end of class CourseSet */ diff --git a/lib/classes/admission/RandomAlgorithm.class.php b/lib/classes/admission/RandomAlgorithm.class.php deleted file mode 100644 index 54d44d7..0000000 --- a/lib/classes/admission/RandomAlgorithm.class.php +++ /dev/null @@ -1,437 +0,0 @@ - - * @author Thomas Hackl - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 3.0 - */ - -class RandomAlgorithm extends AdmissionAlgorithm -{ - - /** - * Runs the algorithm, thus distributing course seats. - * - * @param CourseSet $courseSet The course set containing the courses - * that seats shall be distributed for. - * @see CourseSet - */ - public function run($courseSet) - { - if ($courseSet->hasAdmissionRule('LimitedAdmission')) { - return $this->distributeByPriorities($courseSet); - } else { - return $this->distributeByCourses($courseSet); - } - } - - /** - * Distribute seats for several courses in a course set. - * No priorities are given. - * - * @param CourseSet $courseSet The course set containing the courses - * that seats shall be distributed for. - * @see CourseSet - */ - private function distributeByCourses($courseSet) - { - Log::debug('start seat distribution for course set: ' . $courseSet->getId()); - $groups_quota = []; - $conditional_rule_filter = array_filter($courseSet->getAdmissionRules(), function ($r) { - return $r instanceof ConditionalAdmission - && count($r->getConditionGroups()); - }); - $conditional_rule = array_pop($conditional_rule_filter); - $conditiongroups = $conditional_rule ? $conditional_rule->getConditionGroups() : []; - if (count($conditiongroups)) { - foreach (array_keys($conditiongroups) as $group_id) { - $groups_quota[$group_id] = $conditional_rule->getQuota($group_id); - } - } else { - $groups_quota[] = 100; - } - foreach ($courseSet->getCourses() as $course_id) { - $waiting_users = []; - $course = Course::find($course_id); - if (!$course) { - Log::debug(sprintf('course %s not found, continue', $course_id)); - continue; - } - $free_seats_course = $course->getFreeSeats(); - foreach ($groups_quota as $group_id => $quota) { - $claiming_users = AdmissionPriority::getPrioritiesByCourse($courseSet->getId(), $course->id); - if (isset($conditiongroups[$group_id])) { - Log::debug(sprintf('found conditiongroup %s with quota %s', $group_id, $quota)); - foreach(array_keys($claiming_users) as $user_id) { - $condition_ok = true; - foreach ($conditiongroups[$group_id] as $condition) { - if ($condition->isFulfilled($user_id)) { - $condition_ok = true; - break; - } - $condition_ok = false; - } - if (!$condition_ok) { - unset($claiming_users[$user_id]); - } - } - } - $factored_users = $courseSet->getUserFactorList(); - //apply bonus/malus to users, exclude participants - foreach(array_keys($claiming_users) as $user_id) { - if (!$course->getParticipantStatus($user_id)) { - $claiming_users[$user_id] = 1; - if (isset($factored_users[$user_id])) { - $claiming_users[$user_id] = - $factored_users[$user_id] == PHP_INT_MAX ? - PHP_INT_MAX : - $claiming_users[$user_id] * $factored_users[$user_id]; - } - Log::debug(sprintf('user %s gets factor %s', $user_id, $claiming_users[$user_id])); - } else { - unset($claiming_users[$user_id]); - Log::debug(sprintf('user %s is already %s, ignoring', $user_id, $course->getParticipantStatus($user_id))); - } - } - $free_seats = round($free_seats_course * $quota / 100, 0, PHP_ROUND_HALF_DOWN); - Log::debug(sprintf('distribute %s seats on %s claiming in course %s %s', $free_seats, count($claiming_users), $course->id, ($group_id ? 'conditiongroup ' . $group_id . ' quota ' . $quota : ''))); - $claiming_users = $this->rollTheDice($claiming_users); - Log::debug('the die is cast: ' . print_r($claiming_users,1)); - $chosen_ones = array_slice(array_keys($claiming_users),0 , $free_seats); - Log::debug('chosen ones: ' . print_r($chosen_ones,1)); - $this->addUsersToCourse($chosen_ones, $course); - foreach ($chosen_ones as $one) unset($waiting_users[$one]); - if ($free_seats < count($claiming_users)) { - $remaining_ones = array_slice(array_keys($claiming_users), $free_seats); - foreach ($remaining_ones as $one) { - $waiting_users[$one] = $claiming_users[$one]; - } - } - } - if (count($waiting_users)) { - $waiting_users = $this->rollTheDice($waiting_users); - if (!$course->admission_disable_waitlist) { - $free_seats_waitlist = $course->admission_waitlist_max ?: count($waiting_users); - $waiting_list_ones = array_slice(array_keys($waiting_users), 0, $free_seats_waitlist); - Log::debug('waiting list ones: ' . print_r($waiting_list_ones, 1)); - $this->addUsersToWaitlist($waiting_list_ones, $course); - } else { - $free_seats_waitlist = 0; - } - if ($free_seats_waitlist < count($waiting_users)) { - $remaining_ones = array_slice(array_keys($waiting_users),$free_seats_waitlist); - Log::debug('remaining ones: ' . print_r($remaining_ones, 1)); - $this->notifyRemainingUsers($remaining_ones, $course); - } - } - } - } - - /** - * Distribute seats for several courses in a course set using the given - * user priorities. - * - * @param CourseSet $courseSet The course set containing the courses - * that seats shall be distributed for. - * @see CourseSet - */ - private function distributeByPriorities($courseSet) - { - Log::debug('start seat distribution for course set: ' . $courseSet->getId()); - $limited_admission = $courseSet->getAdmissionRule('LimitedAdmission'); - //all users with their priorities - $claiming_users = AdmissionPriority::getPriorities($courseSet->getId()); - - //all users which have bonus/malus - $factored_users = $courseSet->getUserFactorList(); - - //all users with their max number of courses - $max_seats_users = array_combine(array_keys($claiming_users), - array_map(function($u) use ($limited_admission) {return $limited_admission->getMaxNumberForUser($u);}, - array_keys($claiming_users) - ) - ); - //unlucky users get a bonus for the next round - $bonus_users = []; - - //users / courses für later waitlist distribution - $waiting_users = []; - - //number of already distributed seats for users - $distributed_users = []; - - $prio_mapper = function ($users, $course_id) use ($claiming_users) { - $mapper = function ($u) use ($course_id) { - return isset($u[$course_id]) ? $u[$course_id] : null; - }; - return array_filter(array_map($mapper, array_intersect_key($claiming_users, array_flip($users)))); - }; - - $groups_quota = []; - $conditional_rule_filter = array_filter($courseSet->getAdmissionRules(), function ($r) { - return $r instanceof ConditionalAdmission - && count($r->getConditionGroups()); - }); - $conditional_rule = array_pop($conditional_rule_filter); - $conditiongroups = $conditional_rule ? $conditional_rule->getConditionGroups() : []; - if (count($conditiongroups)) { - foreach (array_keys($conditiongroups) as $group_id) { - $groups_quota[$group_id] = $conditional_rule->getQuota($group_id); - } - } else { - $groups_quota[] = 100; - } - - //sort courses by highest count of prio 1 applicants - $stats = AdmissionPriority::getPrioritiesStats($courseSet->getId()); - $courses = array_map(function ($a) {return $a['h'];},$stats); - arsort($courses, SORT_NUMERIC); - $id_courses = array_filter(array_keys($courses)); - $max_prio = AdmissionPriority::getPrioritiesMax($courseSet->getId()); - //count already manually distributed places - $distributed_users = $this->countParticipatingUsers($id_courses, array_keys($claiming_users)); - Log::debug('already distributed users: ' . print_r($distributed_users,1)); - //walk through all prios with all courses - foreach(range(1, $max_prio) as $current_prio) { - foreach ($id_courses as $course_id) { - $course = Course::find($course_id); - if (!$course) { - Log::debug(sprintf('course %s not found, continue', $course_id)); - continue; - } - $free_seats_course = $course->getFreeSeats(); - foreach ($groups_quota as $group_id => $quota) { - $current_claiming = []; - //find users with current prio for this course, if they still need a place - foreach ($claiming_users as $user_id => $prio_courses) { - $condition_ok = true; - if (isset($conditiongroups[$group_id])) { - Log::debug(sprintf('found conditiongroup %s with quota %s', $group_id, $quota)); - foreach ($conditiongroups[$group_id] as $condition) { - if ($condition->isFulfilled($user_id)) { - $condition_ok = true; - break; - } - $condition_ok = false; - } - } - - if ($condition_ok && $prio_courses[$course_id] == $current_prio - && $distributed_users[$user_id] < $max_seats_users[$user_id]) { - //exclude participants - if (!$course->getParticipantStatus($user_id)) { - $current_claiming[$user_id] = 1; - if (isset($factored_users[$user_id])) { - $current_claiming[$user_id] = - $factored_users[$user_id] == PHP_INT_MAX ? - PHP_INT_MAX : - $current_claiming[$user_id] * $factored_users[$user_id]; - } - } else { - Log::debug(sprintf('user %s is already %s in course %s, ignoring', $user_id, $course->getParticipantStatus($user_id), $course->id)); - } - } - } - //give maximum bonus to users which were unlucky before - foreach (array_keys($current_claiming) as $user_id) { - if ($bonus_users[$user_id] > 0) { - $current_claiming[$user_id] *= $bonus_users[$user_id] * count($current_claiming) + 1; - } - } - $free_seats = round($free_seats_course * $quota / 100, 0, PHP_ROUND_HALF_DOWN); - Log::debug(sprintf('distribute %s seats on %s claiming with prio %s in course %s %s', $free_seats, count($current_claiming),$current_prio, $course->id, ($group_id ? 'conditiongroup ' . $group_id . ' quota ' . $quota : ''))); - Log::debug('users to distribute: ' . print_r($current_claiming,1)); - $current_claiming = $this->rollTheDice($current_claiming); - Log::debug('the die is cast: ' . print_r($current_claiming,1)); - $chosen_ones = array_slice(array_keys($current_claiming),0 , $free_seats); - Log::debug('chosen ones: ' . print_r($chosen_ones,1)); - $this->addUsersToCourse($chosen_ones, $course, $prio_mapper($chosen_ones, $course->id)); - foreach ($chosen_ones as $one) { - $distributed_users[$one]++; - $bonus_users[$one]--; - } - if ($free_seats < count($current_claiming)) { - $remaining_ones = array_slice(array_keys($current_claiming), $free_seats); - foreach ($remaining_ones as $one) { - $bonus_users[$one]++; - $waiting_users[$current_prio][$course_id][] = $one; - } - } - } - } - } - //distribute to waitlists if applicable - Log::debug('waiting list: ' . print_r($waiting_users, 1)); - foreach ($waiting_users as $current_prio => $current_prio_waiting_courses) { - foreach ($current_prio_waiting_courses as $course_id => $users) { - $users = array_filter(array_unique($users), function($user_id) use ($distributed_users, $max_seats_users) { - return $distributed_users[$user_id] < $max_seats_users[$user_id];}); - $course = Course::find($course_id); - Log::debug(sprintf('distribute waitlist of %s with prio %s in course %s', count($users), $current_prio, $course->id)); - if (!$course->admission_disable_waitlist) { - if ($course->admission_waitlist_max) { - $free_seats_waitlist = $course->admission_waitlist_max - $course->getNumWaiting(); - $free_seats_waitlist = $free_seats_waitlist < 0 ? 0 : $free_seats_waitlist; - } else { - $free_seats_waitlist = count($users); - } - $waiting_list_ones = array_slice($users, 0, $free_seats_waitlist); - Log::debug('waiting list ones: ' . print_r($waiting_list_ones, 1)); - $this->addUsersToWaitlist($waiting_list_ones, $course, $prio_mapper($waiting_list_ones, $course->id)); - foreach ($waiting_list_ones as $one) { - $distributed_users[$one]++; - } - } else { - $free_seats_waitlist = 0; - } - if ($free_seats_waitlist < count($users)) { - $remaining_ones = array_slice($users, $free_seats_waitlist); - Log::debug('remaining ones: ' . print_r($remaining_ones, 1)); - $this->notifyRemainingUsers($remaining_ones, $course, $prio_mapper($remaining_ones, $course->id)); - } - } - } - } - - /** - * Notify users about the fact that they couldn't get a seat and the - * waiting list is disabled in a course. - * - * @param Array $user_list Users to be notified - * @param Course $course The course without waiting list - * @param int $prio User's priority for the given course. - */ - public function notifyRemainingUsers($user_list, $course, $prio = null) - { - foreach ($user_list as $chosen_one) { - setTempLanguage($chosen_one); - $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $course->name); - $message_body = sprintf(_('Sie haben leider bei der Platzverteilung der Veranstaltung **%s** __keinen__ Platz erhalten.'), - $course->name); - if ($prio) { - $message_body .= "\n" . sprintf(_("Sie hatten für diese Veranstaltung die Priorität %s gewählt."), $prio[$chosen_one]); - } - messaging::sendSystemMessage($chosen_one, $message_title, $message_body); - restoreLanguage(); - } - } - - /** - * Notify users that they couldn't get a seat but are now on the waiting - * list for a given course. - * - * @param Array $user_list Users to be notified - * @param Course $course The course without waiting list - * @param int $prio User's priority for the given course. - */ - private function addUsersToWaitlist($user_list, $course, $prio = null) - { - $maxpos = $course->admission_applicants->findBy('status', 'awaiting')->orderBy('position desc')->val('position'); - foreach ($user_list as $chosen_one) { - if ($course->getParticipantStatus($chosen_one)) { - continue; - } - $maxpos++; - $new_admission_member = new AdmissionApplication(); - $new_admission_member->user_id = $chosen_one; - $new_admission_member->position = $maxpos; - $new_admission_member->status = 'awaiting'; - try { - $course->admission_applicants[] = $new_admission_member; - } catch (InvalidArgumentException $e) { - Log::debug($e->getMessage()); - continue; - } - if ($new_admission_member->store()) { - setTempLanguage($chosen_one); - $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $course->name); - $message_body = sprintf(_('Sie haben leider bei der Platzverteilung der Veranstaltung **%s** __keinen__ Platz erhalten. Sie wurden jedoch auf Position %s auf die Warteliste gesetzt. Das System wird Sie automatisch eintragen und benachrichtigen, sobald ein Platz für Sie frei wird.'), - $course->name, - $maxpos); - if ($prio) { - $message_body .= "\n" . sprintf(_("Sie hatten für diese Veranstaltung die Priorität %s gewählt."), $prio[$chosen_one]); - } - messaging::sendSystemMessage($chosen_one, $message_title, $message_body); - restoreLanguage(); - StudipLog::log('SEM_USER_ADD', $course->id, $chosen_one, 'awaiting', 'Auf Warteliste gelost, Position: ' . $maxpos); - } - } - } - - /** - * Add the lucky ones who got a seat to the given course. - * - * @param Array $user_list users to add as members - * @param Course $course course to add users to - * @param int $prio user's priority for the given course - */ - private function addUsersToCourse($user_list, $course, $prio = null) - { - $seminar = new Seminar($course); - foreach ($user_list as $chosen_one) { - setTempLanguage($chosen_one); - $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $seminar->getName()); - if ($seminar->admission_prelim) { - if ($seminar->addPreliminaryMember($chosen_one)) { - $message_body = sprintf (_('Sie haben bei der Platzvergabe der Veranstaltung **%s** einen vorläufigen Platz erhalten. Die endgültige Zulassung zu der Veranstaltung ist noch von weiteren Bedingungen abhängig, die Sie bitte der Veranstaltungsbeschreibung entnehmen.'), - $seminar->getName()); - } - } else { - if ($seminar->addMember($chosen_one, 'autor')) { - $message_body = sprintf (_("Sie haben bei der Platzvergabe der Veranstaltung **%s** einen Platz erhalten. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen."), - $seminar->getName()); - } - } - if ($prio) { - $message_body .= "\n" . sprintf(_("Sie hatten für diese Veranstaltung die Priorität %s gewählt."), $prio[$chosen_one]); - } - messaging::sendSystemMessage($chosen_one, $message_title, $message_body); - restoreLanguage(); - } - } - - /** - * Caedite eos. Novit enim Dominus qui sunt eius. - * - * @param array $user_list - */ - private function rollTheDice($user_list) - { - $max = count($user_list); - foreach($user_list as $user_id => $factor) { - $user_list[$user_id] = $factor * mt_rand(1, $max); - } - arsort($user_list, SORT_NUMERIC); - return $user_list; - } - - /** - * How many users have gotten a seat in distribution? - * - * @return Number of users who where lucky enough to be course members now. - */ - public function countParticipatingUsers($course_ids, $user_ids) - { - $distributed_users = []; - $sum = function($r) use (&$distributed_users) { - $distributed_users[$r['user_id']] += $r['c']; - }; - $db = DBManager::get(); - $db->fetchAll("SELECT user_id, COUNT(*) as c FROM seminar_user - WHERE seminar_id IN(?) AND user_id IN(?) AND status IN (?) GROUP BY user_id", - [$course_ids, $user_ids, ['user', 'autor']], $sum); - $db->fetchAll("SELECT user_id, COUNT(*) as c FROM admission_seminar_user - WHERE seminar_id IN(?) AND user_id IN(?) GROUP BY user_id", [$course_ids, $user_ids], $sum); - return $distributed_users; - } - -} diff --git a/lib/classes/admission/RandomAlgorithm.php b/lib/classes/admission/RandomAlgorithm.php new file mode 100644 index 0000000..54d44d7 --- /dev/null +++ b/lib/classes/admission/RandomAlgorithm.php @@ -0,0 +1,437 @@ + + * @author Thomas Hackl + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 3.0 + */ + +class RandomAlgorithm extends AdmissionAlgorithm +{ + + /** + * Runs the algorithm, thus distributing course seats. + * + * @param CourseSet $courseSet The course set containing the courses + * that seats shall be distributed for. + * @see CourseSet + */ + public function run($courseSet) + { + if ($courseSet->hasAdmissionRule('LimitedAdmission')) { + return $this->distributeByPriorities($courseSet); + } else { + return $this->distributeByCourses($courseSet); + } + } + + /** + * Distribute seats for several courses in a course set. + * No priorities are given. + * + * @param CourseSet $courseSet The course set containing the courses + * that seats shall be distributed for. + * @see CourseSet + */ + private function distributeByCourses($courseSet) + { + Log::debug('start seat distribution for course set: ' . $courseSet->getId()); + $groups_quota = []; + $conditional_rule_filter = array_filter($courseSet->getAdmissionRules(), function ($r) { + return $r instanceof ConditionalAdmission + && count($r->getConditionGroups()); + }); + $conditional_rule = array_pop($conditional_rule_filter); + $conditiongroups = $conditional_rule ? $conditional_rule->getConditionGroups() : []; + if (count($conditiongroups)) { + foreach (array_keys($conditiongroups) as $group_id) { + $groups_quota[$group_id] = $conditional_rule->getQuota($group_id); + } + } else { + $groups_quota[] = 100; + } + foreach ($courseSet->getCourses() as $course_id) { + $waiting_users = []; + $course = Course::find($course_id); + if (!$course) { + Log::debug(sprintf('course %s not found, continue', $course_id)); + continue; + } + $free_seats_course = $course->getFreeSeats(); + foreach ($groups_quota as $group_id => $quota) { + $claiming_users = AdmissionPriority::getPrioritiesByCourse($courseSet->getId(), $course->id); + if (isset($conditiongroups[$group_id])) { + Log::debug(sprintf('found conditiongroup %s with quota %s', $group_id, $quota)); + foreach(array_keys($claiming_users) as $user_id) { + $condition_ok = true; + foreach ($conditiongroups[$group_id] as $condition) { + if ($condition->isFulfilled($user_id)) { + $condition_ok = true; + break; + } + $condition_ok = false; + } + if (!$condition_ok) { + unset($claiming_users[$user_id]); + } + } + } + $factored_users = $courseSet->getUserFactorList(); + //apply bonus/malus to users, exclude participants + foreach(array_keys($claiming_users) as $user_id) { + if (!$course->getParticipantStatus($user_id)) { + $claiming_users[$user_id] = 1; + if (isset($factored_users[$user_id])) { + $claiming_users[$user_id] = + $factored_users[$user_id] == PHP_INT_MAX ? + PHP_INT_MAX : + $claiming_users[$user_id] * $factored_users[$user_id]; + } + Log::debug(sprintf('user %s gets factor %s', $user_id, $claiming_users[$user_id])); + } else { + unset($claiming_users[$user_id]); + Log::debug(sprintf('user %s is already %s, ignoring', $user_id, $course->getParticipantStatus($user_id))); + } + } + $free_seats = round($free_seats_course * $quota / 100, 0, PHP_ROUND_HALF_DOWN); + Log::debug(sprintf('distribute %s seats on %s claiming in course %s %s', $free_seats, count($claiming_users), $course->id, ($group_id ? 'conditiongroup ' . $group_id . ' quota ' . $quota : ''))); + $claiming_users = $this->rollTheDice($claiming_users); + Log::debug('the die is cast: ' . print_r($claiming_users,1)); + $chosen_ones = array_slice(array_keys($claiming_users),0 , $free_seats); + Log::debug('chosen ones: ' . print_r($chosen_ones,1)); + $this->addUsersToCourse($chosen_ones, $course); + foreach ($chosen_ones as $one) unset($waiting_users[$one]); + if ($free_seats < count($claiming_users)) { + $remaining_ones = array_slice(array_keys($claiming_users), $free_seats); + foreach ($remaining_ones as $one) { + $waiting_users[$one] = $claiming_users[$one]; + } + } + } + if (count($waiting_users)) { + $waiting_users = $this->rollTheDice($waiting_users); + if (!$course->admission_disable_waitlist) { + $free_seats_waitlist = $course->admission_waitlist_max ?: count($waiting_users); + $waiting_list_ones = array_slice(array_keys($waiting_users), 0, $free_seats_waitlist); + Log::debug('waiting list ones: ' . print_r($waiting_list_ones, 1)); + $this->addUsersToWaitlist($waiting_list_ones, $course); + } else { + $free_seats_waitlist = 0; + } + if ($free_seats_waitlist < count($waiting_users)) { + $remaining_ones = array_slice(array_keys($waiting_users),$free_seats_waitlist); + Log::debug('remaining ones: ' . print_r($remaining_ones, 1)); + $this->notifyRemainingUsers($remaining_ones, $course); + } + } + } + } + + /** + * Distribute seats for several courses in a course set using the given + * user priorities. + * + * @param CourseSet $courseSet The course set containing the courses + * that seats shall be distributed for. + * @see CourseSet + */ + private function distributeByPriorities($courseSet) + { + Log::debug('start seat distribution for course set: ' . $courseSet->getId()); + $limited_admission = $courseSet->getAdmissionRule('LimitedAdmission'); + //all users with their priorities + $claiming_users = AdmissionPriority::getPriorities($courseSet->getId()); + + //all users which have bonus/malus + $factored_users = $courseSet->getUserFactorList(); + + //all users with their max number of courses + $max_seats_users = array_combine(array_keys($claiming_users), + array_map(function($u) use ($limited_admission) {return $limited_admission->getMaxNumberForUser($u);}, + array_keys($claiming_users) + ) + ); + //unlucky users get a bonus for the next round + $bonus_users = []; + + //users / courses für later waitlist distribution + $waiting_users = []; + + //number of already distributed seats for users + $distributed_users = []; + + $prio_mapper = function ($users, $course_id) use ($claiming_users) { + $mapper = function ($u) use ($course_id) { + return isset($u[$course_id]) ? $u[$course_id] : null; + }; + return array_filter(array_map($mapper, array_intersect_key($claiming_users, array_flip($users)))); + }; + + $groups_quota = []; + $conditional_rule_filter = array_filter($courseSet->getAdmissionRules(), function ($r) { + return $r instanceof ConditionalAdmission + && count($r->getConditionGroups()); + }); + $conditional_rule = array_pop($conditional_rule_filter); + $conditiongroups = $conditional_rule ? $conditional_rule->getConditionGroups() : []; + if (count($conditiongroups)) { + foreach (array_keys($conditiongroups) as $group_id) { + $groups_quota[$group_id] = $conditional_rule->getQuota($group_id); + } + } else { + $groups_quota[] = 100; + } + + //sort courses by highest count of prio 1 applicants + $stats = AdmissionPriority::getPrioritiesStats($courseSet->getId()); + $courses = array_map(function ($a) {return $a['h'];},$stats); + arsort($courses, SORT_NUMERIC); + $id_courses = array_filter(array_keys($courses)); + $max_prio = AdmissionPriority::getPrioritiesMax($courseSet->getId()); + //count already manually distributed places + $distributed_users = $this->countParticipatingUsers($id_courses, array_keys($claiming_users)); + Log::debug('already distributed users: ' . print_r($distributed_users,1)); + //walk through all prios with all courses + foreach(range(1, $max_prio) as $current_prio) { + foreach ($id_courses as $course_id) { + $course = Course::find($course_id); + if (!$course) { + Log::debug(sprintf('course %s not found, continue', $course_id)); + continue; + } + $free_seats_course = $course->getFreeSeats(); + foreach ($groups_quota as $group_id => $quota) { + $current_claiming = []; + //find users with current prio for this course, if they still need a place + foreach ($claiming_users as $user_id => $prio_courses) { + $condition_ok = true; + if (isset($conditiongroups[$group_id])) { + Log::debug(sprintf('found conditiongroup %s with quota %s', $group_id, $quota)); + foreach ($conditiongroups[$group_id] as $condition) { + if ($condition->isFulfilled($user_id)) { + $condition_ok = true; + break; + } + $condition_ok = false; + } + } + + if ($condition_ok && $prio_courses[$course_id] == $current_prio + && $distributed_users[$user_id] < $max_seats_users[$user_id]) { + //exclude participants + if (!$course->getParticipantStatus($user_id)) { + $current_claiming[$user_id] = 1; + if (isset($factored_users[$user_id])) { + $current_claiming[$user_id] = + $factored_users[$user_id] == PHP_INT_MAX ? + PHP_INT_MAX : + $current_claiming[$user_id] * $factored_users[$user_id]; + } + } else { + Log::debug(sprintf('user %s is already %s in course %s, ignoring', $user_id, $course->getParticipantStatus($user_id), $course->id)); + } + } + } + //give maximum bonus to users which were unlucky before + foreach (array_keys($current_claiming) as $user_id) { + if ($bonus_users[$user_id] > 0) { + $current_claiming[$user_id] *= $bonus_users[$user_id] * count($current_claiming) + 1; + } + } + $free_seats = round($free_seats_course * $quota / 100, 0, PHP_ROUND_HALF_DOWN); + Log::debug(sprintf('distribute %s seats on %s claiming with prio %s in course %s %s', $free_seats, count($current_claiming),$current_prio, $course->id, ($group_id ? 'conditiongroup ' . $group_id . ' quota ' . $quota : ''))); + Log::debug('users to distribute: ' . print_r($current_claiming,1)); + $current_claiming = $this->rollTheDice($current_claiming); + Log::debug('the die is cast: ' . print_r($current_claiming,1)); + $chosen_ones = array_slice(array_keys($current_claiming),0 , $free_seats); + Log::debug('chosen ones: ' . print_r($chosen_ones,1)); + $this->addUsersToCourse($chosen_ones, $course, $prio_mapper($chosen_ones, $course->id)); + foreach ($chosen_ones as $one) { + $distributed_users[$one]++; + $bonus_users[$one]--; + } + if ($free_seats < count($current_claiming)) { + $remaining_ones = array_slice(array_keys($current_claiming), $free_seats); + foreach ($remaining_ones as $one) { + $bonus_users[$one]++; + $waiting_users[$current_prio][$course_id][] = $one; + } + } + } + } + } + //distribute to waitlists if applicable + Log::debug('waiting list: ' . print_r($waiting_users, 1)); + foreach ($waiting_users as $current_prio => $current_prio_waiting_courses) { + foreach ($current_prio_waiting_courses as $course_id => $users) { + $users = array_filter(array_unique($users), function($user_id) use ($distributed_users, $max_seats_users) { + return $distributed_users[$user_id] < $max_seats_users[$user_id];}); + $course = Course::find($course_id); + Log::debug(sprintf('distribute waitlist of %s with prio %s in course %s', count($users), $current_prio, $course->id)); + if (!$course->admission_disable_waitlist) { + if ($course->admission_waitlist_max) { + $free_seats_waitlist = $course->admission_waitlist_max - $course->getNumWaiting(); + $free_seats_waitlist = $free_seats_waitlist < 0 ? 0 : $free_seats_waitlist; + } else { + $free_seats_waitlist = count($users); + } + $waiting_list_ones = array_slice($users, 0, $free_seats_waitlist); + Log::debug('waiting list ones: ' . print_r($waiting_list_ones, 1)); + $this->addUsersToWaitlist($waiting_list_ones, $course, $prio_mapper($waiting_list_ones, $course->id)); + foreach ($waiting_list_ones as $one) { + $distributed_users[$one]++; + } + } else { + $free_seats_waitlist = 0; + } + if ($free_seats_waitlist < count($users)) { + $remaining_ones = array_slice($users, $free_seats_waitlist); + Log::debug('remaining ones: ' . print_r($remaining_ones, 1)); + $this->notifyRemainingUsers($remaining_ones, $course, $prio_mapper($remaining_ones, $course->id)); + } + } + } + } + + /** + * Notify users about the fact that they couldn't get a seat and the + * waiting list is disabled in a course. + * + * @param Array $user_list Users to be notified + * @param Course $course The course without waiting list + * @param int $prio User's priority for the given course. + */ + public function notifyRemainingUsers($user_list, $course, $prio = null) + { + foreach ($user_list as $chosen_one) { + setTempLanguage($chosen_one); + $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $course->name); + $message_body = sprintf(_('Sie haben leider bei der Platzverteilung der Veranstaltung **%s** __keinen__ Platz erhalten.'), + $course->name); + if ($prio) { + $message_body .= "\n" . sprintf(_("Sie hatten für diese Veranstaltung die Priorität %s gewählt."), $prio[$chosen_one]); + } + messaging::sendSystemMessage($chosen_one, $message_title, $message_body); + restoreLanguage(); + } + } + + /** + * Notify users that they couldn't get a seat but are now on the waiting + * list for a given course. + * + * @param Array $user_list Users to be notified + * @param Course $course The course without waiting list + * @param int $prio User's priority for the given course. + */ + private function addUsersToWaitlist($user_list, $course, $prio = null) + { + $maxpos = $course->admission_applicants->findBy('status', 'awaiting')->orderBy('position desc')->val('position'); + foreach ($user_list as $chosen_one) { + if ($course->getParticipantStatus($chosen_one)) { + continue; + } + $maxpos++; + $new_admission_member = new AdmissionApplication(); + $new_admission_member->user_id = $chosen_one; + $new_admission_member->position = $maxpos; + $new_admission_member->status = 'awaiting'; + try { + $course->admission_applicants[] = $new_admission_member; + } catch (InvalidArgumentException $e) { + Log::debug($e->getMessage()); + continue; + } + if ($new_admission_member->store()) { + setTempLanguage($chosen_one); + $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $course->name); + $message_body = sprintf(_('Sie haben leider bei der Platzverteilung der Veranstaltung **%s** __keinen__ Platz erhalten. Sie wurden jedoch auf Position %s auf die Warteliste gesetzt. Das System wird Sie automatisch eintragen und benachrichtigen, sobald ein Platz für Sie frei wird.'), + $course->name, + $maxpos); + if ($prio) { + $message_body .= "\n" . sprintf(_("Sie hatten für diese Veranstaltung die Priorität %s gewählt."), $prio[$chosen_one]); + } + messaging::sendSystemMessage($chosen_one, $message_title, $message_body); + restoreLanguage(); + StudipLog::log('SEM_USER_ADD', $course->id, $chosen_one, 'awaiting', 'Auf Warteliste gelost, Position: ' . $maxpos); + } + } + } + + /** + * Add the lucky ones who got a seat to the given course. + * + * @param Array $user_list users to add as members + * @param Course $course course to add users to + * @param int $prio user's priority for the given course + */ + private function addUsersToCourse($user_list, $course, $prio = null) + { + $seminar = new Seminar($course); + foreach ($user_list as $chosen_one) { + setTempLanguage($chosen_one); + $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $seminar->getName()); + if ($seminar->admission_prelim) { + if ($seminar->addPreliminaryMember($chosen_one)) { + $message_body = sprintf (_('Sie haben bei der Platzvergabe der Veranstaltung **%s** einen vorläufigen Platz erhalten. Die endgültige Zulassung zu der Veranstaltung ist noch von weiteren Bedingungen abhängig, die Sie bitte der Veranstaltungsbeschreibung entnehmen.'), + $seminar->getName()); + } + } else { + if ($seminar->addMember($chosen_one, 'autor')) { + $message_body = sprintf (_("Sie haben bei der Platzvergabe der Veranstaltung **%s** einen Platz erhalten. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen."), + $seminar->getName()); + } + } + if ($prio) { + $message_body .= "\n" . sprintf(_("Sie hatten für diese Veranstaltung die Priorität %s gewählt."), $prio[$chosen_one]); + } + messaging::sendSystemMessage($chosen_one, $message_title, $message_body); + restoreLanguage(); + } + } + + /** + * Caedite eos. Novit enim Dominus qui sunt eius. + * + * @param array $user_list + */ + private function rollTheDice($user_list) + { + $max = count($user_list); + foreach($user_list as $user_id => $factor) { + $user_list[$user_id] = $factor * mt_rand(1, $max); + } + arsort($user_list, SORT_NUMERIC); + return $user_list; + } + + /** + * How many users have gotten a seat in distribution? + * + * @return Number of users who where lucky enough to be course members now. + */ + public function countParticipatingUsers($course_ids, $user_ids) + { + $distributed_users = []; + $sum = function($r) use (&$distributed_users) { + $distributed_users[$r['user_id']] += $r['c']; + }; + $db = DBManager::get(); + $db->fetchAll("SELECT user_id, COUNT(*) as c FROM seminar_user + WHERE seminar_id IN(?) AND user_id IN(?) AND status IN (?) GROUP BY user_id", + [$course_ids, $user_ids, ['user', 'autor']], $sum); + $db->fetchAll("SELECT user_id, COUNT(*) as c FROM admission_seminar_user + WHERE seminar_id IN(?) AND user_id IN(?) GROUP BY user_id", [$course_ids, $user_ids], $sum); + return $distributed_users; + } + +} diff --git a/lib/classes/admission/UserFilter.class.php b/lib/classes/admission/UserFilter.class.php deleted file mode 100644 index d380ef0..0000000 --- a/lib/classes/admission/UserFilter.class.php +++ /dev/null @@ -1,279 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class UserFilter -{ - // --- ATTRIBUTES --- - - /** - * All condition fields that form this condition. - */ - public $fields = []; - - /** - * Unique identifier for this condition. - */ - public $id = ''; - - public $show_user_count = false; - // --- OPERATIONS --- - - /** - * Standard constructor. - * - * @param String conditionId - * @return UserFilter - */ - public function __construct($conditionId='') - { - UserFilterField::getAvailableFilterFields(); - $this->id = $conditionId; - if ($conditionId) { - $this->load(); - } else { - $this->id = $this->generateId(); - } - return $this; - } - - /** - * Add a new condition field. - * - * @param ConditionField fieldId - * @return UserFilter - */ - public function addField($field) - { - $this->fields[$field->getId()] = $field; - $field->setConditionId($this->id); - return $this; - } - - /** - * Deletes the condition and all associated fields. - */ - public function delete() { - // Delete condition data. - $stmt = DBManager::get()->prepare("DELETE FROM `userfilter` - WHERE `filter_id`=?"); - $stmt->execute([$this->id]); - // Delete all defined condition fields. - foreach ($this->fields as $field) { - $field->delete(); - } - } - - /** - * Generate a new unique ID. - * - * @param String tableName - */ - public function generateId() { - do { - $newid = md5(uniqid(get_class($this).microtime(), true)); - $id = DBManager::get()->fetchColumn("SELECT `filter_id` - FROM `userfilter` WHERE `filter_id`=?", [$newid]); - } while ($id); - return $newid; - } - - /** - * Get all fields (without checking for validity according - * to the current time). - * - * @return Array - */ - public function getFields() - { - uasort($this->fields, function($a, $b) { - return $a->sortOrder - $b->sortOrder; - }); - return $this->fields; - } - - /** - * Get ID. - * - * @return String - */ - public function getId() - { - return $this->id; - } - - /** - * Gets all users that fulfill the current condition. - * - * @return Array - */ - public function getUsers() { - $users = null; - foreach ($this->fields as $field) { - // Check if restrictions for the field value must be taken into consideration. - $restrictions = []; - foreach ($field->relations as $className => $related) { - if ($other = $this->hasField($className)) { - if ($other->getValue()) { - $restrictions[$className] = [ - 'table' => $other->userDataDbTable, - 'field' => $other->userDataDbField, - 'compare' => $other->getCompareOperator(), - 'value' => $other->getValue() - ]; - } - } - } - $users = isset($users) ? array_intersect($users, $field->getUsers($restrictions)) : $field->getUsers($restrictions); - } - return (array) $users; - } - - /** - * Checks whether the current filter object contains a field - * of the given type. - * - * @param String $className the type to check for - * @return UserFilterField Return the found field or null if not applicable. - */ - public function hasField($className) { - foreach ($this->fields as $field) { - if ($field instanceof $className) { - return $field; - break; - } - } - return null; - } - - /** - * Is the current condition fulfilled (that means, are all - * required field values matched)? - * - * @return boolean - */ - public function isFulfilled($userId) - { - // Check all fields. - foreach ($this->fields as $field) { - if (!$field->checkValue($field->getUserValues($userId, $this->fields))) { - return false; - } - } - return true; - } - - /** - * Helper function for loading data from DB. - */ - public function load() { - // Load basic condition data. - $stmt = DBManager::get()->prepare( - "SELECT * FROM `userfilter` WHERE `filter_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->id = $data['filter_id']; - // Load the associated condition fields. - $stmt = DBManager::get()->prepare( - "SELECT `field_id`, `type` FROM `userfilter_fields` - WHERE `filter_id`=?"); - $stmt->execute([$this->id]); - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - /* - * Create instance of appropriate UserFilterField subclass. - * We just "try" here because the class definition could have - * been removed since saving data to DB. - */ - //try { - $chunks = explode('_', $data['type']); - $type = $chunks[0]; - $param = $chunks[1] ?? null; - if ($param) { - $field = new $type($param, $data['field_id']); - } else { - $field = new $type($data['field_id']); - } - - $this->fields[$field->getId()] = $field; - //} catch (Exception $e) {} - } - } - } - - /** - * Removes the field with the given ID from the condition fields. - * - * @param String fieldId - * @return UserFilter - */ - public function removeField($fieldId) - { - unset($this->fields[$fieldId]); - return $this; - } - - /** - * Stores data to DB. - */ - public function store() { - // Generate new ID if condition entry doesn't exist in DB yet. - if (!$this->id) { - $this->id = $this->generateId(); - } - - // Store condition data. - $stmt = DBManager::get()->prepare("INSERT INTO `userfilter` - (`filter_id`, `mkdate`, `chdate`) - VALUES (?, ?, ?) - ON DUPLICATE KEY UPDATE `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, time(), time()]); - // Delete removed condition fields from DB. - DBManager::get()->exec("DELETE FROM `userfilter_fields` - WHERE `filter_id`='".$this->id."' AND `field_id` NOT IN ('". - implode("', '", array_keys($this->fields))."')"); - // Store all fields. - foreach ($this->fields as $field) { - $field->store($this->id); - } - } - - public function toString() { - $tpl = $GLOBALS['template_factory']->open('userfilter/display'); - $tpl->set_attribute('filter', $this); - return $tpl->render(); - } - - public function __toString() { - return $this->toString(); - } - - public function __clone() - { - $this->id = md5(uniqid(get_class($this))); - $cloned_fields= []; - foreach ($this->fields as $field) { - $dolly = clone $field; - $dolly->conditionId = $this->id; - $cloned_fields[$dolly->id] = $dolly; - } - $this->fields = $cloned_fields; - } - -} /* end of class UserFilter */ - -?> diff --git a/lib/classes/admission/UserFilter.php b/lib/classes/admission/UserFilter.php new file mode 100644 index 0000000..d380ef0 --- /dev/null +++ b/lib/classes/admission/UserFilter.php @@ -0,0 +1,279 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class UserFilter +{ + // --- ATTRIBUTES --- + + /** + * All condition fields that form this condition. + */ + public $fields = []; + + /** + * Unique identifier for this condition. + */ + public $id = ''; + + public $show_user_count = false; + // --- OPERATIONS --- + + /** + * Standard constructor. + * + * @param String conditionId + * @return UserFilter + */ + public function __construct($conditionId='') + { + UserFilterField::getAvailableFilterFields(); + $this->id = $conditionId; + if ($conditionId) { + $this->load(); + } else { + $this->id = $this->generateId(); + } + return $this; + } + + /** + * Add a new condition field. + * + * @param ConditionField fieldId + * @return UserFilter + */ + public function addField($field) + { + $this->fields[$field->getId()] = $field; + $field->setConditionId($this->id); + return $this; + } + + /** + * Deletes the condition and all associated fields. + */ + public function delete() { + // Delete condition data. + $stmt = DBManager::get()->prepare("DELETE FROM `userfilter` + WHERE `filter_id`=?"); + $stmt->execute([$this->id]); + // Delete all defined condition fields. + foreach ($this->fields as $field) { + $field->delete(); + } + } + + /** + * Generate a new unique ID. + * + * @param String tableName + */ + public function generateId() { + do { + $newid = md5(uniqid(get_class($this).microtime(), true)); + $id = DBManager::get()->fetchColumn("SELECT `filter_id` + FROM `userfilter` WHERE `filter_id`=?", [$newid]); + } while ($id); + return $newid; + } + + /** + * Get all fields (without checking for validity according + * to the current time). + * + * @return Array + */ + public function getFields() + { + uasort($this->fields, function($a, $b) { + return $a->sortOrder - $b->sortOrder; + }); + return $this->fields; + } + + /** + * Get ID. + * + * @return String + */ + public function getId() + { + return $this->id; + } + + /** + * Gets all users that fulfill the current condition. + * + * @return Array + */ + public function getUsers() { + $users = null; + foreach ($this->fields as $field) { + // Check if restrictions for the field value must be taken into consideration. + $restrictions = []; + foreach ($field->relations as $className => $related) { + if ($other = $this->hasField($className)) { + if ($other->getValue()) { + $restrictions[$className] = [ + 'table' => $other->userDataDbTable, + 'field' => $other->userDataDbField, + 'compare' => $other->getCompareOperator(), + 'value' => $other->getValue() + ]; + } + } + } + $users = isset($users) ? array_intersect($users, $field->getUsers($restrictions)) : $field->getUsers($restrictions); + } + return (array) $users; + } + + /** + * Checks whether the current filter object contains a field + * of the given type. + * + * @param String $className the type to check for + * @return UserFilterField Return the found field or null if not applicable. + */ + public function hasField($className) { + foreach ($this->fields as $field) { + if ($field instanceof $className) { + return $field; + break; + } + } + return null; + } + + /** + * Is the current condition fulfilled (that means, are all + * required field values matched)? + * + * @return boolean + */ + public function isFulfilled($userId) + { + // Check all fields. + foreach ($this->fields as $field) { + if (!$field->checkValue($field->getUserValues($userId, $this->fields))) { + return false; + } + } + return true; + } + + /** + * Helper function for loading data from DB. + */ + public function load() { + // Load basic condition data. + $stmt = DBManager::get()->prepare( + "SELECT * FROM `userfilter` WHERE `filter_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->id = $data['filter_id']; + // Load the associated condition fields. + $stmt = DBManager::get()->prepare( + "SELECT `field_id`, `type` FROM `userfilter_fields` + WHERE `filter_id`=?"); + $stmt->execute([$this->id]); + while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + /* + * Create instance of appropriate UserFilterField subclass. + * We just "try" here because the class definition could have + * been removed since saving data to DB. + */ + //try { + $chunks = explode('_', $data['type']); + $type = $chunks[0]; + $param = $chunks[1] ?? null; + if ($param) { + $field = new $type($param, $data['field_id']); + } else { + $field = new $type($data['field_id']); + } + + $this->fields[$field->getId()] = $field; + //} catch (Exception $e) {} + } + } + } + + /** + * Removes the field with the given ID from the condition fields. + * + * @param String fieldId + * @return UserFilter + */ + public function removeField($fieldId) + { + unset($this->fields[$fieldId]); + return $this; + } + + /** + * Stores data to DB. + */ + public function store() { + // Generate new ID if condition entry doesn't exist in DB yet. + if (!$this->id) { + $this->id = $this->generateId(); + } + + // Store condition data. + $stmt = DBManager::get()->prepare("INSERT INTO `userfilter` + (`filter_id`, `mkdate`, `chdate`) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, time(), time()]); + // Delete removed condition fields from DB. + DBManager::get()->exec("DELETE FROM `userfilter_fields` + WHERE `filter_id`='".$this->id."' AND `field_id` NOT IN ('". + implode("', '", array_keys($this->fields))."')"); + // Store all fields. + foreach ($this->fields as $field) { + $field->store($this->id); + } + } + + public function toString() { + $tpl = $GLOBALS['template_factory']->open('userfilter/display'); + $tpl->set_attribute('filter', $this); + return $tpl->render(); + } + + public function __toString() { + return $this->toString(); + } + + public function __clone() + { + $this->id = md5(uniqid(get_class($this))); + $cloned_fields= []; + foreach ($this->fields as $field) { + $dolly = clone $field; + $dolly->conditionId = $this->id; + $cloned_fields[$dolly->id] = $dolly; + } + $this->fields = $cloned_fields; + } + +} /* end of class UserFilter */ + +?> diff --git a/lib/classes/admission/UserFilterField.class.php b/lib/classes/admission/UserFilterField.class.php deleted file mode 100644 index 9081e90..0000000 --- a/lib/classes/admission/UserFilterField.class.php +++ /dev/null @@ -1,473 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class UserFilterField -{ - // --- ATTRIBUTES --- - - /** - * Which of the valid compare operators is currently chosen? - */ - public $compareOperator = ''; - - /** - * ID of the UserFilter this field belongs to. - */ - public $conditionId = ''; - - /** - * Unique ID for this condition field. - */ - public $id = ''; - - /** - * The set of valid compare operators. - */ - public $validCompareOperators = []; - - /** - * All valid values for this field. - */ - public $validValues = []; - - /** - * Which of the valid values is currently chosen? - */ - public $value = null; - - /* - * Provide some kind of sort order for filter fields. By default, - * all subclasses without an explicitly given order will be sorted at the end. - */ - public $sortOrder = 99; - - public static $isParameterized = false; - - protected static $cached_valid_values; - protected static $available_filter_fields; - - /** - * Database tables and fields to get valid values and concrete user values - * from. - */ - public $valuesDbTable = ''; - public $valuesDbIdField = ''; - public $valuesDbNameField = ''; - public $userDataDbTable = ''; - public $userDataDbField = ''; - public $relations = []; - - // --- OPERATIONS --- - - public static function getParameterizedTypes() - { - - } - - - /** - * Standard constructor. - * - * @param String $fieldId If a fieldId is given, the corresponding data is - * loaded from database. - * - */ - public function __construct($fieldId = '') - { - $this->validCompareOperators = [ - '=' => _('ist'), - '!=' => _('ist nicht') - ]; - if ($this->valuesDbNameField) { - if (isset(self::$cached_valid_values[static::class])) { - $this->validValues = self::$cached_valid_values[static::class]; - } else { - // Get all available values from database. - $stmt = DBManager::get()->query( - "SELECT DISTINCT `" . $this->valuesDbIdField . "`, `" . $this->valuesDbNameField . "` " . - "FROM `" . $this->valuesDbTable . "` ORDER BY `" . $this->valuesDbNameField . "` ASC"); - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->validValues[$current[$this->valuesDbIdField]] = $current[$this->valuesDbNameField]; - } - self::$cached_valid_values[static::class] = $this->validValues; - } - } - if ($fieldId) { - $this->id = $fieldId; - $this->load(); - } else { - $this->id = $this->generateId(); - } - } - - /** - * Checks whether the given value fits the configured condition. The - * value is compared to the currently selected value by using the - * currently selected compare operator. - * - * @param Array values - * @return Boolean - */ - public function checkValue($values) - { - // Validate compare operator - if (!isset($this->validCompareOperators[$this->compareOperator])) { - throw new Exception('Invalid compare operator'); - } - - $result = false; - foreach ($values as $value) { - switch ($this->compareOperator) { - case '=': - $result = $value == $this->value; - break; - case '!=': - $result = $value != $this->value; - break; - case '<': - $result = $value < $this->value; - break; - case '<=': - $result = $value <= $this->value; - break; - case '>=': - $result = $value >= $this->value; - break; - case '>': - $result = $value > $this->value; - break; - default: - throw new Exception('Unknown compare operator.'); - } - - if ($result) { - break; - } - } - return $result; - } - - /** - * Deletes the stored data for this condition field from DB. - */ - public function delete() - { - // Delete condition data. - $stmt = DBManager::get()->prepare("DELETE FROM `userfilter_fields` - WHERE `field_id`=?"); - $stmt->execute([$this->id]); - } - - /** - * Generate a new unique ID. - * - * @param String tableName - */ - public function generateId() - { - do { - $newid = md5(uniqid(get_class($this).microtime(), true)); - $id = DBManager::get()->fetchColumn("SELECT `field_id` - FROM `userfilter_fields` WHERE `field_id`=?", [$newid]); - } while ($id); - return $newid; - } - - /** - * Reads all available UserFilterField subclasses and loads their definitions. - */ - public static function getAvailableFilterFields() - { - if (self::$available_filter_fields === null) { - $fields = []; - // Load all PHP class files found in the condition field folder. - foreach (glob(realpath(dirname(__FILE__).'/userfilter').'/*.class.php') as $file) { - require_once($file); - // Try to auto-calculate class name from file name. - $className = mb_substr(basename($file), 0, - mb_strpos(basename($file), '.class.php')); - // Check if class is right. - if (is_subclass_of($className, 'UserFilterField')) { - if ($className::$isParameterized) { - $fields = array_merge($fields, $className::getParameterizedTypes()); - } else { - $filter = new $className(); - $fields[$className] = $filter->getName(); - } - } - } - asort($fields); - self::$available_filter_fields = $fields; - } - return self::$available_filter_fields; - } - - - /** - * Which compare operator is set? - * - * @return String - */ - public function getCompareOperator() - { - return $this->compareOperator; - } - - /** - * Which compare operator is set? - * - * @return String - */ - public function getCompareOperatorAsText() - { - return $this->getValidCompareOperators()[$this->compareOperator]; - } - - /** - * Field ID. - * - * @return String - */ - public function getId() - { - return $this->id; - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return _("Nutzerfilterfeld"); - } - - /** - * Compares all the users' values by using the specified compare operator - * and returns all users that fulfill the condition. This can be - * an important information when checking on validity of a combination - * of conditions. - * - * @param Array $restrictions values from other fields that restrict the valid - * values for a user (e.g. a semester of study in - * a given subject) - * @return Array All users that are affected by the current condition - * field. - */ - public function getUsers($restrictions = []) - { - $db = DBManager::get(); - $users = []; - // Standard query getting the values without respecting other values. - $select = "SELECT DISTINCT `".$this->userDataDbTable."`.`user_id` "; - $from = "FROM `".$this->userDataDbTable."` "; - $where = "WHERE `".$this->userDataDbTable."`.`".$this->userDataDbField. - "`".$this->compareOperator."?"; - $parameters = [$this->value]; - $joinedTables = [ - $this->userDataDbTable => true - ]; - // Check if there are restrictions given. - foreach ($restrictions as $otherField => $restriction) { - // We only take the value into consideration if it represents a valid restriction. - if ($this->relations[$otherField]) { - // Do we need to join in another table? - if (!$joinedTables[$restriction['table']]) { - $joinedTables[$restriction['table']] = true; - $from .= " INNER JOIN `".$restriction['table']."` ON (`". - $this->userDataDbTable."`.`". - $this->relations[$otherField]['local_field']."`=`". - $restriction['table']."`.`". - $this->relations[$otherField]['foreign_field']."`)"; - } - // Expand WHERE statement with the value from restriction. - $where .= " AND `".$restriction['table']."`.`". - $restriction['field']."`".$restriction['compare']."?"; - $parameters[] = $restriction['value']; - } - } - // Get all the users that fulfill the condition. - $stmt = $db->prepare($select.$from.$where); - $stmt->execute($parameters); - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $users[] = $current['user_id']; - } - return $users; - } - - /** - * Gets the value for the given user that is relevant for this - * condition field. Here, this method looks up the study degree(s) - * for the user. These can then be compared with the required degrees - * whether they fit. - * - * @param String $userId User to check. - * @param array $additional conditions that are required for check. - * @return array The value(s) for this user. - */ - public function getUserValues($userId, $additional = null) - { - $result = []; - $query = "SELECT DISTINCT `".$this->userDataDbField."` ". - "FROM `".$this->userDataDbTable."` ". - "WHERE `user_id`=?"; - $parameters = [$userId]; - // Additional requirements given... - if (is_array($additional)) { - - // Don't use the same database field twice as this can only get ugly. - $usedFields = [$this->userDataDbField]; - - foreach ($additional as $a_condition) { - if ($a_condition->id != $this->id && $this->userDataDbTable == $a_condition->userDataDbTable && - !in_array($a_condition->userDataDbField, $usedFields)) { - $query .= " AND `" . $a_condition->userDataDbField . "` " . $a_condition->compareOperator . "?"; - $parameters[] = $a_condition->value; - } - } - } - // Get semester of study for user. - $stmt = DBManager::get()->prepare($query); - $stmt->execute($parameters); - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $result[] = $current[$this->userDataDbField]; - } - return $result; - } - - /** - * Returns all valid compare operators. - * - * @return Array Array of valid compare operators. - */ - public function getValidCompareOperators() - { - return $this->validCompareOperators; - } - - /** - * Returns all valid values. Values can be loaded dynamically from - * database or be returned as static array. - * - * @return Array Valid values in the form $value => $displayname. - */ - public function getValidValues() - { - return $this->validValues; - } - - /** - * Which value is set? - * - * @return String - */ - public function getValue() - { - return $this->value; - } - - /** - * Helper function for loading data from DB. - */ - public function load() - { - $stmt = DBManager::get()->prepare( - "SELECT * FROM `userfilter_fields` WHERE `field_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->conditionId = $data['filter_id']; - $this->value = $data['value']; - $this->compareOperator = $data['compare_op']; - } - } - - /** - * Sets a new selected compare operator - * - * @param String newOperator - * @return UserFilterField - */ - public function setCompareOperator($newOperator) - { - if (in_array($newOperator, array_keys($this->validCompareOperators))) { - $this->compareOperator = $newOperator; - return $this; - } else { - return false; - } - } - - /** - * Connects the current field to a UserFilter. - * - * @param String $id ID of a UserFilter object. - * @return UserFilterField - */ - public function setConditionId($id) - { - $this->conditionId = $id; - return $this; - } - - /** - * Sets a new selected value. - * - * @param String newValue - * @return UserFilterField - */ - public function setValue($newValue) - { - if ($this->validValues[$newValue]) { - $this->value = $newValue; - return $this; - } else { - return false; - } - } - - /** - * Stores data to DB. - * - * @param String conditionId The condition this field belongs to. - */ - public function store() - { - // Generate new ID if field entry doesn't exist in DB yet. - if (!$this->id) { - $this->id = $this->generateId(); - } - // Store field data. - $stmt = DBManager::get()->prepare("INSERT INTO `userfilter_fields` - (`field_id`, `filter_id`, `type`, `value`, `compare_op`, - `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE `filter_id`=VALUES(`filter_id`), - `type`=VALUES(`type`),`value`=VALUES(`value`), - `compare_op`=VALUES(`compare_op`), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->conditionId, get_class($this), - $this->value, $this->compareOperator, time(), time()]); - } - - public function __clone() - { - $this->id = md5(uniqid(get_class($this))); - $this->conditionId = null; - } - -} /* end of class UserFilterField */ diff --git a/lib/classes/admission/UserFilterField.php b/lib/classes/admission/UserFilterField.php new file mode 100644 index 0000000..9081e90 --- /dev/null +++ b/lib/classes/admission/UserFilterField.php @@ -0,0 +1,473 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class UserFilterField +{ + // --- ATTRIBUTES --- + + /** + * Which of the valid compare operators is currently chosen? + */ + public $compareOperator = ''; + + /** + * ID of the UserFilter this field belongs to. + */ + public $conditionId = ''; + + /** + * Unique ID for this condition field. + */ + public $id = ''; + + /** + * The set of valid compare operators. + */ + public $validCompareOperators = []; + + /** + * All valid values for this field. + */ + public $validValues = []; + + /** + * Which of the valid values is currently chosen? + */ + public $value = null; + + /* + * Provide some kind of sort order for filter fields. By default, + * all subclasses without an explicitly given order will be sorted at the end. + */ + public $sortOrder = 99; + + public static $isParameterized = false; + + protected static $cached_valid_values; + protected static $available_filter_fields; + + /** + * Database tables and fields to get valid values and concrete user values + * from. + */ + public $valuesDbTable = ''; + public $valuesDbIdField = ''; + public $valuesDbNameField = ''; + public $userDataDbTable = ''; + public $userDataDbField = ''; + public $relations = []; + + // --- OPERATIONS --- + + public static function getParameterizedTypes() + { + + } + + + /** + * Standard constructor. + * + * @param String $fieldId If a fieldId is given, the corresponding data is + * loaded from database. + * + */ + public function __construct($fieldId = '') + { + $this->validCompareOperators = [ + '=' => _('ist'), + '!=' => _('ist nicht') + ]; + if ($this->valuesDbNameField) { + if (isset(self::$cached_valid_values[static::class])) { + $this->validValues = self::$cached_valid_values[static::class]; + } else { + // Get all available values from database. + $stmt = DBManager::get()->query( + "SELECT DISTINCT `" . $this->valuesDbIdField . "`, `" . $this->valuesDbNameField . "` " . + "FROM `" . $this->valuesDbTable . "` ORDER BY `" . $this->valuesDbNameField . "` ASC"); + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->validValues[$current[$this->valuesDbIdField]] = $current[$this->valuesDbNameField]; + } + self::$cached_valid_values[static::class] = $this->validValues; + } + } + if ($fieldId) { + $this->id = $fieldId; + $this->load(); + } else { + $this->id = $this->generateId(); + } + } + + /** + * Checks whether the given value fits the configured condition. The + * value is compared to the currently selected value by using the + * currently selected compare operator. + * + * @param Array values + * @return Boolean + */ + public function checkValue($values) + { + // Validate compare operator + if (!isset($this->validCompareOperators[$this->compareOperator])) { + throw new Exception('Invalid compare operator'); + } + + $result = false; + foreach ($values as $value) { + switch ($this->compareOperator) { + case '=': + $result = $value == $this->value; + break; + case '!=': + $result = $value != $this->value; + break; + case '<': + $result = $value < $this->value; + break; + case '<=': + $result = $value <= $this->value; + break; + case '>=': + $result = $value >= $this->value; + break; + case '>': + $result = $value > $this->value; + break; + default: + throw new Exception('Unknown compare operator.'); + } + + if ($result) { + break; + } + } + return $result; + } + + /** + * Deletes the stored data for this condition field from DB. + */ + public function delete() + { + // Delete condition data. + $stmt = DBManager::get()->prepare("DELETE FROM `userfilter_fields` + WHERE `field_id`=?"); + $stmt->execute([$this->id]); + } + + /** + * Generate a new unique ID. + * + * @param String tableName + */ + public function generateId() + { + do { + $newid = md5(uniqid(get_class($this).microtime(), true)); + $id = DBManager::get()->fetchColumn("SELECT `field_id` + FROM `userfilter_fields` WHERE `field_id`=?", [$newid]); + } while ($id); + return $newid; + } + + /** + * Reads all available UserFilterField subclasses and loads their definitions. + */ + public static function getAvailableFilterFields() + { + if (self::$available_filter_fields === null) { + $fields = []; + // Load all PHP class files found in the condition field folder. + foreach (glob(realpath(dirname(__FILE__).'/userfilter').'/*.class.php') as $file) { + require_once($file); + // Try to auto-calculate class name from file name. + $className = mb_substr(basename($file), 0, + mb_strpos(basename($file), '.class.php')); + // Check if class is right. + if (is_subclass_of($className, 'UserFilterField')) { + if ($className::$isParameterized) { + $fields = array_merge($fields, $className::getParameterizedTypes()); + } else { + $filter = new $className(); + $fields[$className] = $filter->getName(); + } + } + } + asort($fields); + self::$available_filter_fields = $fields; + } + return self::$available_filter_fields; + } + + + /** + * Which compare operator is set? + * + * @return String + */ + public function getCompareOperator() + { + return $this->compareOperator; + } + + /** + * Which compare operator is set? + * + * @return String + */ + public function getCompareOperatorAsText() + { + return $this->getValidCompareOperators()[$this->compareOperator]; + } + + /** + * Field ID. + * + * @return String + */ + public function getId() + { + return $this->id; + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return _("Nutzerfilterfeld"); + } + + /** + * Compares all the users' values by using the specified compare operator + * and returns all users that fulfill the condition. This can be + * an important information when checking on validity of a combination + * of conditions. + * + * @param Array $restrictions values from other fields that restrict the valid + * values for a user (e.g. a semester of study in + * a given subject) + * @return Array All users that are affected by the current condition + * field. + */ + public function getUsers($restrictions = []) + { + $db = DBManager::get(); + $users = []; + // Standard query getting the values without respecting other values. + $select = "SELECT DISTINCT `".$this->userDataDbTable."`.`user_id` "; + $from = "FROM `".$this->userDataDbTable."` "; + $where = "WHERE `".$this->userDataDbTable."`.`".$this->userDataDbField. + "`".$this->compareOperator."?"; + $parameters = [$this->value]; + $joinedTables = [ + $this->userDataDbTable => true + ]; + // Check if there are restrictions given. + foreach ($restrictions as $otherField => $restriction) { + // We only take the value into consideration if it represents a valid restriction. + if ($this->relations[$otherField]) { + // Do we need to join in another table? + if (!$joinedTables[$restriction['table']]) { + $joinedTables[$restriction['table']] = true; + $from .= " INNER JOIN `".$restriction['table']."` ON (`". + $this->userDataDbTable."`.`". + $this->relations[$otherField]['local_field']."`=`". + $restriction['table']."`.`". + $this->relations[$otherField]['foreign_field']."`)"; + } + // Expand WHERE statement with the value from restriction. + $where .= " AND `".$restriction['table']."`.`". + $restriction['field']."`".$restriction['compare']."?"; + $parameters[] = $restriction['value']; + } + } + // Get all the users that fulfill the condition. + $stmt = $db->prepare($select.$from.$where); + $stmt->execute($parameters); + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $users[] = $current['user_id']; + } + return $users; + } + + /** + * Gets the value for the given user that is relevant for this + * condition field. Here, this method looks up the study degree(s) + * for the user. These can then be compared with the required degrees + * whether they fit. + * + * @param String $userId User to check. + * @param array $additional conditions that are required for check. + * @return array The value(s) for this user. + */ + public function getUserValues($userId, $additional = null) + { + $result = []; + $query = "SELECT DISTINCT `".$this->userDataDbField."` ". + "FROM `".$this->userDataDbTable."` ". + "WHERE `user_id`=?"; + $parameters = [$userId]; + // Additional requirements given... + if (is_array($additional)) { + + // Don't use the same database field twice as this can only get ugly. + $usedFields = [$this->userDataDbField]; + + foreach ($additional as $a_condition) { + if ($a_condition->id != $this->id && $this->userDataDbTable == $a_condition->userDataDbTable && + !in_array($a_condition->userDataDbField, $usedFields)) { + $query .= " AND `" . $a_condition->userDataDbField . "` " . $a_condition->compareOperator . "?"; + $parameters[] = $a_condition->value; + } + } + } + // Get semester of study for user. + $stmt = DBManager::get()->prepare($query); + $stmt->execute($parameters); + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $result[] = $current[$this->userDataDbField]; + } + return $result; + } + + /** + * Returns all valid compare operators. + * + * @return Array Array of valid compare operators. + */ + public function getValidCompareOperators() + { + return $this->validCompareOperators; + } + + /** + * Returns all valid values. Values can be loaded dynamically from + * database or be returned as static array. + * + * @return Array Valid values in the form $value => $displayname. + */ + public function getValidValues() + { + return $this->validValues; + } + + /** + * Which value is set? + * + * @return String + */ + public function getValue() + { + return $this->value; + } + + /** + * Helper function for loading data from DB. + */ + public function load() + { + $stmt = DBManager::get()->prepare( + "SELECT * FROM `userfilter_fields` WHERE `field_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->conditionId = $data['filter_id']; + $this->value = $data['value']; + $this->compareOperator = $data['compare_op']; + } + } + + /** + * Sets a new selected compare operator + * + * @param String newOperator + * @return UserFilterField + */ + public function setCompareOperator($newOperator) + { + if (in_array($newOperator, array_keys($this->validCompareOperators))) { + $this->compareOperator = $newOperator; + return $this; + } else { + return false; + } + } + + /** + * Connects the current field to a UserFilter. + * + * @param String $id ID of a UserFilter object. + * @return UserFilterField + */ + public function setConditionId($id) + { + $this->conditionId = $id; + return $this; + } + + /** + * Sets a new selected value. + * + * @param String newValue + * @return UserFilterField + */ + public function setValue($newValue) + { + if ($this->validValues[$newValue]) { + $this->value = $newValue; + return $this; + } else { + return false; + } + } + + /** + * Stores data to DB. + * + * @param String conditionId The condition this field belongs to. + */ + public function store() + { + // Generate new ID if field entry doesn't exist in DB yet. + if (!$this->id) { + $this->id = $this->generateId(); + } + // Store field data. + $stmt = DBManager::get()->prepare("INSERT INTO `userfilter_fields` + (`field_id`, `filter_id`, `type`, `value`, `compare_op`, + `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE `filter_id`=VALUES(`filter_id`), + `type`=VALUES(`type`),`value`=VALUES(`value`), + `compare_op`=VALUES(`compare_op`), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->conditionId, get_class($this), + $this->value, $this->compareOperator, time(), time()]); + } + + public function __clone() + { + $this->id = md5(uniqid(get_class($this))); + $this->conditionId = null; + } + +} /* end of class UserFilterField */ diff --git a/lib/classes/admission/userfilter/DatafieldCondition.class.php b/lib/classes/admission/userfilter/DatafieldCondition.class.php deleted file mode 100644 index 966fe8e..0000000 --- a/lib/classes/admission/userfilter/DatafieldCondition.class.php +++ /dev/null @@ -1,164 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class DatafieldCondition extends UserFilterField -{ - public static $isParameterized = true; - - public $datafield_id, $null_yields, $datafield_name; - - public $sortOrder = 6; - - public static function getParameterizedTypes() - { - $ret = []; - try { - foreach (DataField::findBySQL("object_type='user' AND (object_class & (1|2|4|8) OR object_class IS NULL) AND is_userfilter = 1 ORDER BY priority") as $df) { - $ret[__CLASS__ . '_' . $df->id] = utf8_encode(chr(160)) . _("Datenfeld") . ': ' . $df->name; - } - } catch (PDOException $e) {} //migration 128 chokes on this... - return $ret; - } - /** - * @see UserFilterField::__construct - */ - public function __construct($typeparam, $fieldId = '') - { - $this->validCompareOperators = [ - '>=' => _('mindestens'), - '<=' => _('höchstens'), - '=' => _('ist'), - '!=' => _('ist nicht') - ]; - if ($fieldId) { - $this->id = $fieldId; - $this->load(); - } else { - $this->id = $this->generateId(); - $this->datafield_id = $typeparam; - } - - $df = DataField::find($this->datafield_id); - if ($df) { - $this->datafield_name = $df->name; - } else { - throw new UnexpectedValueException('datafield not found, id: ' . $typeparam); - } - $typed_df = DataFieldEntry::createDataFieldEntry($df); - if ($typed_df instanceof DataFieldBoolEntry) { - $this->validValues = [1 => _('Ja'), 0 => _('Nein')]; - unset($this->validCompareOperators['>=']); - unset($this->validCompareOperators['<=']); - unset($this->validCompareOperators['!=']); - $this->null_yields = 0; - } else if ($typed_df instanceof DataFieldSelectboxEntry) { - list($valid_values, $is_assoc) = $typed_df->getParameters(); - if (!$is_assoc) { - $valid_values = array_combine($valid_values, $valid_values); - } - $this->validValues = $valid_values; - $this->null_yields = $typed_df instanceof DataFieldSelectboxMultipleEntry ? '' : key($valid_values); - } else { - $this->null_yields = ''; - } - - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return $this->datafield_name; - } - - public function getUsers($restrictions = []) - { - $db = DBManager::get(); - // Standard query getting the values without respecting other values. - $select = "SELECT user_id FROM - auth_user_md5 LEFT JOIN - datafields_entries ON range_id = user_id AND datafield_id = ? - WHERE perms IN ('user','autor','tutor','dozent') AND IFNULL(content, ?) - " . $this->compareOperator . " ?"; - $users = $db->fetchFirst($select, [$this->datafield_id, $this->null_yields,$this->value]); - return $users; - } - - /** - * Gets the value for the given user that is relevant for this - * - * @param String $userId User to check. - * @param Array $additional additional conditions that are required for check. - * @return array The value(s) for this user. - */ - public function getUserValues($userId, $additional = null) - { - $result = DBManager::get()->fetchColumn( - "SELECT content FROM datafields_entries - WHERE datafield_id = ? AND range_id = ?", [$this->datafield_id, $userId]); - return [$result === null || $result === false ? $this->null_yields : $result]; - } - - /** - * Helper function for loading data from DB. - */ - public function load() - { - $stmt = DBManager::get()->prepare( - "SELECT * FROM `userfilter_fields` WHERE `field_id`=? LIMIT 1"); - $stmt->execute([$this->id]); - if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->conditionId = $data['filter_id']; - $this->value = $data['value']; - $this->compareOperator = $data['compare_op']; - list(,$this->datafield_id) = explode('_', $data['type']); - } - } - - /** - * Sets a new selected value. - * - * @param String newValue - * @return UserFilterField - */ - public function setValue($newValue) - { - $this->value = $newValue; - return $this; - } - - /** - * Stores data to DB. - * - */ - public function store() - { - // Generate new ID if field entry doesn't exist in DB yet. - if (!$this->id) { - $this->id = $this->generateId(); - } - // Store field data. - $stmt = DBManager::get()->prepare("INSERT INTO `userfilter_fields` - (`field_id`, `filter_id`, `type`, `value`, `compare_op`, - `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE `filter_id`=VALUES(`filter_id`), - `type`=VALUES(`type`),`value`=VALUES(`value`), - `compare_op`=VALUES(`compare_op`), `chdate`=VALUES(`chdate`)"); - $stmt->execute([$this->id, $this->conditionId, get_class($this).'_'.$this->datafield_id, - $this->value, $this->compareOperator, time(), time()]); - } -} diff --git a/lib/classes/admission/userfilter/DatafieldCondition.php b/lib/classes/admission/userfilter/DatafieldCondition.php new file mode 100644 index 0000000..966fe8e --- /dev/null +++ b/lib/classes/admission/userfilter/DatafieldCondition.php @@ -0,0 +1,164 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class DatafieldCondition extends UserFilterField +{ + public static $isParameterized = true; + + public $datafield_id, $null_yields, $datafield_name; + + public $sortOrder = 6; + + public static function getParameterizedTypes() + { + $ret = []; + try { + foreach (DataField::findBySQL("object_type='user' AND (object_class & (1|2|4|8) OR object_class IS NULL) AND is_userfilter = 1 ORDER BY priority") as $df) { + $ret[__CLASS__ . '_' . $df->id] = utf8_encode(chr(160)) . _("Datenfeld") . ': ' . $df->name; + } + } catch (PDOException $e) {} //migration 128 chokes on this... + return $ret; + } + /** + * @see UserFilterField::__construct + */ + public function __construct($typeparam, $fieldId = '') + { + $this->validCompareOperators = [ + '>=' => _('mindestens'), + '<=' => _('höchstens'), + '=' => _('ist'), + '!=' => _('ist nicht') + ]; + if ($fieldId) { + $this->id = $fieldId; + $this->load(); + } else { + $this->id = $this->generateId(); + $this->datafield_id = $typeparam; + } + + $df = DataField::find($this->datafield_id); + if ($df) { + $this->datafield_name = $df->name; + } else { + throw new UnexpectedValueException('datafield not found, id: ' . $typeparam); + } + $typed_df = DataFieldEntry::createDataFieldEntry($df); + if ($typed_df instanceof DataFieldBoolEntry) { + $this->validValues = [1 => _('Ja'), 0 => _('Nein')]; + unset($this->validCompareOperators['>=']); + unset($this->validCompareOperators['<=']); + unset($this->validCompareOperators['!=']); + $this->null_yields = 0; + } else if ($typed_df instanceof DataFieldSelectboxEntry) { + list($valid_values, $is_assoc) = $typed_df->getParameters(); + if (!$is_assoc) { + $valid_values = array_combine($valid_values, $valid_values); + } + $this->validValues = $valid_values; + $this->null_yields = $typed_df instanceof DataFieldSelectboxMultipleEntry ? '' : key($valid_values); + } else { + $this->null_yields = ''; + } + + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return $this->datafield_name; + } + + public function getUsers($restrictions = []) + { + $db = DBManager::get(); + // Standard query getting the values without respecting other values. + $select = "SELECT user_id FROM + auth_user_md5 LEFT JOIN + datafields_entries ON range_id = user_id AND datafield_id = ? + WHERE perms IN ('user','autor','tutor','dozent') AND IFNULL(content, ?) + " . $this->compareOperator . " ?"; + $users = $db->fetchFirst($select, [$this->datafield_id, $this->null_yields,$this->value]); + return $users; + } + + /** + * Gets the value for the given user that is relevant for this + * + * @param String $userId User to check. + * @param Array $additional additional conditions that are required for check. + * @return array The value(s) for this user. + */ + public function getUserValues($userId, $additional = null) + { + $result = DBManager::get()->fetchColumn( + "SELECT content FROM datafields_entries + WHERE datafield_id = ? AND range_id = ?", [$this->datafield_id, $userId]); + return [$result === null || $result === false ? $this->null_yields : $result]; + } + + /** + * Helper function for loading data from DB. + */ + public function load() + { + $stmt = DBManager::get()->prepare( + "SELECT * FROM `userfilter_fields` WHERE `field_id`=? LIMIT 1"); + $stmt->execute([$this->id]); + if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->conditionId = $data['filter_id']; + $this->value = $data['value']; + $this->compareOperator = $data['compare_op']; + list(,$this->datafield_id) = explode('_', $data['type']); + } + } + + /** + * Sets a new selected value. + * + * @param String newValue + * @return UserFilterField + */ + public function setValue($newValue) + { + $this->value = $newValue; + return $this; + } + + /** + * Stores data to DB. + * + */ + public function store() + { + // Generate new ID if field entry doesn't exist in DB yet. + if (!$this->id) { + $this->id = $this->generateId(); + } + // Store field data. + $stmt = DBManager::get()->prepare("INSERT INTO `userfilter_fields` + (`field_id`, `filter_id`, `type`, `value`, `compare_op`, + `mkdate`, `chdate`) VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE `filter_id`=VALUES(`filter_id`), + `type`=VALUES(`type`),`value`=VALUES(`value`), + `compare_op`=VALUES(`compare_op`), `chdate`=VALUES(`chdate`)"); + $stmt->execute([$this->id, $this->conditionId, get_class($this).'_'.$this->datafield_id, + $this->value, $this->compareOperator, time(), time()]); + } +} diff --git a/lib/classes/admission/userfilter/DegreeCondition.class.php b/lib/classes/admission/userfilter/DegreeCondition.class.php deleted file mode 100644 index 9180e2d..0000000 --- a/lib/classes/admission/userfilter/DegreeCondition.class.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class DegreeCondition extends UserFilterField -{ - // --- ATTRIBUTES --- - public $valuesDbTable = 'abschluss'; - public $valuesDbIdField = 'abschluss_id'; - public $valuesDbNameField = 'name'; - public $userDataDbTable = 'user_studiengang'; - public $userDataDbField = 'abschluss_id'; - - public $sortOrder = 1; - - /** - * @see UserFilterField::__construct - */ - public function __construct($fieldId = '') - { - parent::__construct($fieldId); - $this->relations = [ - 'SubjectCondition' => [ - 'local_field' => 'fach_id', - 'foreign_field' => 'fach_id' - ] - ]; - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return _('Abschluss'); - } - -} diff --git a/lib/classes/admission/userfilter/DegreeCondition.php b/lib/classes/admission/userfilter/DegreeCondition.php new file mode 100644 index 0000000..9180e2d --- /dev/null +++ b/lib/classes/admission/userfilter/DegreeCondition.php @@ -0,0 +1,51 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class DegreeCondition extends UserFilterField +{ + // --- ATTRIBUTES --- + public $valuesDbTable = 'abschluss'; + public $valuesDbIdField = 'abschluss_id'; + public $valuesDbNameField = 'name'; + public $userDataDbTable = 'user_studiengang'; + public $userDataDbField = 'abschluss_id'; + + public $sortOrder = 1; + + /** + * @see UserFilterField::__construct + */ + public function __construct($fieldId = '') + { + parent::__construct($fieldId); + $this->relations = [ + 'SubjectCondition' => [ + 'local_field' => 'fach_id', + 'foreign_field' => 'fach_id' + ] + ]; + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return _('Abschluss'); + } + +} diff --git a/lib/classes/admission/userfilter/PermissionCondition.class.php b/lib/classes/admission/userfilter/PermissionCondition.class.php deleted file mode 100644 index 4adfdbf..0000000 --- a/lib/classes/admission/userfilter/PermissionCondition.class.php +++ /dev/null @@ -1,46 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class PermissionCondition extends UserFilterField -{ - public $sortOrder = 7; - - /** - * @see UserFilterField::__construct - */ - public function __construct($fieldId = '') - { - $this->userDataDbTable = 'auth_user_md5'; - $this->userDataDbField = 'perms'; - - parent::__construct($fieldId); - - $this->validValues = [ - 'autor' => _('Student/in'), - 'tutor' => _('Tutor/in'), - 'dozent' => _('Lehrende/r') - ]; - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return _('Globaler Status'); - } -} diff --git a/lib/classes/admission/userfilter/PermissionCondition.php b/lib/classes/admission/userfilter/PermissionCondition.php new file mode 100644 index 0000000..4adfdbf --- /dev/null +++ b/lib/classes/admission/userfilter/PermissionCondition.php @@ -0,0 +1,46 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class PermissionCondition extends UserFilterField +{ + public $sortOrder = 7; + + /** + * @see UserFilterField::__construct + */ + public function __construct($fieldId = '') + { + $this->userDataDbTable = 'auth_user_md5'; + $this->userDataDbField = 'perms'; + + parent::__construct($fieldId); + + $this->validValues = [ + 'autor' => _('Student/in'), + 'tutor' => _('Tutor/in'), + 'dozent' => _('Lehrende/r') + ]; + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return _('Globaler Status'); + } +} diff --git a/lib/classes/admission/userfilter/SemesterOfStudyCondition.class.php b/lib/classes/admission/userfilter/SemesterOfStudyCondition.class.php deleted file mode 100644 index 2c1233a..0000000 --- a/lib/classes/admission/userfilter/SemesterOfStudyCondition.class.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class SemesterOfStudyCondition extends UserFilterField -{ - // --- ATTRIBUTES --- - public $valuesDbTable = 'user_studiengang'; - public $valuesDbIdField = 'semester'; - public $userDataDbTable = 'user_studiengang'; - public $userDataDbField = 'semester'; - - public $sortOrder = 4; - - // --- OPERATIONS --- - - /** - * @see UserFilterField::__construct - */ - public function __construct($fieldId='') - { - parent::__construct($fieldId); - $this->validValues = []; - $this->relations = [ - 'DegreeCondition' => [ - 'local_field' => 'abschluss_id', - 'foreign_field' => 'abschluss_id' - ], - 'SubjectCondition' => [ - 'local_field' => 'fach_id', - 'foreign_field' => 'fach_id' - ] - ]; - $this->validCompareOperators = [ - '>=' => _('mindestens'), - '<=' => _('höchstens'), - '=' => _('ist'), - '!=' => _('ist nicht') - ]; - if (isset(self::$cached_valid_values[static::class])) { - $this->validValues = self::$cached_valid_values[static::class]; - } else { - // Initialize to some value in case there are no semester numbers. - $maxsem = 15; - // Calculate the maximal available semester. - $stmt = DBManager::get()->query("SELECT MAX(" . $this->valuesDbIdField . ") AS maxsem " . - "FROM `" . $this->valuesDbTable . "`"); - if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - if ($current['maxsem']) { - $maxsem = $current['maxsem']; - } - } - for ($i = 1; $i <= $maxsem; $i++) { - $this->validValues[$i] = $i; - } - self::$cached_valid_values[static::class] = $this->validValues; - } - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return _('Fachsemester'); - } - -} diff --git a/lib/classes/admission/userfilter/SemesterOfStudyCondition.php b/lib/classes/admission/userfilter/SemesterOfStudyCondition.php new file mode 100644 index 0000000..2c1233a --- /dev/null +++ b/lib/classes/admission/userfilter/SemesterOfStudyCondition.php @@ -0,0 +1,81 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class SemesterOfStudyCondition extends UserFilterField +{ + // --- ATTRIBUTES --- + public $valuesDbTable = 'user_studiengang'; + public $valuesDbIdField = 'semester'; + public $userDataDbTable = 'user_studiengang'; + public $userDataDbField = 'semester'; + + public $sortOrder = 4; + + // --- OPERATIONS --- + + /** + * @see UserFilterField::__construct + */ + public function __construct($fieldId='') + { + parent::__construct($fieldId); + $this->validValues = []; + $this->relations = [ + 'DegreeCondition' => [ + 'local_field' => 'abschluss_id', + 'foreign_field' => 'abschluss_id' + ], + 'SubjectCondition' => [ + 'local_field' => 'fach_id', + 'foreign_field' => 'fach_id' + ] + ]; + $this->validCompareOperators = [ + '>=' => _('mindestens'), + '<=' => _('höchstens'), + '=' => _('ist'), + '!=' => _('ist nicht') + ]; + if (isset(self::$cached_valid_values[static::class])) { + $this->validValues = self::$cached_valid_values[static::class]; + } else { + // Initialize to some value in case there are no semester numbers. + $maxsem = 15; + // Calculate the maximal available semester. + $stmt = DBManager::get()->query("SELECT MAX(" . $this->valuesDbIdField . ") AS maxsem " . + "FROM `" . $this->valuesDbTable . "`"); + if ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + if ($current['maxsem']) { + $maxsem = $current['maxsem']; + } + } + for ($i = 1; $i <= $maxsem; $i++) { + $this->validValues[$i] = $i; + } + self::$cached_valid_values[static::class] = $this->validValues; + } + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return _('Fachsemester'); + } + +} diff --git a/lib/classes/admission/userfilter/StgteilVersionCondition.class.php b/lib/classes/admission/userfilter/StgteilVersionCondition.class.php deleted file mode 100644 index f6348e5..0000000 --- a/lib/classes/admission/userfilter/StgteilVersionCondition.class.php +++ /dev/null @@ -1,83 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class StgteilVersionCondition extends UserFilterField -{ - // --- ATTRIBUTES --- - public $valuesDbTable = 'mvv_stgteilversion'; - public $valuesDbIdField = 'version_id'; - public $valuesDbNameField = 'code'; - public $userDataDbTable = 'user_studiengang'; - public $userDataDbField = 'version_id'; - - public $sortOrder = 5; - - public static $isParameterized = true; - - public static function getParameterizedTypes() - { - if (Config::get()->DISPLAY_STGTEILVERSION_USERFILTER) { - $filter = new StgteilVersionCondition; - $fields['StgteilVersionCondition'] = $filter->getName(); - return $fields; - } else { - return []; - } - } - - /** - * @see UserFilterField::__construct - */ - public function __construct($fieldId = '') - { - $this->validCompareOperators = [ - '=' => _('ist'), - '!=' => _('ist nicht') - ]; - if ($this->valuesDbNameField) { - // Get all available values from database. - $stmt = DBManager::get()->query( - "SELECT DISTINCT `version_id`, `fach`.`name` ". - "FROM `mvv_stgteilversion` LEFT JOIN mvv_stgteil USING (stgteil_id)". - "LEFT JOIN fach USING (fach_id)". - "WHERE `mvv_stgteilversion`.`stat` = 'genehmigt' ORDER BY `fach`.`name` ASC"); - - while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { - $this->validValues[$current[$this->valuesDbIdField]] = $current[$this->valuesDbNameField]; - } - } - if ($fieldId) { - $this->id = $fieldId; - $this->load(); - } else { - $this->id = $this->generateId(); - } - - foreach ($this->validValues as $version_id => $name) { - $stgteilversion = StgteilVersion::find($version_id); - $this->validValues[$version_id] = $stgteilversion->getDisplayName(); - } - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return _('Studiengangteil-Version'); - } -} diff --git a/lib/classes/admission/userfilter/StgteilVersionCondition.php b/lib/classes/admission/userfilter/StgteilVersionCondition.php new file mode 100644 index 0000000..f6348e5 --- /dev/null +++ b/lib/classes/admission/userfilter/StgteilVersionCondition.php @@ -0,0 +1,83 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class StgteilVersionCondition extends UserFilterField +{ + // --- ATTRIBUTES --- + public $valuesDbTable = 'mvv_stgteilversion'; + public $valuesDbIdField = 'version_id'; + public $valuesDbNameField = 'code'; + public $userDataDbTable = 'user_studiengang'; + public $userDataDbField = 'version_id'; + + public $sortOrder = 5; + + public static $isParameterized = true; + + public static function getParameterizedTypes() + { + if (Config::get()->DISPLAY_STGTEILVERSION_USERFILTER) { + $filter = new StgteilVersionCondition; + $fields['StgteilVersionCondition'] = $filter->getName(); + return $fields; + } else { + return []; + } + } + + /** + * @see UserFilterField::__construct + */ + public function __construct($fieldId = '') + { + $this->validCompareOperators = [ + '=' => _('ist'), + '!=' => _('ist nicht') + ]; + if ($this->valuesDbNameField) { + // Get all available values from database. + $stmt = DBManager::get()->query( + "SELECT DISTINCT `version_id`, `fach`.`name` ". + "FROM `mvv_stgteilversion` LEFT JOIN mvv_stgteil USING (stgteil_id)". + "LEFT JOIN fach USING (fach_id)". + "WHERE `mvv_stgteilversion`.`stat` = 'genehmigt' ORDER BY `fach`.`name` ASC"); + + while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->validValues[$current[$this->valuesDbIdField]] = $current[$this->valuesDbNameField]; + } + } + if ($fieldId) { + $this->id = $fieldId; + $this->load(); + } else { + $this->id = $this->generateId(); + } + + foreach ($this->validValues as $version_id => $name) { + $stgteilversion = StgteilVersion::find($version_id); + $this->validValues[$version_id] = $stgteilversion->getDisplayName(); + } + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return _('Studiengangteil-Version'); + } +} diff --git a/lib/classes/admission/userfilter/SubjectCondition.class.php b/lib/classes/admission/userfilter/SubjectCondition.class.php deleted file mode 100644 index ab44d49..0000000 --- a/lib/classes/admission/userfilter/SubjectCondition.class.php +++ /dev/null @@ -1,52 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class SubjectCondition extends UserFilterField -{ - // --- ATTRIBUTES --- - public $valuesDbTable = 'fach'; - public $valuesDbIdField = 'fach_id'; - public $valuesDbNameField = 'name'; - public $userDataDbTable = 'user_studiengang'; - public $userDataDbField = 'fach_id'; - - public $sortOrder = 2; - - // --- OPERATIONS --- - - /** - * @see UserFilterField::__construct - */ - public function __construct($fieldId = '') - { - parent::__construct($fieldId); - $this->relations = [ - 'DegreeCondition' => [ - 'local_field' => 'abschluss_id', - 'foreign_field' => 'abschluss_id' - ] - ]; - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return _('Studienfach'); - } -} diff --git a/lib/classes/admission/userfilter/SubjectCondition.php b/lib/classes/admission/userfilter/SubjectCondition.php new file mode 100644 index 0000000..ab44d49 --- /dev/null +++ b/lib/classes/admission/userfilter/SubjectCondition.php @@ -0,0 +1,52 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class SubjectCondition extends UserFilterField +{ + // --- ATTRIBUTES --- + public $valuesDbTable = 'fach'; + public $valuesDbIdField = 'fach_id'; + public $valuesDbNameField = 'name'; + public $userDataDbTable = 'user_studiengang'; + public $userDataDbField = 'fach_id'; + + public $sortOrder = 2; + + // --- OPERATIONS --- + + /** + * @see UserFilterField::__construct + */ + public function __construct($fieldId = '') + { + parent::__construct($fieldId); + $this->relations = [ + 'DegreeCondition' => [ + 'local_field' => 'abschluss_id', + 'foreign_field' => 'abschluss_id' + ] + ]; + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return _('Studienfach'); + } +} diff --git a/lib/classes/admission/userfilter/SubjectConditionAny.class.php b/lib/classes/admission/userfilter/SubjectConditionAny.class.php deleted file mode 100644 index 107c86c..0000000 --- a/lib/classes/admission/userfilter/SubjectConditionAny.class.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once realpath(__DIR__ . '/..') . '/UserFilterField.class.php'; - -class SubjectConditionAny extends UserFilterField -{ - // --- ATTRIBUTES --- - public $userDataDbTable = 'user_studiengang'; - public $userDataDbField = 'fach_id'; - - public $sortOrder = 3; - - // --- OPERATIONS --- - - /** - * @see UserFilterField::__construct - */ - public function __construct($fieldId = '') - { - parent::__construct($fieldId); - $this->validCompareOperators = [ - '!=' => ' ' - ]; - $this->validValues = ['' => ' ']; - } - - /** - * Get this field's display name. - * - * @return String - */ - public function getName() - { - return _('Alle Studienfächer'); - } -} diff --git a/lib/classes/admission/userfilter/SubjectConditionAny.php b/lib/classes/admission/userfilter/SubjectConditionAny.php new file mode 100644 index 0000000..107c86c --- /dev/null +++ b/lib/classes/admission/userfilter/SubjectConditionAny.php @@ -0,0 +1,50 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +require_once realpath(__DIR__ . '/..') . '/UserFilterField.class.php'; + +class SubjectConditionAny extends UserFilterField +{ + // --- ATTRIBUTES --- + public $userDataDbTable = 'user_studiengang'; + public $userDataDbField = 'fach_id'; + + public $sortOrder = 3; + + // --- OPERATIONS --- + + /** + * @see UserFilterField::__construct + */ + public function __construct($fieldId = '') + { + parent::__construct($fieldId); + $this->validCompareOperators = [ + '!=' => ' ' + ]; + $this->validValues = ['' => ' ']; + } + + /** + * Get this field's display name. + * + * @return String + */ + public function getName() + { + return _('Alle Studienfächer'); + } +} diff --git a/lib/classes/auth_plugins/StudipAuthAbstract.class.php b/lib/classes/auth_plugins/StudipAuthAbstract.class.php deleted file mode 100644 index 36c75df..0000000 --- a/lib/classes/auth_plugins/StudipAuthAbstract.class.php +++ /dev/null @@ -1,578 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** - * abstract base class for authentication plugins - * - * abstract base class for authentication plugins - * to write your own authentication plugin, derive it from this class and - * implement the following abstract methods: isUsedUsername($username) and - * isAuthenticated($username, $password, $jscript) - * don't forget to call the parents constructor if you implement your own, php - * won't do that for you ! - * - * @abstract - * @author André Noack - * @package - */ -class StudipAuthAbstract -{ - - /** - * contains error message, if authentication fails - * - * - * @var string $error_msg - */ - public $error_msg; - - /** - * indicates whether the authenticated user logs in for the first time - * - * - * @var bool $is_new_user - */ - public $is_new_user = false; - - /** - * array of user domains to assign to each user, can be set in local.inc - * - * @access public - * @var array $user_domains - */ - public $user_domains; - - /** - * associative array with mapping for database fields - * - * associative array with mapping for database fields, - * should be set in local.inc - * structure : - * array('.' => array( 'callback' => '', - * 'map_args' => '')) - * @var array $user_data_mapping - */ - public $user_data_mapping = null; - - /** - * name of the plugin - * - * name of the plugin (last part of class name) is set in the constructor - * @var string $plugin_name - */ - public $plugin_name; - - /** - * text, which precedes error message for the plugin - * - * - * @var string $error_head - */ - public $error_head; - - /** - * toggles display of standard login - * - * - * @var bool $show_login - */ - public $show_login; - - /** - * @var $plugin_instances - */ - private static $plugin_instances; - - private $config_data = []; - - /** - * static method to instantiate and retrieve a reference to an object (singleton) - * - * always use this method to instantiate a plugin object, it will ensure that only one object of each - * plugin will exist - * @param string $plugin_name name of plugin, if omitted an array with all plugin objects will be returned - * @return mixed either a reference to the plugin with the passed name, or an array with references to all plugins - */ - public static function getInstance($plugin_name = false) - { - if (!is_array(self::$plugin_instances)) { - foreach ($GLOBALS['STUDIP_AUTH_PLUGIN'] as $plugin) { - $config = $GLOBALS['STUDIP_AUTH_CONFIG_' . strtoupper($plugin)]; - $plugin_class = $config['plugin_class'] ?? 'StudipAuth' . $plugin; - if (empty($config['plugin_name'])) { - $config['plugin_name'] = strtolower($plugin); - } - self::$plugin_instances[strtoupper($plugin)] = new $plugin_class($config); - } - } - return ($plugin_name) ? self::$plugin_instances[strtoupper($plugin_name)]??null : self::$plugin_instances; - } - - /** - * static method to check if SSO login is enabled - * - * @return bool - */ - public static function isSSOEnabled(): bool - { - self::getInstance(); - foreach (self::$plugin_instances as $auth_plugin) { - if ($auth_plugin instanceof StudipAuthSSO) { - return true; - } - } - return false; - } - - /** - * static method to check if standard login is enabled - * - * @return bool - */ - public static function isLoginEnabled(): bool - { - self::getInstance(); - foreach (self::$plugin_instances as $auth_plugin) { - if ($auth_plugin->show_login === true) { - return true; - } - } - return false; - } - - /** - * static method to check authentication in all plugins - * - * if authentication fails in one plugin, the error message is stored and the next plugin is used - * if authentication succeeds, the uid element in the returned array will contain the Stud.IP user id - * - * @param string $username the username to check - * @param string $password the password to check - * @return array structure: array('uid'=>'string ','error'=>'string ','is_new_user'=>'bool') - */ - public static function CheckAuthentication($username, $password) - { - - $plugins = StudipAuthAbstract::GetInstance(); - $error = false; - $uid = false; - foreach ($plugins as $object) { - // SSO plugins can't be used - if ($object instanceof StudipAuthSSO) { - continue; - } - if ($user = $object->authenticateUser($username, $password)) { - if ($user) { - $uid = $user->id; - $locked = $user['locked']; - $key = $user['validation_key']; - $checkIPRange = ($GLOBALS['ENABLE_ADMIN_IP_CHECK'] && $user['perms'] === 'admin') - || ($GLOBALS['ENABLE_ROOT_IP_CHECK'] && $user['perms'] === 'root'); - - if ($user->isExpired()) { - $error .= _('Dieses Benutzerkonto ist abgelaufen.
Wenden Sie sich bitte an die Administration.') . '
'; - return ['uid' => false, 'error' => $error]; - } else if ($locked) { - $error .= _('Dieser Benutzer ist gesperrt! Wenden Sie sich bitte an die Administration.') . '
'; - return ['uid' => false, 'error' => $error]; - } else if ($key != '') { - return ['uid' => $uid, 'user' => $user, 'error' => $error, 'need_email_activation' => $uid]; - } else if ($checkIPRange && !self::CheckIPRange()) { - $error .= _('Der Login in Ihren Account ist aus diesem Netzwerk nicht erlaubt.') . '
'; - return ['uid' => false, 'error' => $error]; - } - } - return ['uid' => $uid, 'user' => $user, 'error' => $error, 'is_new_user' => $object->is_new_user]; - } else { - $error .= (($object->error_head) ? ('' . $object->error_head . ': ') : '') . $object->error_msg . '
'; - } - } - return ['uid' => $uid, 'error' => $error]; - } - - /** - * static method to check if passed username is used in external data sources - * - * all plugins are checked, the error messages are stored and returned - * - * @param string $username the username - * @return array - */ - public static function CheckUsername($username) - { - $plugins = StudipAuthAbstract::GetInstance(); - $error = false; - $found = false; - foreach ($plugins as $object) { - if ($found = $object->isUsedUsername($username)) { - return ['found' => $found, 'error' => $error]; - } else { - $error .= (($object->error_head) ? ('' . $object->error_head . ': ') : '') . $object->error_msg . '
'; - } - } - return ['found' => $found, 'error' => $error]; - } - - /** - * static method to check for a mapped field - * - * this method checks in the plugin with the passed name, if the passed - * Stud.IP DB field is mapped to an external data source - * - * @param string the name of the db field must be in form '
.' - * @param string the name of the plugin to check - * @return bool true if the field is mapped, else false - */ - public static function CheckField($field_name, $plugin_name) - { - if (!$plugin_name) { - return false; - } - $plugin = StudipAuthAbstract::GetInstance($plugin_name); - return (is_object($plugin) ? $plugin->isMappedField($field_name) : false); - } - - /** - * static method to check if ip address belongs to allowed range - * - * @return bool true if the client ip address is within the valid range - */ - public static function CheckIPRange() - { - $ip = $_SERVER['REMOTE_ADDR']; - $version = substr_count($ip, ':') > 1 ? 'V6' : 'V4'; // valid ip v6 addresses have atleast two colons - $method = 'CheckIPRange' . $version; - if (is_array($GLOBALS['LOGIN_IP_RANGES'][$version])) { - foreach ($GLOBALS['LOGIN_IP_RANGES'][$version] as $range) { - if (self::$method($ip, $range)) { - return true; - } - } - } - return false; - } - - /** - * @param $ip string IPv4 adress - * @param $range array assoc array with [start] & [end] - * @return bool - */ - public static function CheckIPRangeV4($ip, $range) - { - $ipv4 = ip2long($ip); - if ($ipv4 === false) { - return false; // invalid ip address - } - - $start = ip2long($range['start']); - $end = ip2long($range['end']); - - return $ipv4 >= $start && $ipv4 <= $end; - } - - /** - * @param $ip string IPv6 address - * @param $range array assoc array with [start] & [end] - * @return bool - */ - public static function CheckIPRangeV6($ip, $range) - { - $ipv6 = inet_pton($ip); - if ($ipv6 === false) { - return false; // invalid ip address - } - - $start = inet_pton($range['start']); - $end = inet_pton($range['end']); - - return strlen($ipv6) === strlen($start) - && $ipv6 >= $start && $ipv6 <= $end; - } - - /** - * Constructor - * - * you should use StudipAuthAbstract::GetInstance($plugin_name) - * to get a reference to a plugin object. Make sure the constructor in the base class is called - * when deriving your own plugin class, it assigns the settings from local.inc as members of the plugin - * each key of the $STUDIP_AUTH_CONFIG_ array will become a member of the object - * - * @param array $config - */ - public function __construct($config = []) - { - //get configuration array set in local inc - if (empty($config)) { - $this->plugin_name = strtolower(substr(get_class($this), 10)); - $config = $GLOBALS['STUDIP_AUTH_CONFIG_' . strtoupper($this->plugin_name)]; - } - //assign each key in the config array as a member of the plugin object - foreach ($config as $key => $value) { - $this->$key = $value; - } - } - - /** - * authentication method - * - * this method authenticates the passed username, it is used by StudipAuthAbstract::CheckAuthentication() - * if authentication succeeds it calls StudipAuthAbstract::doDataMapping() to map data fields - * if the authenticated user logs in for the first time it calls StudipAuthAbstract::doNewUserInit() to - * initialize the new user - * @param string $username the username to check - * @param string $password the password to check - * @return string if authentication succeeds the Stud.IP user , else false - */ - public function authenticateUser($username, $password) - { - $username = $this->verifyUsername($username); - if ($this->isAuthenticated($username, $password)) { - if ($user = $this->getStudipUser($username)) { - $this->doDataMapping($user); - if ($this->is_new_user) { - $this->doNewUserInit($user); - } - $this->setUserDomains($user); - } - return $user; - } else { - return false; - } - } - - /** - * method to retrieve the Stud.IP user id to a given username - * - * - * @access private - * @param string the username - * @return User the Stud.IP or false if an error occurs - */ - function getStudipUser($username) - { - $user = User::findByUsername($username); - if ($user) { - $auth_plugin = $user->auth_plugin; - if ($auth_plugin === null) { - $this->error_msg = _('Dies ist ein vorläufiger Benutzer.') . '
'; - return false; - } - if ($auth_plugin != $this->plugin_name) { - $this->error_msg = sprintf(_('Dieser Benutzername wird bereits über %s authentifiziert!'), $auth_plugin) . '
'; - return false; - } - return $user; - } - $new_user = new User(); - $new_user->username = $username; - $new_user->perms = 'autor'; - $new_user->auth_plugin = $this->plugin_name; - $new_user->preferred_language = $_SESSION['_language']; - if ($new_user->store()) { - $this->is_new_user = true; - return $new_user; - } - } - - /** - * initialize a new user - * - * this method is invoked for one time, if a new user logs in ($this->is_new_user is true) - * place special treatment of new users here - * - * @access private - * @param User $user the user object - */ - function doNewUserInit($user) - { - // auto insertion of new users, according to $AUTO_INSERT_SEM[] (defined in local.inc) - AutoInsert::instance()->saveUser($user->id, $user->perms); - } - - /** - * This method sets the user domains for the current user. - * - * @access private - * @param User the user object - */ - function setUserDomains($user) - { - $user_domains = $this->getUserDomains(); - $uid = $user->id; - if (isset($user_domains)) { - $old_domains = UserDomain::getUserDomainsForUser($uid); - - foreach ($old_domains as $domain) { - if (!in_array($domain->id, $user_domains)) { - $domain->removeUser($uid); - } - } - - foreach ($user_domains as $user_domain) { - $domain = new UserDomain($user_domain); - - if ($domain->isNew()) { - $domain->name = $user_domain; - $domain->store(); - } - - if (!in_array($domain, $old_domains)) { - $domain->addUser($uid); - } - } - } - } - - /** - * Get the user domains to assign to the current user. - */ - function getUserDomains() - { - return $this->user_domains; - } - - /** - * this method handles the data mapping - * - * for each entry in $this->user_data_mapping the according callback will be invoked - * the return value of the callback method is then written to the db field, which is specified - * in the key of the array - * - * @access private - * @param User the user object - * @return bool - */ - function doDataMapping($user) - { - if ($user && is_array($this->user_data_mapping)) { - foreach ($this->user_data_mapping as $key => $value) { - $callback = null; - if (method_exists($this, $value['callback'])) { - $callback = [$this, $value['callback']]; - } else if (is_callable($value['callback'])) { - $callback = $value['callback']; - } - if ($callback) { - $split = explode('.', $key); - $table = $split[0]; - $field = $split[1]; - if ($table === 'auth_user_md5' || $table === 'user_info') { - $mapped_value = call_user_func($callback, $value['map_args']); - if (isset($mapped_value)) { - $user->setValue($field, $mapped_value); - } - } else { - call_user_func($callback, [$table, $field, $user, $value['map_args']]); - } - } - } - return $user->store(); - } - return false; - } - - /** - * method to check, if a given db field is mapped by the plugin - * - * - * @access private - * @param string the name of the db field (.) - * @return bool true if the field is mapped - */ - function isMappedField($name) - { - return isset($this->user_data_mapping[$name]); - } - - /** - * method to eliminate bad characters in the given username - * - * - * @access private - * @param string the username - * @return string the username - */ - function verifyUsername($username) - { - if ($this->username_case_insensitiv) { - $username = mb_strtolower($username); - } - if ($this->bad_char_regex) { - return preg_replace($this->bad_char_regex, '', $username); - } else { - return trim($username); - } - } - - /** - * method to check, if username is used - * - * abstract MUST be realized - * - * @access private - * @param string the username - * @return bool true if the username exists - */ - function isUsedUsername($username) - { - $this->error_msg = sprintf(_('Methode %s nicht implementiert!'), get_class($this) . '::isUsedUsername()'); - return false; - } - - /** - * method to check the authentication of a given username and a given password - * - * abstract, MUST be realized - * - * @access private - * @param string the username - * @param string the password - * @return bool true if authentication succeeds - */ - function isAuthenticated($username, $password) - { - $this->error = sprintf(_('Methode %s nicht implementiert!'), get_class($this) . '::isAuthenticated()'); - return false; - } - - // Store dynamically set dynamically created properties in $config_data - public function __isset($offset) - { - return isset($this->config_data[$offset]); - } - - public function __set($offset, $value) - { - $this->config_data[$offset] = $value; - } - - public function __get($offset) - { - return $this->config_data[$offset] ?? null; - } - - public function __unset($offset) - { - unset($this->config_data[$offset]); - } -} diff --git a/lib/classes/auth_plugins/StudipAuthAbstract.php b/lib/classes/auth_plugins/StudipAuthAbstract.php new file mode 100644 index 0000000..36c75df --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthAbstract.php @@ -0,0 +1,578 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** + * abstract base class for authentication plugins + * + * abstract base class for authentication plugins + * to write your own authentication plugin, derive it from this class and + * implement the following abstract methods: isUsedUsername($username) and + * isAuthenticated($username, $password, $jscript) + * don't forget to call the parents constructor if you implement your own, php + * won't do that for you ! + * + * @abstract + * @author André Noack + * @package + */ +class StudipAuthAbstract +{ + + /** + * contains error message, if authentication fails + * + * + * @var string $error_msg + */ + public $error_msg; + + /** + * indicates whether the authenticated user logs in for the first time + * + * + * @var bool $is_new_user + */ + public $is_new_user = false; + + /** + * array of user domains to assign to each user, can be set in local.inc + * + * @access public + * @var array $user_domains + */ + public $user_domains; + + /** + * associative array with mapping for database fields + * + * associative array with mapping for database fields, + * should be set in local.inc + * structure : + * array('
.' => array( 'callback' => '', + * 'map_args' => '')) + * @var array $user_data_mapping + */ + public $user_data_mapping = null; + + /** + * name of the plugin + * + * name of the plugin (last part of class name) is set in the constructor + * @var string $plugin_name + */ + public $plugin_name; + + /** + * text, which precedes error message for the plugin + * + * + * @var string $error_head + */ + public $error_head; + + /** + * toggles display of standard login + * + * + * @var bool $show_login + */ + public $show_login; + + /** + * @var $plugin_instances + */ + private static $plugin_instances; + + private $config_data = []; + + /** + * static method to instantiate and retrieve a reference to an object (singleton) + * + * always use this method to instantiate a plugin object, it will ensure that only one object of each + * plugin will exist + * @param string $plugin_name name of plugin, if omitted an array with all plugin objects will be returned + * @return mixed either a reference to the plugin with the passed name, or an array with references to all plugins + */ + public static function getInstance($plugin_name = false) + { + if (!is_array(self::$plugin_instances)) { + foreach ($GLOBALS['STUDIP_AUTH_PLUGIN'] as $plugin) { + $config = $GLOBALS['STUDIP_AUTH_CONFIG_' . strtoupper($plugin)]; + $plugin_class = $config['plugin_class'] ?? 'StudipAuth' . $plugin; + if (empty($config['plugin_name'])) { + $config['plugin_name'] = strtolower($plugin); + } + self::$plugin_instances[strtoupper($plugin)] = new $plugin_class($config); + } + } + return ($plugin_name) ? self::$plugin_instances[strtoupper($plugin_name)]??null : self::$plugin_instances; + } + + /** + * static method to check if SSO login is enabled + * + * @return bool + */ + public static function isSSOEnabled(): bool + { + self::getInstance(); + foreach (self::$plugin_instances as $auth_plugin) { + if ($auth_plugin instanceof StudipAuthSSO) { + return true; + } + } + return false; + } + + /** + * static method to check if standard login is enabled + * + * @return bool + */ + public static function isLoginEnabled(): bool + { + self::getInstance(); + foreach (self::$plugin_instances as $auth_plugin) { + if ($auth_plugin->show_login === true) { + return true; + } + } + return false; + } + + /** + * static method to check authentication in all plugins + * + * if authentication fails in one plugin, the error message is stored and the next plugin is used + * if authentication succeeds, the uid element in the returned array will contain the Stud.IP user id + * + * @param string $username the username to check + * @param string $password the password to check + * @return array structure: array('uid'=>'string ','error'=>'string ','is_new_user'=>'bool') + */ + public static function CheckAuthentication($username, $password) + { + + $plugins = StudipAuthAbstract::GetInstance(); + $error = false; + $uid = false; + foreach ($plugins as $object) { + // SSO plugins can't be used + if ($object instanceof StudipAuthSSO) { + continue; + } + if ($user = $object->authenticateUser($username, $password)) { + if ($user) { + $uid = $user->id; + $locked = $user['locked']; + $key = $user['validation_key']; + $checkIPRange = ($GLOBALS['ENABLE_ADMIN_IP_CHECK'] && $user['perms'] === 'admin') + || ($GLOBALS['ENABLE_ROOT_IP_CHECK'] && $user['perms'] === 'root'); + + if ($user->isExpired()) { + $error .= _('Dieses Benutzerkonto ist abgelaufen.
Wenden Sie sich bitte an die Administration.') . '
'; + return ['uid' => false, 'error' => $error]; + } else if ($locked) { + $error .= _('Dieser Benutzer ist gesperrt! Wenden Sie sich bitte an die Administration.') . '
'; + return ['uid' => false, 'error' => $error]; + } else if ($key != '') { + return ['uid' => $uid, 'user' => $user, 'error' => $error, 'need_email_activation' => $uid]; + } else if ($checkIPRange && !self::CheckIPRange()) { + $error .= _('Der Login in Ihren Account ist aus diesem Netzwerk nicht erlaubt.') . '
'; + return ['uid' => false, 'error' => $error]; + } + } + return ['uid' => $uid, 'user' => $user, 'error' => $error, 'is_new_user' => $object->is_new_user]; + } else { + $error .= (($object->error_head) ? ('' . $object->error_head . ': ') : '') . $object->error_msg . '
'; + } + } + return ['uid' => $uid, 'error' => $error]; + } + + /** + * static method to check if passed username is used in external data sources + * + * all plugins are checked, the error messages are stored and returned + * + * @param string $username the username + * @return array + */ + public static function CheckUsername($username) + { + $plugins = StudipAuthAbstract::GetInstance(); + $error = false; + $found = false; + foreach ($plugins as $object) { + if ($found = $object->isUsedUsername($username)) { + return ['found' => $found, 'error' => $error]; + } else { + $error .= (($object->error_head) ? ('' . $object->error_head . ': ') : '') . $object->error_msg . '
'; + } + } + return ['found' => $found, 'error' => $error]; + } + + /** + * static method to check for a mapped field + * + * this method checks in the plugin with the passed name, if the passed + * Stud.IP DB field is mapped to an external data source + * + * @param string the name of the db field must be in form '
.' + * @param string the name of the plugin to check + * @return bool true if the field is mapped, else false + */ + public static function CheckField($field_name, $plugin_name) + { + if (!$plugin_name) { + return false; + } + $plugin = StudipAuthAbstract::GetInstance($plugin_name); + return (is_object($plugin) ? $plugin->isMappedField($field_name) : false); + } + + /** + * static method to check if ip address belongs to allowed range + * + * @return bool true if the client ip address is within the valid range + */ + public static function CheckIPRange() + { + $ip = $_SERVER['REMOTE_ADDR']; + $version = substr_count($ip, ':') > 1 ? 'V6' : 'V4'; // valid ip v6 addresses have atleast two colons + $method = 'CheckIPRange' . $version; + if (is_array($GLOBALS['LOGIN_IP_RANGES'][$version])) { + foreach ($GLOBALS['LOGIN_IP_RANGES'][$version] as $range) { + if (self::$method($ip, $range)) { + return true; + } + } + } + return false; + } + + /** + * @param $ip string IPv4 adress + * @param $range array assoc array with [start] & [end] + * @return bool + */ + public static function CheckIPRangeV4($ip, $range) + { + $ipv4 = ip2long($ip); + if ($ipv4 === false) { + return false; // invalid ip address + } + + $start = ip2long($range['start']); + $end = ip2long($range['end']); + + return $ipv4 >= $start && $ipv4 <= $end; + } + + /** + * @param $ip string IPv6 address + * @param $range array assoc array with [start] & [end] + * @return bool + */ + public static function CheckIPRangeV6($ip, $range) + { + $ipv6 = inet_pton($ip); + if ($ipv6 === false) { + return false; // invalid ip address + } + + $start = inet_pton($range['start']); + $end = inet_pton($range['end']); + + return strlen($ipv6) === strlen($start) + && $ipv6 >= $start && $ipv6 <= $end; + } + + /** + * Constructor + * + * you should use StudipAuthAbstract::GetInstance($plugin_name) + * to get a reference to a plugin object. Make sure the constructor in the base class is called + * when deriving your own plugin class, it assigns the settings from local.inc as members of the plugin + * each key of the $STUDIP_AUTH_CONFIG_ array will become a member of the object + * + * @param array $config + */ + public function __construct($config = []) + { + //get configuration array set in local inc + if (empty($config)) { + $this->plugin_name = strtolower(substr(get_class($this), 10)); + $config = $GLOBALS['STUDIP_AUTH_CONFIG_' . strtoupper($this->plugin_name)]; + } + //assign each key in the config array as a member of the plugin object + foreach ($config as $key => $value) { + $this->$key = $value; + } + } + + /** + * authentication method + * + * this method authenticates the passed username, it is used by StudipAuthAbstract::CheckAuthentication() + * if authentication succeeds it calls StudipAuthAbstract::doDataMapping() to map data fields + * if the authenticated user logs in for the first time it calls StudipAuthAbstract::doNewUserInit() to + * initialize the new user + * @param string $username the username to check + * @param string $password the password to check + * @return string if authentication succeeds the Stud.IP user , else false + */ + public function authenticateUser($username, $password) + { + $username = $this->verifyUsername($username); + if ($this->isAuthenticated($username, $password)) { + if ($user = $this->getStudipUser($username)) { + $this->doDataMapping($user); + if ($this->is_new_user) { + $this->doNewUserInit($user); + } + $this->setUserDomains($user); + } + return $user; + } else { + return false; + } + } + + /** + * method to retrieve the Stud.IP user id to a given username + * + * + * @access private + * @param string the username + * @return User the Stud.IP or false if an error occurs + */ + function getStudipUser($username) + { + $user = User::findByUsername($username); + if ($user) { + $auth_plugin = $user->auth_plugin; + if ($auth_plugin === null) { + $this->error_msg = _('Dies ist ein vorläufiger Benutzer.') . '
'; + return false; + } + if ($auth_plugin != $this->plugin_name) { + $this->error_msg = sprintf(_('Dieser Benutzername wird bereits über %s authentifiziert!'), $auth_plugin) . '
'; + return false; + } + return $user; + } + $new_user = new User(); + $new_user->username = $username; + $new_user->perms = 'autor'; + $new_user->auth_plugin = $this->plugin_name; + $new_user->preferred_language = $_SESSION['_language']; + if ($new_user->store()) { + $this->is_new_user = true; + return $new_user; + } + } + + /** + * initialize a new user + * + * this method is invoked for one time, if a new user logs in ($this->is_new_user is true) + * place special treatment of new users here + * + * @access private + * @param User $user the user object + */ + function doNewUserInit($user) + { + // auto insertion of new users, according to $AUTO_INSERT_SEM[] (defined in local.inc) + AutoInsert::instance()->saveUser($user->id, $user->perms); + } + + /** + * This method sets the user domains for the current user. + * + * @access private + * @param User the user object + */ + function setUserDomains($user) + { + $user_domains = $this->getUserDomains(); + $uid = $user->id; + if (isset($user_domains)) { + $old_domains = UserDomain::getUserDomainsForUser($uid); + + foreach ($old_domains as $domain) { + if (!in_array($domain->id, $user_domains)) { + $domain->removeUser($uid); + } + } + + foreach ($user_domains as $user_domain) { + $domain = new UserDomain($user_domain); + + if ($domain->isNew()) { + $domain->name = $user_domain; + $domain->store(); + } + + if (!in_array($domain, $old_domains)) { + $domain->addUser($uid); + } + } + } + } + + /** + * Get the user domains to assign to the current user. + */ + function getUserDomains() + { + return $this->user_domains; + } + + /** + * this method handles the data mapping + * + * for each entry in $this->user_data_mapping the according callback will be invoked + * the return value of the callback method is then written to the db field, which is specified + * in the key of the array + * + * @access private + * @param User the user object + * @return bool + */ + function doDataMapping($user) + { + if ($user && is_array($this->user_data_mapping)) { + foreach ($this->user_data_mapping as $key => $value) { + $callback = null; + if (method_exists($this, $value['callback'])) { + $callback = [$this, $value['callback']]; + } else if (is_callable($value['callback'])) { + $callback = $value['callback']; + } + if ($callback) { + $split = explode('.', $key); + $table = $split[0]; + $field = $split[1]; + if ($table === 'auth_user_md5' || $table === 'user_info') { + $mapped_value = call_user_func($callback, $value['map_args']); + if (isset($mapped_value)) { + $user->setValue($field, $mapped_value); + } + } else { + call_user_func($callback, [$table, $field, $user, $value['map_args']]); + } + } + } + return $user->store(); + } + return false; + } + + /** + * method to check, if a given db field is mapped by the plugin + * + * + * @access private + * @param string the name of the db field (.) + * @return bool true if the field is mapped + */ + function isMappedField($name) + { + return isset($this->user_data_mapping[$name]); + } + + /** + * method to eliminate bad characters in the given username + * + * + * @access private + * @param string the username + * @return string the username + */ + function verifyUsername($username) + { + if ($this->username_case_insensitiv) { + $username = mb_strtolower($username); + } + if ($this->bad_char_regex) { + return preg_replace($this->bad_char_regex, '', $username); + } else { + return trim($username); + } + } + + /** + * method to check, if username is used + * + * abstract MUST be realized + * + * @access private + * @param string the username + * @return bool true if the username exists + */ + function isUsedUsername($username) + { + $this->error_msg = sprintf(_('Methode %s nicht implementiert!'), get_class($this) . '::isUsedUsername()'); + return false; + } + + /** + * method to check the authentication of a given username and a given password + * + * abstract, MUST be realized + * + * @access private + * @param string the username + * @param string the password + * @return bool true if authentication succeeds + */ + function isAuthenticated($username, $password) + { + $this->error = sprintf(_('Methode %s nicht implementiert!'), get_class($this) . '::isAuthenticated()'); + return false; + } + + // Store dynamically set dynamically created properties in $config_data + public function __isset($offset) + { + return isset($this->config_data[$offset]); + } + + public function __set($offset, $value) + { + $this->config_data[$offset] = $value; + } + + public function __get($offset) + { + return $this->config_data[$offset] ?? null; + } + + public function __unset($offset) + { + unset($this->config_data[$offset]); + } +} diff --git a/lib/classes/auth_plugins/StudipAuthCAS.class.php b/lib/classes/auth_plugins/StudipAuthCAS.class.php deleted file mode 100644 index 29deb75..0000000 --- a/lib/classes/auth_plugins/StudipAuthCAS.class.php +++ /dev/null @@ -1,89 +0,0 @@ - - * @package - */ - -require_once 'lib/classes/cas/CAS_PGTStorage_Cache.php'; - -class StudipAuthCAS extends StudipAuthSSO -{ - public $host; - public $port; - public $uri; - public $cacert; - - public $userdata; - - /** - * Constructor - */ - public function __construct($config = []) - { - parent::__construct($config); - if (!isset($this->plugin_fullname)) { - $this->plugin_fullname = _('CAS'); - } - if (!isset($this->login_description)) { - $this->login_description = _('für Single Sign On mit CAS'); - } - if (Request::get('sso') === $this->plugin_name) { - if ($this->proxy) { - URLHelper::setBaseUrl($GLOBALS['ABSOLUTE_URI_STUDIP']); - phpCAS::proxy(CAS_VERSION_2_0, $this->host, $this->port, $this->uri, false); - phpCAS::setPGTStorage(new CAS_PGTStorage_Cache(phpCAS::getCasClient())); - phpCAS::setFixedCallbackURL(URLHelper::getURL('dispatch.php/cas/proxy')); - } else { - phpCAS::client(CAS_VERSION_2_0, $this->host, $this->port, $this->uri, false); - } - - if (isset($this->cacert)) { - phpCAS::setCasServerCACert($this->cacert); - } else { - phpCAS::setNoCasServerValidation(); - } - } - } - - /** - * Return the current username. - */ - function getUser() - { - return phpCAS::getUser(); - } - - /** - * Validate the username passed to the auth plugin. - * Note: This triggers authentication if needed. - */ - function verifyUsername($username) - { - phpCAS::forceAuthentication(); - return $this->getUser(); - } - - function getUserData($key) - { - $userdataclassname = $this->user_data_mapping_class; - if (!class_exists($userdataclassname)) { - Log::error($this->plugin_name . ': no userdataclassname specified or found.'); - return; - } - // get the userdata - if (empty($this->userdata)) { - $this->userdata = new $userdataclassname(); - } - return $this->userdata->getUserData($key, phpCAS::getUser()); - } - - function logout() - { - // do a global cas logout - phpCAS::client(CAS_VERSION_2_0, $this->host, $this->port, $this->uri, false); - phpCAS::logout(); - } -} diff --git a/lib/classes/auth_plugins/StudipAuthCAS.php b/lib/classes/auth_plugins/StudipAuthCAS.php new file mode 100644 index 0000000..29deb75 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthCAS.php @@ -0,0 +1,89 @@ + + * @package + */ + +require_once 'lib/classes/cas/CAS_PGTStorage_Cache.php'; + +class StudipAuthCAS extends StudipAuthSSO +{ + public $host; + public $port; + public $uri; + public $cacert; + + public $userdata; + + /** + * Constructor + */ + public function __construct($config = []) + { + parent::__construct($config); + if (!isset($this->plugin_fullname)) { + $this->plugin_fullname = _('CAS'); + } + if (!isset($this->login_description)) { + $this->login_description = _('für Single Sign On mit CAS'); + } + if (Request::get('sso') === $this->plugin_name) { + if ($this->proxy) { + URLHelper::setBaseUrl($GLOBALS['ABSOLUTE_URI_STUDIP']); + phpCAS::proxy(CAS_VERSION_2_0, $this->host, $this->port, $this->uri, false); + phpCAS::setPGTStorage(new CAS_PGTStorage_Cache(phpCAS::getCasClient())); + phpCAS::setFixedCallbackURL(URLHelper::getURL('dispatch.php/cas/proxy')); + } else { + phpCAS::client(CAS_VERSION_2_0, $this->host, $this->port, $this->uri, false); + } + + if (isset($this->cacert)) { + phpCAS::setCasServerCACert($this->cacert); + } else { + phpCAS::setNoCasServerValidation(); + } + } + } + + /** + * Return the current username. + */ + function getUser() + { + return phpCAS::getUser(); + } + + /** + * Validate the username passed to the auth plugin. + * Note: This triggers authentication if needed. + */ + function verifyUsername($username) + { + phpCAS::forceAuthentication(); + return $this->getUser(); + } + + function getUserData($key) + { + $userdataclassname = $this->user_data_mapping_class; + if (!class_exists($userdataclassname)) { + Log::error($this->plugin_name . ': no userdataclassname specified or found.'); + return; + } + // get the userdata + if (empty($this->userdata)) { + $this->userdata = new $userdataclassname(); + } + return $this->userdata->getUserData($key, phpCAS::getUser()); + } + + function logout() + { + // do a global cas logout + phpCAS::client(CAS_VERSION_2_0, $this->host, $this->port, $this->uri, false); + phpCAS::logout(); + } +} diff --git a/lib/classes/auth_plugins/StudipAuthIP.class.php b/lib/classes/auth_plugins/StudipAuthIP.class.php deleted file mode 100644 index e0d6afa..0000000 --- a/lib/classes/auth_plugins/StudipAuthIP.class.php +++ /dev/null @@ -1,20 +0,0 @@ -allowed_users[$username] && in_array($_SERVER['REMOTE_ADDR'], $this->allowed_users[$username]); - } -} diff --git a/lib/classes/auth_plugins/StudipAuthIP.php b/lib/classes/auth_plugins/StudipAuthIP.php new file mode 100644 index 0000000..e0d6afa --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthIP.php @@ -0,0 +1,20 @@ +allowed_users[$username] && in_array($_SERVER['REMOTE_ADDR'], $this->allowed_users[$username]); + } +} diff --git a/lib/classes/auth_plugins/StudipAuthLTI.class.php b/lib/classes/auth_plugins/StudipAuthLTI.class.php deleted file mode 100644 index 07ab8c3..0000000 --- a/lib/classes/auth_plugins/StudipAuthLTI.class.php +++ /dev/null @@ -1,135 +0,0 @@ -consumer_keys[$consumer_key]['allow_domain_override']; - $domain = $this->consumer_keys[$consumer_key]['domain']; - - if (!$username) { - throw new InvalidArgumentException('user_id must not be empty'); - } - - if ($domain === null) { - $domain = $consumer_key; - } - - if ($override && strpos($username, '@') !== false) { - list($username, $domain) = explode('@', $username); - } - - if ($domain !== '') { - $username .= '@' . $domain; - $this->domain = $domain; - } - - return $this->username = parent::verifyUsername($username); - } - - /** - * Check whether this user can be authenticated. Since we trust the user - * information sent by the LTI consumer, only the OAuth signature is checked. - * - * @param string $username account name - * @param string $password (ignored) - * - * @return bool true if authentication succeeds - * - */ - public function isAuthenticated($username, $password) - { - $consumer_key = Request::get('oauth_consumer_key'); - $consumer_secret = $this->consumer_keys[$consumer_key]['consumer_secret']; - - if (!Studip\OAuth1::verifyRequest($this->getPsrRequest(), $consumer_secret, '')) { - return false; - } - - return parent::isAuthenticated($username, $password); - } - - /** - * Authenticate this user and handle auto enrollment. If the URL parameter - * "sem_id" is set, the user is automatically redircted to the enrollment - * action for this course. - * - * @param string $username the username to check - * @param string $password the password (ignored) - * - * @return mixed if authentication succeeds: the Stud.IP user, else false - */ - public function authenticateUser($username, $password) - { - $user = parent::authenticateUser($username, $password); - $course_id = Request::option('sem_id'); - - if ($user && $course_id) { - header('Location: ' . URLHelper::getURL('dispatch.php/lti/index/' . $course_id)); - } - - return $user; - } - - /** - * Return the current username of the pending authentication request. - */ - public function getUser() - { - return $this->username; - } - - /** - * Get the user domains to assign to the current user (if any). - * - * @return array array of user domain names - */ - public function getUserDomains() - { - return $this->domain ? [$this->domain] : null; - } - - /** - * Callback that can be used in user_data_mapping array. For LTI, this is - * equivalent to Request::get(), since all launch data is POST parameters. - * @see http://www.imsglobal.org/specs/ltiv1p1/implementation-guide - * - * @param string key (e.g. "lis_person_contact_email_primary") - * - * @return string parameter value (null if not set) - */ - public function getUserData($key) - { - return Request::get($key); - } -} diff --git a/lib/classes/auth_plugins/StudipAuthLTI.php b/lib/classes/auth_plugins/StudipAuthLTI.php new file mode 100644 index 0000000..07ab8c3 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthLTI.php @@ -0,0 +1,135 @@ +consumer_keys[$consumer_key]['allow_domain_override']; + $domain = $this->consumer_keys[$consumer_key]['domain']; + + if (!$username) { + throw new InvalidArgumentException('user_id must not be empty'); + } + + if ($domain === null) { + $domain = $consumer_key; + } + + if ($override && strpos($username, '@') !== false) { + list($username, $domain) = explode('@', $username); + } + + if ($domain !== '') { + $username .= '@' . $domain; + $this->domain = $domain; + } + + return $this->username = parent::verifyUsername($username); + } + + /** + * Check whether this user can be authenticated. Since we trust the user + * information sent by the LTI consumer, only the OAuth signature is checked. + * + * @param string $username account name + * @param string $password (ignored) + * + * @return bool true if authentication succeeds + * + */ + public function isAuthenticated($username, $password) + { + $consumer_key = Request::get('oauth_consumer_key'); + $consumer_secret = $this->consumer_keys[$consumer_key]['consumer_secret']; + + if (!Studip\OAuth1::verifyRequest($this->getPsrRequest(), $consumer_secret, '')) { + return false; + } + + return parent::isAuthenticated($username, $password); + } + + /** + * Authenticate this user and handle auto enrollment. If the URL parameter + * "sem_id" is set, the user is automatically redircted to the enrollment + * action for this course. + * + * @param string $username the username to check + * @param string $password the password (ignored) + * + * @return mixed if authentication succeeds: the Stud.IP user, else false + */ + public function authenticateUser($username, $password) + { + $user = parent::authenticateUser($username, $password); + $course_id = Request::option('sem_id'); + + if ($user && $course_id) { + header('Location: ' . URLHelper::getURL('dispatch.php/lti/index/' . $course_id)); + } + + return $user; + } + + /** + * Return the current username of the pending authentication request. + */ + public function getUser() + { + return $this->username; + } + + /** + * Get the user domains to assign to the current user (if any). + * + * @return array array of user domain names + */ + public function getUserDomains() + { + return $this->domain ? [$this->domain] : null; + } + + /** + * Callback that can be used in user_data_mapping array. For LTI, this is + * equivalent to Request::get(), since all launch data is POST parameters. + * @see http://www.imsglobal.org/specs/ltiv1p1/implementation-guide + * + * @param string key (e.g. "lis_person_contact_email_primary") + * + * @return string parameter value (null if not set) + */ + public function getUserData($key) + { + return Request::get($key); + } +} diff --git a/lib/classes/auth_plugins/StudipAuthLdap.class.php b/lib/classes/auth_plugins/StudipAuthLdap.class.php deleted file mode 100644 index 7cb8686..0000000 --- a/lib/classes/auth_plugins/StudipAuthLdap.class.php +++ /dev/null @@ -1,216 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** - * Stud.IP authentication against LDAP Server - * - * Stud.IP authentication against LDAP Server - * - * @access public - * @author André Noack - * @package - */ -class StudipAuthLdap extends StudipAuthAbstract -{ - - public $anonymous_bind = true; - - public $host; - public $base_dn; - public $username_attribute = 'uid'; - public $ldap_filter; - public $bad_char_regex = '/[^0-9_a-zA-Z]/'; - public $show_login = true; - - public $conn = null; - public $user_data = null; - - - function getLdapFilter($username) - { - if (isset($this->ldap_filter)) { - list($user, $domain) = explode('@', $username); - $search = ['%u', '%U', '%d', '%%']; - $replace = [$username, $user, $domain, '%']; - - return str_replace($search, $replace, $this->ldap_filter); - } - - return $this->username_attribute . '=' . $username; - } - - function doLdapConnect() - { - if (!($this->conn = ldap_connect($this->host))) { - $this->error_msg = _('Keine Verbindung zum LDAP Server möglich.'); - return false; - } - if (!($r = ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3))) { - $this->error_msg = _('Setzen der LDAP Protokolversion fehlgeschlagen.'); - return false; - } - if ($this->start_tls) { - if (!ldap_start_tls($this->conn)) { - $this->error_msg = _('"Start TLS" fehlgeschlagen.'); - return false; - } - } - return true; - } - - function getUserDn($username) - { - $user_dn = ''; - - if ($this->anonymous_bind) { - if (!($r = @ldap_bind($this->conn))) { - $this->error_msg = _('Anonymer Bind fehlgeschlagen.') . $this->getLdapError(); - return false; - } - if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) { - $this->error_msg = _('Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError(); - return false; - } - if (!ldap_count_entries($this->conn, $result)) { - $this->error_msg = sprintf(_('%s wurde nicht unterhalb von %s gefunden.'), $username, $this->base_dn); - return false; - } - if (!($entry = @ldap_first_entry($this->conn, $result))) { - $this->error_msg = $this->getLdapError(); - return false; - } - if (!($user_dn = @ldap_get_dn($this->conn, $entry))) { - $this->error_msg = $this->getLdapError(); - return false; - } - } else { - $user_dn = $this->username_attribute . '=' . $username . ',' . $this->base_dn; - } - return $user_dn; - } - - function doLdapBind($username, $password) - { - if (!$this->doLdapConnect()) { - return false; - } - if (!($user_dn = $this->getUserDn($username))) { - return false; - } - if (!$password) { - $this->error_msg = _('Kein Passwort eingegeben.'); //some ldap servers seem to allow binding with a user dn and without a password, if anonymous bind is enabled - return false; - } - if (!($r = @ldap_bind($this->conn, $user_dn, $password))) { - if (ldap_errno($this->conn) == 49) { - $this->error_msg = _('Bitte überprüfen Sie ihre Zugangsdaten.'); - } - $this->error_msg = _('Anmeldung fehlgeschlagen.') . $this->getLdapError(); - return false; - } - if (!($result = @ldap_search($this->conn, $user_dn, 'objectclass=*'))) { - $this->error_msg = _('Abholen der Benutzer Attribute fehlgeschlagen.') . $this->getLdapError(); - return false; - } - if (@ldap_count_entries($this->conn, $result)) { - if (!($info = @ldap_get_entries($this->conn, $result))) { - $this->error_msg = $this->getLdapError(); - return false; - } - } - $this->user_data = $info[0]; - return true; - } - - /** - * - * - * - * @access private - * - */ - function isAuthenticated($username, $password) - { - if (!$this->doLdapBind($username, $password)) { - ldap_unbind($this->conn); - return false; - } - ldap_unbind($this->conn); - return true; - } - - - function doLdapMap($map_params) - { - if (isset($this->user_data[$map_params][0])) { - $ret = $this->user_data[$map_params][0]; - if ($ret[0] === ':') { - $ret = base64_decode($ret); - } - } - return $ret; - } - - function doLdapMapDatafield($params) - { - $datafield_id = $params[1]; - $user = $params[2]; - $ldap_field = $this->doLdapMap($params[3]); - if (isset($ldap_field)) { - $df = $user->datafields->findOneBy('datafield_id', $datafield_id); - if ($df) { - $df->content = $ldap_field; - return true; - } - } - } - - function isUsedUsername($username) - { - if (!$this->anonymous_bind) { - $this->error = _('Kann den Benutzernamen nicht überprüfen, anonymous_bind ist ausgeschaltet!'); - return false; - } - if (!$this->doLdapConnect()) { - return false; - } - if (!($r = @ldap_bind($this->conn))) { - $this->error = _('Anonymer Bind fehlgeschlagen.') . $this->getLdapError(); - return false; - } - if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) { - $this->error = _('Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError(); - return false; - } - if (!ldap_count_entries($this->conn, $result)) { - $this->error_msg = _('Der Benutzername wurde nicht gefunden.'); - return false; - } - return true; - } - - function getLdapError() - { - return _('
LDAP Fehler: ') . ldap_error($this->conn) . ' (#' . ldap_errno($this->conn) . ')'; - } -} diff --git a/lib/classes/auth_plugins/StudipAuthLdap.php b/lib/classes/auth_plugins/StudipAuthLdap.php new file mode 100644 index 0000000..7cb8686 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthLdap.php @@ -0,0 +1,216 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** + * Stud.IP authentication against LDAP Server + * + * Stud.IP authentication against LDAP Server + * + * @access public + * @author André Noack + * @package + */ +class StudipAuthLdap extends StudipAuthAbstract +{ + + public $anonymous_bind = true; + + public $host; + public $base_dn; + public $username_attribute = 'uid'; + public $ldap_filter; + public $bad_char_regex = '/[^0-9_a-zA-Z]/'; + public $show_login = true; + + public $conn = null; + public $user_data = null; + + + function getLdapFilter($username) + { + if (isset($this->ldap_filter)) { + list($user, $domain) = explode('@', $username); + $search = ['%u', '%U', '%d', '%%']; + $replace = [$username, $user, $domain, '%']; + + return str_replace($search, $replace, $this->ldap_filter); + } + + return $this->username_attribute . '=' . $username; + } + + function doLdapConnect() + { + if (!($this->conn = ldap_connect($this->host))) { + $this->error_msg = _('Keine Verbindung zum LDAP Server möglich.'); + return false; + } + if (!($r = ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3))) { + $this->error_msg = _('Setzen der LDAP Protokolversion fehlgeschlagen.'); + return false; + } + if ($this->start_tls) { + if (!ldap_start_tls($this->conn)) { + $this->error_msg = _('"Start TLS" fehlgeschlagen.'); + return false; + } + } + return true; + } + + function getUserDn($username) + { + $user_dn = ''; + + if ($this->anonymous_bind) { + if (!($r = @ldap_bind($this->conn))) { + $this->error_msg = _('Anonymer Bind fehlgeschlagen.') . $this->getLdapError(); + return false; + } + if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) { + $this->error_msg = _('Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError(); + return false; + } + if (!ldap_count_entries($this->conn, $result)) { + $this->error_msg = sprintf(_('%s wurde nicht unterhalb von %s gefunden.'), $username, $this->base_dn); + return false; + } + if (!($entry = @ldap_first_entry($this->conn, $result))) { + $this->error_msg = $this->getLdapError(); + return false; + } + if (!($user_dn = @ldap_get_dn($this->conn, $entry))) { + $this->error_msg = $this->getLdapError(); + return false; + } + } else { + $user_dn = $this->username_attribute . '=' . $username . ',' . $this->base_dn; + } + return $user_dn; + } + + function doLdapBind($username, $password) + { + if (!$this->doLdapConnect()) { + return false; + } + if (!($user_dn = $this->getUserDn($username))) { + return false; + } + if (!$password) { + $this->error_msg = _('Kein Passwort eingegeben.'); //some ldap servers seem to allow binding with a user dn and without a password, if anonymous bind is enabled + return false; + } + if (!($r = @ldap_bind($this->conn, $user_dn, $password))) { + if (ldap_errno($this->conn) == 49) { + $this->error_msg = _('Bitte überprüfen Sie ihre Zugangsdaten.'); + } + $this->error_msg = _('Anmeldung fehlgeschlagen.') . $this->getLdapError(); + return false; + } + if (!($result = @ldap_search($this->conn, $user_dn, 'objectclass=*'))) { + $this->error_msg = _('Abholen der Benutzer Attribute fehlgeschlagen.') . $this->getLdapError(); + return false; + } + if (@ldap_count_entries($this->conn, $result)) { + if (!($info = @ldap_get_entries($this->conn, $result))) { + $this->error_msg = $this->getLdapError(); + return false; + } + } + $this->user_data = $info[0]; + return true; + } + + /** + * + * + * + * @access private + * + */ + function isAuthenticated($username, $password) + { + if (!$this->doLdapBind($username, $password)) { + ldap_unbind($this->conn); + return false; + } + ldap_unbind($this->conn); + return true; + } + + + function doLdapMap($map_params) + { + if (isset($this->user_data[$map_params][0])) { + $ret = $this->user_data[$map_params][0]; + if ($ret[0] === ':') { + $ret = base64_decode($ret); + } + } + return $ret; + } + + function doLdapMapDatafield($params) + { + $datafield_id = $params[1]; + $user = $params[2]; + $ldap_field = $this->doLdapMap($params[3]); + if (isset($ldap_field)) { + $df = $user->datafields->findOneBy('datafield_id', $datafield_id); + if ($df) { + $df->content = $ldap_field; + return true; + } + } + } + + function isUsedUsername($username) + { + if (!$this->anonymous_bind) { + $this->error = _('Kann den Benutzernamen nicht überprüfen, anonymous_bind ist ausgeschaltet!'); + return false; + } + if (!$this->doLdapConnect()) { + return false; + } + if (!($r = @ldap_bind($this->conn))) { + $this->error = _('Anonymer Bind fehlgeschlagen.') . $this->getLdapError(); + return false; + } + if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) { + $this->error = _('Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError(); + return false; + } + if (!ldap_count_entries($this->conn, $result)) { + $this->error_msg = _('Der Benutzername wurde nicht gefunden.'); + return false; + } + return true; + } + + function getLdapError() + { + return _('
LDAP Fehler: ') . ldap_error($this->conn) . ' (#' . ldap_errno($this->conn) . ')'; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php b/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php deleted file mode 100644 index 742f0cb..0000000 --- a/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php +++ /dev/null @@ -1,81 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** - * Stud.IP authentication against LDAP Server - * - * Stud.IP authentication against LDAP Server using read-only account and - * following user bind - * - * @access public - * @author André Noack - * @package - */ -class StudipAuthLdapReadAndBind extends StudipAuthLdap -{ - - public $anonymous_bind = false; - - public $reader_dn; - public $reader_password; - - function getUserDn($username) - { - $user_dn = ''; - if (!($r = @ldap_bind($this->conn, $this->reader_dn, $this->reader_password))) { - $this->error_msg = sprintf(_('Anmeldung von %s fehlgeschlagen.'), $this->reader_dn) . $this->getLdapError(); - return false; - } - if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) { - $this->error_msg = _('Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError(); - return false; - } - if (!ldap_count_entries($this->conn, $result)) { - $this->error_msg = sprintf(_('%s wurde nicht unterhalb von %s gefunden.'), $username, $this->base_dn); - return false; - } - if (!($entry = @ldap_first_entry($this->conn, $result))) { - $this->error_msg = $this->getLdapError(); - return false; - } - if (!($user_dn = @ldap_get_dn($this->conn, $entry))) { - $this->error_msg = $this->getLdapError(); - return false; - } - return $user_dn; - } - - function isUsedUsername($username) - { - if (!$this->doLdapConnect()) { - return false; - } - $ret = (bool)$this->getUserDn($username); - ldap_unbind($this->conn); - return $ret; - } -} diff --git a/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.php b/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.php new file mode 100644 index 0000000..742f0cb --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.php @@ -0,0 +1,81 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** + * Stud.IP authentication against LDAP Server + * + * Stud.IP authentication against LDAP Server using read-only account and + * following user bind + * + * @access public + * @author André Noack + * @package + */ +class StudipAuthLdapReadAndBind extends StudipAuthLdap +{ + + public $anonymous_bind = false; + + public $reader_dn; + public $reader_password; + + function getUserDn($username) + { + $user_dn = ''; + if (!($r = @ldap_bind($this->conn, $this->reader_dn, $this->reader_password))) { + $this->error_msg = sprintf(_('Anmeldung von %s fehlgeschlagen.'), $this->reader_dn) . $this->getLdapError(); + return false; + } + if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) { + $this->error_msg = _('Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError(); + return false; + } + if (!ldap_count_entries($this->conn, $result)) { + $this->error_msg = sprintf(_('%s wurde nicht unterhalb von %s gefunden.'), $username, $this->base_dn); + return false; + } + if (!($entry = @ldap_first_entry($this->conn, $result))) { + $this->error_msg = $this->getLdapError(); + return false; + } + if (!($user_dn = @ldap_get_dn($this->conn, $entry))) { + $this->error_msg = $this->getLdapError(); + return false; + } + return $user_dn; + } + + function isUsedUsername($username) + { + if (!$this->doLdapConnect()) { + return false; + } + $ret = (bool)$this->getUserDn($username); + ldap_unbind($this->conn); + return $ret; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthOIDC.class.php b/lib/classes/auth_plugins/StudipAuthOIDC.class.php deleted file mode 100644 index adfe9c9..0000000 --- a/lib/classes/auth_plugins/StudipAuthOIDC.class.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -use Jumbojett\OpenIDConnectClient; -use Jumbojett\OpenIDConnectClientException; - -class StudipAuthOIDC extends StudipAuthSSO -{ - /** - * @var OpenIDConnectClient - */ - private $oidc; - - /** - * @var string - */ - public $provider_url; - /** - * @var string - */ - public $client_id; - /** - * @var string - */ - public $client_secret; - - - /** - * @param array $config - */ - public function __construct($config = []) - { - parent::__construct($config); - if (Request::get('sso') === $this->plugin_name) { - $this->oidc = new OpenIDConnectClient($this->provider_url, $this->client_id, $this->client_secret); - if (isset($this->ssl_options)) { - foreach ($this->ssl_options as $option_key => $option_value) { - if (isset($option_value)) { - $this->oidc->{'set' . $option_key}($option_value); - } - } - if (Config::get()->HTTP_PROXY) { - $this->oidc->setHttpProxy(Config::get()->HTTP_PROXY); - } - $return_url = URLHelper::getScriptURL($GLOBALS['ABSOLUTE_URI_STUDIP'] . 'index.php', ['sso' => $this->plugin_name, 'again' => 'yes']); - $this->oidc->setRedirectURL($return_url); - $this->oidc->addScope(['openid', 'email', 'profile']); - } - } - } - - /** - * Validate the username passed to the auth plugin. - * - * @param string $username - * - * @return string username openid attribute user_id@domain - * - * @throws OpenIDConnectClientException - */ - public function verifyUsername($username) - { - - $this->oidc->authenticate(); - $this->userdata = (array)$this->oidc->requestUserInfo(); - if (isset($this->userdata['sub'])) { - return $this->userdata['username'] = $this->userdata['sub'] . '@' . $this->domain; - } else { - return null; - } - } - - /** - * Return the current username of the pending authentication request. - */ - public function getUser() - { - return $this->userdata['username']; - } - - /** - * Get the user domains to assign to the current user (if any). - * - * @return array array of user domain names - */ - public function getUserDomains() - { - return $this->domain ? [$this->domain] : null; - } - - /** - * Callback that can be used in user_data_mapping array. - * - * @see https://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims - * - * @param string key - * - * @return string parameter value (null if not set) - */ - public function getUserData($key) - { - return $this->userdata[$key]; - } -} diff --git a/lib/classes/auth_plugins/StudipAuthOIDC.php b/lib/classes/auth_plugins/StudipAuthOIDC.php new file mode 100644 index 0000000..adfe9c9 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthOIDC.php @@ -0,0 +1,112 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +use Jumbojett\OpenIDConnectClient; +use Jumbojett\OpenIDConnectClientException; + +class StudipAuthOIDC extends StudipAuthSSO +{ + /** + * @var OpenIDConnectClient + */ + private $oidc; + + /** + * @var string + */ + public $provider_url; + /** + * @var string + */ + public $client_id; + /** + * @var string + */ + public $client_secret; + + + /** + * @param array $config + */ + public function __construct($config = []) + { + parent::__construct($config); + if (Request::get('sso') === $this->plugin_name) { + $this->oidc = new OpenIDConnectClient($this->provider_url, $this->client_id, $this->client_secret); + if (isset($this->ssl_options)) { + foreach ($this->ssl_options as $option_key => $option_value) { + if (isset($option_value)) { + $this->oidc->{'set' . $option_key}($option_value); + } + } + if (Config::get()->HTTP_PROXY) { + $this->oidc->setHttpProxy(Config::get()->HTTP_PROXY); + } + $return_url = URLHelper::getScriptURL($GLOBALS['ABSOLUTE_URI_STUDIP'] . 'index.php', ['sso' => $this->plugin_name, 'again' => 'yes']); + $this->oidc->setRedirectURL($return_url); + $this->oidc->addScope(['openid', 'email', 'profile']); + } + } + } + + /** + * Validate the username passed to the auth plugin. + * + * @param string $username + * + * @return string username openid attribute user_id@domain + * + * @throws OpenIDConnectClientException + */ + public function verifyUsername($username) + { + + $this->oidc->authenticate(); + $this->userdata = (array)$this->oidc->requestUserInfo(); + if (isset($this->userdata['sub'])) { + return $this->userdata['username'] = $this->userdata['sub'] . '@' . $this->domain; + } else { + return null; + } + } + + /** + * Return the current username of the pending authentication request. + */ + public function getUser() + { + return $this->userdata['username']; + } + + /** + * Get the user domains to assign to the current user (if any). + * + * @return array array of user domain names + */ + public function getUserDomains() + { + return $this->domain ? [$this->domain] : null; + } + + /** + * Callback that can be used in user_data_mapping array. + * + * @see https://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims + * + * @param string key + * + * @return string parameter value (null if not set) + */ + public function getUserData($key) + { + return $this->userdata[$key]; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthSSO.class.php b/lib/classes/auth_plugins/StudipAuthSSO.class.php deleted file mode 100644 index 752fa59..0000000 --- a/lib/classes/auth_plugins/StudipAuthSSO.class.php +++ /dev/null @@ -1,51 +0,0 @@ -plugin_fullname)) { - $this->plugin_fullname = _('Shibboleth'); - } - if (!isset($this->login_description)) { - $this->login_description = _('für Single Sign On mit Shibboleth'); - } - - if (Request::get('sso') === $this->plugin_name && isset($this->validate_url) && isset($_REQUEST['token'])) { - $context = get_default_http_stream_context($this->validate_url); - $auth = file_get_contents($this->validate_url . '/' . $_REQUEST['token'], false, $context); - - $this->userdata = json_decode($auth, true); - - if ($this->username_attribute !== 'username') { - $this->userdata['username'] = $this->userdata[$this->username_attribute]; - } - if (isset($this->local_domain)) { - $this->userdata['username'] = - str_replace('@' . $this->local_domain, '', $this->userdata['username']); - } - } - } - - /** - * Return the current username. - */ - function getUser() - { - return $this->userdata['username']; - } - - /** - * Validate the username passed to the auth plugin. - * Note: This triggers authentication if needed. - */ - function verifyUsername($username) - { - if (isset($this->userdata)) { - // use cached user information - return $this->getUser(); - } - - $remote_user = $_SERVER[$this->env_remote_user] ?? null; - - if (empty($remote_user) || isset($this->validate_url)) { - if (Request::get('sso') === $this->plugin_name) { - // force Shibboleth authentication (lazy session) - $shib_url = URLHelper::getURL( - $this->session_initiator, - ['target' => Request::url()], - true - ); - - // break redirection loop in case of misconfiguration - if (strpos($_SERVER['HTTP_REFERER'] ?? '', 'target=') === false) { - header('Location: ' . $shib_url); - exit(); - } - } - - // not authenticated - return NULL; - } - - // import authentication information - $this->userdata['username'] = $remote_user; - - foreach ($_SERVER as $key => $value) { - if (mb_substr($key, 0, 10) == 'HTTP_SHIB_') { - $key = mb_strtolower(mb_substr($key, 10)); - $this->userdata[$key] = $value; - } - } - - if ($this->username_attribute !== 'username') { - $this->userdata['username'] = $this->userdata[$this->username_attribute]; - } - if (isset($this->local_domain)) { - $this->userdata['username'] = - str_replace('@' . $this->local_domain, '', $this->userdata['username']); - } - return $this->getUser(); - } - - /** - * Get the user domains to assign to the current user. - */ - function getUserDomains() - { - $user = $this->getUser(); - $pos = mb_strpos($user, '@'); - - if ($pos !== false) { - return [mb_substr($user, $pos + 1)]; - } - - return NULL; - } - - /** - * Callback that can be used in user_data_mapping array. - */ - function getUserData($key) - { - $data = explode(';', $this->userdata[$key]); - - return $data[0]; - } -} diff --git a/lib/classes/auth_plugins/StudipAuthShib.php b/lib/classes/auth_plugins/StudipAuthShib.php new file mode 100644 index 0000000..3eedc65 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthShib.php @@ -0,0 +1,139 @@ +plugin_fullname)) { + $this->plugin_fullname = _('Shibboleth'); + } + if (!isset($this->login_description)) { + $this->login_description = _('für Single Sign On mit Shibboleth'); + } + + if (Request::get('sso') === $this->plugin_name && isset($this->validate_url) && isset($_REQUEST['token'])) { + $context = get_default_http_stream_context($this->validate_url); + $auth = file_get_contents($this->validate_url . '/' . $_REQUEST['token'], false, $context); + + $this->userdata = json_decode($auth, true); + + if ($this->username_attribute !== 'username') { + $this->userdata['username'] = $this->userdata[$this->username_attribute]; + } + if (isset($this->local_domain)) { + $this->userdata['username'] = + str_replace('@' . $this->local_domain, '', $this->userdata['username']); + } + } + } + + /** + * Return the current username. + */ + function getUser() + { + return $this->userdata['username']; + } + + /** + * Validate the username passed to the auth plugin. + * Note: This triggers authentication if needed. + */ + function verifyUsername($username) + { + if (isset($this->userdata)) { + // use cached user information + return $this->getUser(); + } + + $remote_user = $_SERVER[$this->env_remote_user] ?? null; + + if (empty($remote_user) || isset($this->validate_url)) { + if (Request::get('sso') === $this->plugin_name) { + // force Shibboleth authentication (lazy session) + $shib_url = URLHelper::getURL( + $this->session_initiator, + ['target' => Request::url()], + true + ); + + // break redirection loop in case of misconfiguration + if (strpos($_SERVER['HTTP_REFERER'] ?? '', 'target=') === false) { + header('Location: ' . $shib_url); + exit(); + } + } + + // not authenticated + return NULL; + } + + // import authentication information + $this->userdata['username'] = $remote_user; + + foreach ($_SERVER as $key => $value) { + if (mb_substr($key, 0, 10) == 'HTTP_SHIB_') { + $key = mb_strtolower(mb_substr($key, 10)); + $this->userdata[$key] = $value; + } + } + + if ($this->username_attribute !== 'username') { + $this->userdata['username'] = $this->userdata[$this->username_attribute]; + } + if (isset($this->local_domain)) { + $this->userdata['username'] = + str_replace('@' . $this->local_domain, '', $this->userdata['username']); + } + return $this->getUser(); + } + + /** + * Get the user domains to assign to the current user. + */ + function getUserDomains() + { + $user = $this->getUser(); + $pos = mb_strpos($user, '@'); + + if ($pos !== false) { + return [mb_substr($user, $pos + 1)]; + } + + return NULL; + } + + /** + * Callback that can be used in user_data_mapping array. + */ + function getUserData($key) + { + $data = explode(';', $this->userdata[$key]); + + return $data[0]; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthStandard.class.php b/lib/classes/auth_plugins/StudipAuthStandard.class.php deleted file mode 100644 index 5bb3e65..0000000 --- a/lib/classes/auth_plugins/StudipAuthStandard.class.php +++ /dev/null @@ -1,89 +0,0 @@ - -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** -* Basic Stud.IP authentication, using the Stud.IP database -* -* Basic Stud.IP authentication, using the Stud.IP database -* -* @access public -* @author André Noack -* @package -*/ -class StudipAuthStandard extends StudipAuthAbstract -{ - - var $bad_char_regex = false; - public $show_login = true; - - /** - * - * - * - * @access public - * - */ - function isAuthenticated($username, $password) - { - $user = User::findByUsername($username); - if (!$user || !$password || mb_strlen($password) > 72) { - $this->error_msg= _('Ungültige Benutzername/Passwort-Kombination!') ; - return false; - } elseif ($user->username !== $username) { - $this->error_msg = _('Bitte achten Sie auf korrekte Groß-Kleinschreibung beim Username!'); - return false; - } elseif (!is_null($user->auth_plugin) && $user->auth_plugin !== 'standard') { - $this->error_msg = sprintf(_('Dieser Benutzername wird bereits über %s authentifiziert!'),$user->auth_plugin) ; - return false; - } else { - $pass = $user->password; // Password is stored as a md5 hash - } - $hasher = UserManagement::getPwdHasher(); - $old_style_check = (strlen($pass) === 32 && md5($password) === $pass); - $migrated_check = $hasher->CheckPassword(md5($password), $pass); - $check = $hasher->CheckPassword($password, $pass); - $old_encoding_check = $hasher->CheckPassword(legacy_studip_utf8decode($password), $pass); - - if (($migrated_check || $old_style_check || $old_encoding_check) && !$check) { - // time to convert the password - $user->password = $hasher->HashPassword($password); - $user->store(); - } - - if (!($check || $migrated_check || $old_style_check || $old_encoding_check)) { - $this->error_msg= _('Das Passwort ist falsch!'); - return false; - } else { - return true; - } - } - - function isUsedUsername($username) - { - return User::findByUsername($username) ? true : false; - } - -} diff --git a/lib/classes/auth_plugins/StudipAuthStandard.php b/lib/classes/auth_plugins/StudipAuthStandard.php new file mode 100644 index 0000000..5bb3e65 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthStandard.php @@ -0,0 +1,89 @@ + +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** +* Basic Stud.IP authentication, using the Stud.IP database +* +* Basic Stud.IP authentication, using the Stud.IP database +* +* @access public +* @author André Noack +* @package +*/ +class StudipAuthStandard extends StudipAuthAbstract +{ + + var $bad_char_regex = false; + public $show_login = true; + + /** + * + * + * + * @access public + * + */ + function isAuthenticated($username, $password) + { + $user = User::findByUsername($username); + if (!$user || !$password || mb_strlen($password) > 72) { + $this->error_msg= _('Ungültige Benutzername/Passwort-Kombination!') ; + return false; + } elseif ($user->username !== $username) { + $this->error_msg = _('Bitte achten Sie auf korrekte Groß-Kleinschreibung beim Username!'); + return false; + } elseif (!is_null($user->auth_plugin) && $user->auth_plugin !== 'standard') { + $this->error_msg = sprintf(_('Dieser Benutzername wird bereits über %s authentifiziert!'),$user->auth_plugin) ; + return false; + } else { + $pass = $user->password; // Password is stored as a md5 hash + } + $hasher = UserManagement::getPwdHasher(); + $old_style_check = (strlen($pass) === 32 && md5($password) === $pass); + $migrated_check = $hasher->CheckPassword(md5($password), $pass); + $check = $hasher->CheckPassword($password, $pass); + $old_encoding_check = $hasher->CheckPassword(legacy_studip_utf8decode($password), $pass); + + if (($migrated_check || $old_style_check || $old_encoding_check) && !$check) { + // time to convert the password + $user->password = $hasher->HashPassword($password); + $user->store(); + } + + if (!($check || $migrated_check || $old_style_check || $old_encoding_check)) { + $this->error_msg= _('Das Passwort ist falsch!'); + return false; + } else { + return true; + } + } + + function isUsedUsername($username) + { + return User::findByUsername($username) ? true : false; + } + +} diff --git a/lib/classes/cache/Cache.class.php b/lib/classes/cache/Cache.class.php deleted file mode 100644 index be703cf..0000000 --- a/lib/classes/cache/Cache.class.php +++ /dev/null @@ -1,213 +0,0 @@ - - * @copyright (c) Authors - * @since 1.6 - * @license GPL2 or any later version - */ -abstract class Cache implements CacheItemPoolInterface -{ - const DEFAULT_EXPIRATION = 12 * 60 * 60; // 12 hours - - /** - * @return string A translateable display name for this cache class. - */ - abstract public static function getDisplayName(): string; - - /** - * Get some statistics from cache, like number of entries, hit rate or - * whatever the underlying cache provides. - * Results are returned in form of an array like - * "[ - * [ - * 'name' => - * 'value' => - * ] - * ]" - * - * @return array - */ - abstract public function getStats(): array; - - /** - * Return the Vue component name and props that handle configuration. - * The associative array is of the form - * [ - * 'component' => , - * 'props' => - * ] - * - * @return array - */ - abstract public static function getConfig(): array; - - /** - * Expire item from the cache. - * - * Example: - * - * # expires foo - * $cache->expire('foo'); - * - * @param string $arg a single key - */ - abstract public function expire($arg); - - /** - * Expire all items from the cache. - */ - abstract public function flush(); - - /** - * @see CacheItemPoolInterface::getItem - */ - abstract public function getItem(string $key): CacheItemInterface; - - /** - * @see CacheItemPoolInterface::hasItem - */ - abstract public function hasItem(string $key): bool; - - /** - * @var array An array of deferred items that shall be saved only - * when commit() is called. This is only used in PSR-6 cache methods. - */ - protected array $deferred_items = []; - - /** - * Retrieve item from the server. - * - * Example: - * - * # reads foo - * $foo = $cache->reads('foo'); - * - * @param string $arg a single key - * - * @return mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - * - * @deprecated To be removed with Stud.IP 7.0. - */ - public function read($arg) - { - $item = $this->getItem($arg); - if ($item->isHit()) { - return $item->get(); - } - return false; - } - - /** - * Store data at the server. - * - * @param string $name the item's key. - * @param mixed $content the item's content (will be serialized if necessary). - * @param int $expires the item's expiry time in seconds. Optional, defaults to 12h. - * - * @return bool returns TRUE on success or FALSE on failure. - - * @deprecated To be removed with Stud.IP 7.0. - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) - { - $item = new Item($name, $content, $expires); - - return $this->save($item); - } - - /** - * Calculates the expiration by a cache item. If that cannot be determined, - * the default expiration period is returned. - * - * @param Item $item The item from which to get the expiration time. - * - * @return int The time from now until the expiration in seconds. - */ - public function getExpiration(CacheItemInterface $item) : int - { - $expiration = self::DEFAULT_EXPIRATION; - if ($item instanceof Item) { - $expiration = $item->getExpirationInSeconds(); - } - return $expiration; - } - - // PSR-6 CacheItemPoolInterface: - - /** - * @see CacheItemPoolInterface::getItems - */ - public function getItems(array $keys = []): iterable - { - $items = []; - foreach ($keys as $key) { - $item = $this->getItem($key); - if ($item instanceof Item) { - $items[] = $item; - } - } - return $items; - } - - /** - * @see CacheItemPoolInterface::clear - */ - public function clear(): bool - { - $this->deferred_items = []; - $this->flush(); - return true; - } - - /** - * @see CacheItemPoolInterface::deleteItem - */ - public function deleteItem($key): bool - { - $this->expire($key); - return true; - } - - /** - * @see CacheItemPoolInterface::deleteItems - */ - public function deleteItems(array $keys): bool - { - foreach ($keys as $key) { - $this->expire($key); - } - return true; - } - - /** - * @see CacheItemPoolInterface::saveDeferred - */ - public function saveDeferred(CacheItemInterface $item): bool - { - $this->deferred_items[] = $item; - return true; - } - - /** - * @see CacheItemPoolInterface::commit - */ - public function commit(): bool - { - foreach ($this->deferred_items as $item) { - $this->save($item); - } - return true; - } -} diff --git a/lib/classes/cache/Cache.php b/lib/classes/cache/Cache.php new file mode 100644 index 0000000..be703cf --- /dev/null +++ b/lib/classes/cache/Cache.php @@ -0,0 +1,213 @@ + + * @copyright (c) Authors + * @since 1.6 + * @license GPL2 or any later version + */ +abstract class Cache implements CacheItemPoolInterface +{ + const DEFAULT_EXPIRATION = 12 * 60 * 60; // 12 hours + + /** + * @return string A translateable display name for this cache class. + */ + abstract public static function getDisplayName(): string; + + /** + * Get some statistics from cache, like number of entries, hit rate or + * whatever the underlying cache provides. + * Results are returned in form of an array like + * "[ + * [ + * 'name' => + * 'value' => + * ] + * ]" + * + * @return array + */ + abstract public function getStats(): array; + + /** + * Return the Vue component name and props that handle configuration. + * The associative array is of the form + * [ + * 'component' => , + * 'props' => + * ] + * + * @return array + */ + abstract public static function getConfig(): array; + + /** + * Expire item from the cache. + * + * Example: + * + * # expires foo + * $cache->expire('foo'); + * + * @param string $arg a single key + */ + abstract public function expire($arg); + + /** + * Expire all items from the cache. + */ + abstract public function flush(); + + /** + * @see CacheItemPoolInterface::getItem + */ + abstract public function getItem(string $key): CacheItemInterface; + + /** + * @see CacheItemPoolInterface::hasItem + */ + abstract public function hasItem(string $key): bool; + + /** + * @var array An array of deferred items that shall be saved only + * when commit() is called. This is only used in PSR-6 cache methods. + */ + protected array $deferred_items = []; + + /** + * Retrieve item from the server. + * + * Example: + * + * # reads foo + * $foo = $cache->reads('foo'); + * + * @param string $arg a single key + * + * @return mixed the previously stored data if an item with such a key + * exists on the server or FALSE on failure. + * + * @deprecated To be removed with Stud.IP 7.0. + */ + public function read($arg) + { + $item = $this->getItem($arg); + if ($item->isHit()) { + return $item->get(); + } + return false; + } + + /** + * Store data at the server. + * + * @param string $name the item's key. + * @param mixed $content the item's content (will be serialized if necessary). + * @param int $expires the item's expiry time in seconds. Optional, defaults to 12h. + * + * @return bool returns TRUE on success or FALSE on failure. + + * @deprecated To be removed with Stud.IP 7.0. + */ + public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) + { + $item = new Item($name, $content, $expires); + + return $this->save($item); + } + + /** + * Calculates the expiration by a cache item. If that cannot be determined, + * the default expiration period is returned. + * + * @param Item $item The item from which to get the expiration time. + * + * @return int The time from now until the expiration in seconds. + */ + public function getExpiration(CacheItemInterface $item) : int + { + $expiration = self::DEFAULT_EXPIRATION; + if ($item instanceof Item) { + $expiration = $item->getExpirationInSeconds(); + } + return $expiration; + } + + // PSR-6 CacheItemPoolInterface: + + /** + * @see CacheItemPoolInterface::getItems + */ + public function getItems(array $keys = []): iterable + { + $items = []; + foreach ($keys as $key) { + $item = $this->getItem($key); + if ($item instanceof Item) { + $items[] = $item; + } + } + return $items; + } + + /** + * @see CacheItemPoolInterface::clear + */ + public function clear(): bool + { + $this->deferred_items = []; + $this->flush(); + return true; + } + + /** + * @see CacheItemPoolInterface::deleteItem + */ + public function deleteItem($key): bool + { + $this->expire($key); + return true; + } + + /** + * @see CacheItemPoolInterface::deleteItems + */ + public function deleteItems(array $keys): bool + { + foreach ($keys as $key) { + $this->expire($key); + } + return true; + } + + /** + * @see CacheItemPoolInterface::saveDeferred + */ + public function saveDeferred(CacheItemInterface $item): bool + { + $this->deferred_items[] = $item; + return true; + } + + /** + * @see CacheItemPoolInterface::commit + */ + public function commit(): bool + { + foreach ($this->deferred_items as $item) { + $this->save($item); + } + return true; + } +} diff --git a/lib/classes/cache/DbCache.class.php b/lib/classes/cache/DbCache.class.php deleted file mode 100644 index c7abef2..0000000 --- a/lib/classes/cache/DbCache.class.php +++ /dev/null @@ -1,143 +0,0 @@ - - */ -class DbCache extends Cache -{ - /** - * @return string A display name (that can be translated) for this cache class. - */ - public static function getDisplayName(): string - { - return _('Datenbank'); - } - - /** - * Expire item from the cache. - * - * @param string $arg a single key - */ - public function expire($arg) - { - $db = DBManager::get(); - - $stmt = $db->prepare('DELETE FROM cache WHERE cache_key = ?'); - $stmt->execute([$arg]); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $db = DBManager::get(); - - $db->exec('TRUNCATE TABLE cache'); - } - - /** - * Delete all expired items from the cache. - */ - public function purge() - { - $db = DBManager::get(); - - $stmt = $db->prepare('DELETE FROM cache WHERE expires < ?'); - $stmt->execute([time()]); - } - - /** - * Return statistics. - * - * @return array|array[] - *@see Cache::getStats() - * - */ - public function getStats(): array - { - return [ - __CLASS__ => [ - 'name' => _('Anzahl Einträge'), - 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") - ] - ]; - } - - /** - * Return the Vue component name and props that handle configuration. - * - * @return array - *@see Cache::getConfig() - * - */ - public static function getConfig(): array - { - return [ - 'component' => null, - 'props' => [] - ]; - } - - /** - * @inheritDoc - */ - public function getItem(string $key): CacheItemInterface - { - $query = "SELECT `content`, `expires` - FROM `cache` - WHERE `cache_key` = :key - AND `expires` > UNIX_TIMESTAMP()"; - $result = DBManager::get()->fetchOne($query, [':key' => $key]); - - $item = new Item($key); - if (!empty($result)) { - $item->setHit(); - if ($result['content']) { - $item->set(unserialize($result['content'])); - } - if ($result['expires']) { - $expiration = new \DateTime(); - $expiration->setTimestamp($result['expires']); - $item->expiresAt($expiration); - } - } - return $item; - } - - /** - * @inheritDoc - */ - public function hasItem(string $key): bool - { - $query = "SELECT 1 - FROM `cache` - WHERE `cache_key` = :key - AND `expires` > UNIX_TIMESTAMP()"; - return (bool) DBManager::get()->fetchColumn($query, [':key' => $key]); - } - - /** - * @inheritDoc - */ - public function save(CacheItemInterface $item): bool - { - $expiration = $this->getExpiration($item); - if ($expiration < 1) { - // The item would expire immediately. - return false; - } - - return DBManager::get()->execute( - 'REPLACE INTO `cache` VALUES (?, ?, ?)', - [$item->getKey(), serialize($item->get()), $expiration] - ); - } -} diff --git a/lib/classes/cache/DbCache.php b/lib/classes/cache/DbCache.php new file mode 100644 index 0000000..c7abef2 --- /dev/null +++ b/lib/classes/cache/DbCache.php @@ -0,0 +1,143 @@ + + */ +class DbCache extends Cache +{ + /** + * @return string A display name (that can be translated) for this cache class. + */ + public static function getDisplayName(): string + { + return _('Datenbank'); + } + + /** + * Expire item from the cache. + * + * @param string $arg a single key + */ + public function expire($arg) + { + $db = DBManager::get(); + + $stmt = $db->prepare('DELETE FROM cache WHERE cache_key = ?'); + $stmt->execute([$arg]); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $db = DBManager::get(); + + $db->exec('TRUNCATE TABLE cache'); + } + + /** + * Delete all expired items from the cache. + */ + public function purge() + { + $db = DBManager::get(); + + $stmt = $db->prepare('DELETE FROM cache WHERE expires < ?'); + $stmt->execute([time()]); + } + + /** + * Return statistics. + * + * @return array|array[] + *@see Cache::getStats() + * + */ + public function getStats(): array + { + return [ + __CLASS__ => [ + 'name' => _('Anzahl Einträge'), + 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") + ] + ]; + } + + /** + * Return the Vue component name and props that handle configuration. + * + * @return array + *@see Cache::getConfig() + * + */ + public static function getConfig(): array + { + return [ + 'component' => null, + 'props' => [] + ]; + } + + /** + * @inheritDoc + */ + public function getItem(string $key): CacheItemInterface + { + $query = "SELECT `content`, `expires` + FROM `cache` + WHERE `cache_key` = :key + AND `expires` > UNIX_TIMESTAMP()"; + $result = DBManager::get()->fetchOne($query, [':key' => $key]); + + $item = new Item($key); + if (!empty($result)) { + $item->setHit(); + if ($result['content']) { + $item->set(unserialize($result['content'])); + } + if ($result['expires']) { + $expiration = new \DateTime(); + $expiration->setTimestamp($result['expires']); + $item->expiresAt($expiration); + } + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem(string $key): bool + { + $query = "SELECT 1 + FROM `cache` + WHERE `cache_key` = :key + AND `expires` > UNIX_TIMESTAMP()"; + return (bool) DBManager::get()->fetchColumn($query, [':key' => $key]); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item): bool + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + return DBManager::get()->execute( + 'REPLACE INTO `cache` VALUES (?, ?, ?)', + [$item->getKey(), serialize($item->get()), $expiration] + ); + } +} diff --git a/lib/classes/cache/Factory.class.php b/lib/classes/cache/Factory.class.php deleted file mode 100644 index b5c8359..0000000 --- a/lib/classes/cache/Factory.class.php +++ /dev/null @@ -1,206 +0,0 @@ -getMessage()); - PageLayout::addBodyElements(MessageBox::error(__METHOD__ . ': ' . $e->getMessage())); - $class = self::DEFAULT_CACHE_CLASS; - self::$cache = new $class(); - } - } - - // If proxy should be used, inject it. Otherwise apply pending - // operations, if any. - if ($proxied) { - self::$cache = new Proxy(self::$cache); - } elseif ($GLOBALS['CACHING_ENABLE'] && $apply_proxied_operations) { - // Even if the above condition will try to eliminate most - // failures, the following operation still needs to be wrapped - // in a try/catch block. Otherwise there are no means to - // execute migration 166 which creates the neccessary tables - // for said operation. - try { - StudipCacheOperation::apply(self::$cache); - } catch (\Exception $e) { - } - } - } - - return self::$cache; - } - - - /** - * Load configured cache class and return its name. - * - * @return string the name of the configured cache class - */ - public static function loadCacheClass() - { - $cacheConfig = self::getConfig()->SYSTEMCACHE; - - $cache_class = $cacheConfig['type'] ?: null; - - # default class - if ($cache_class === null) { - $version = new DBSchemaVersion(); - if ($version->get(1) < 224) { - // db cache is not yet available, use StudipMemoryCache - return 'StudipMemoryCache'; - } - - return self::DEFAULT_CACHE_CLASS; - } - - if (!class_exists($cache_class)) { - throw new UnexpectedValueException("Could not find class: '$cache_class'"); - } - - return $cache_class; - } - - /** - * Return an array of arguments required for instantiation of the cache - * class. - * - * @return array the array of arguments - */ - public static function retrieveConstructorArguments() - { - $cacheConfig = self::getConfig()->SYSTEMCACHE; - - return $cacheConfig ?: []; - } - - /** - * Return an instance of a given class using some arguments. Unless the - * memory cache is instantiated, the cache will be wrapped in a wrapper - * class that uses a memory cache to reduce accesses to the cache. - * - * @param string $class the name of the class - * @param array $arguments an array of arguments to be used by the constructor - * - * @return Cache an instance of the specified class - * @throws \ReflectionException - */ - public static function instantiateCache($class, $arguments) - { - $reflection_class = new ReflectionClass($class); - $cache = (is_array($arguments['config']) && count($arguments['config']) > 0) - ? $reflection_class->newInstanceArgs($arguments['config']) - : $reflection_class->newInstance(); - - if ($class !== MemoryCache::class) { - return new Wrapper($cache); - } - - return $cache; - } -} diff --git a/lib/classes/cache/Factory.php b/lib/classes/cache/Factory.php new file mode 100644 index 0000000..b5c8359 --- /dev/null +++ b/lib/classes/cache/Factory.php @@ -0,0 +1,206 @@ +getMessage()); + PageLayout::addBodyElements(MessageBox::error(__METHOD__ . ': ' . $e->getMessage())); + $class = self::DEFAULT_CACHE_CLASS; + self::$cache = new $class(); + } + } + + // If proxy should be used, inject it. Otherwise apply pending + // operations, if any. + if ($proxied) { + self::$cache = new Proxy(self::$cache); + } elseif ($GLOBALS['CACHING_ENABLE'] && $apply_proxied_operations) { + // Even if the above condition will try to eliminate most + // failures, the following operation still needs to be wrapped + // in a try/catch block. Otherwise there are no means to + // execute migration 166 which creates the neccessary tables + // for said operation. + try { + StudipCacheOperation::apply(self::$cache); + } catch (\Exception $e) { + } + } + } + + return self::$cache; + } + + + /** + * Load configured cache class and return its name. + * + * @return string the name of the configured cache class + */ + public static function loadCacheClass() + { + $cacheConfig = self::getConfig()->SYSTEMCACHE; + + $cache_class = $cacheConfig['type'] ?: null; + + # default class + if ($cache_class === null) { + $version = new DBSchemaVersion(); + if ($version->get(1) < 224) { + // db cache is not yet available, use StudipMemoryCache + return 'StudipMemoryCache'; + } + + return self::DEFAULT_CACHE_CLASS; + } + + if (!class_exists($cache_class)) { + throw new UnexpectedValueException("Could not find class: '$cache_class'"); + } + + return $cache_class; + } + + /** + * Return an array of arguments required for instantiation of the cache + * class. + * + * @return array the array of arguments + */ + public static function retrieveConstructorArguments() + { + $cacheConfig = self::getConfig()->SYSTEMCACHE; + + return $cacheConfig ?: []; + } + + /** + * Return an instance of a given class using some arguments. Unless the + * memory cache is instantiated, the cache will be wrapped in a wrapper + * class that uses a memory cache to reduce accesses to the cache. + * + * @param string $class the name of the class + * @param array $arguments an array of arguments to be used by the constructor + * + * @return Cache an instance of the specified class + * @throws \ReflectionException + */ + public static function instantiateCache($class, $arguments) + { + $reflection_class = new ReflectionClass($class); + $cache = (is_array($arguments['config']) && count($arguments['config']) > 0) + ? $reflection_class->newInstanceArgs($arguments['config']) + : $reflection_class->newInstance(); + + if ($class !== MemoryCache::class) { + return new Wrapper($cache); + } + + return $cache; + } +} diff --git a/lib/classes/cache/FileCache.class.php b/lib/classes/cache/FileCache.class.php deleted file mode 100644 index d760f08..0000000 --- a/lib/classes/cache/FileCache.class.php +++ /dev/null @@ -1,278 +0,0 @@ - - * @copyright 2007 André Noack - * @license GPL2 or any later version - */ -class FileCache extends Cache -{ - use KeyTrait; - - /** - * full path to cache directory - * - * @var string - */ - private string $dir; - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Dateisystem'); - } - - /** - * without the 'dir' argument the cache path is taken from - * $CACHING_FILECACHE_PATH or is set to - * $TMP_PATH/studip_cache - * - * @param string $path the path to use - * @throws Exception if the directory does not exist or could not be - * created - */ - public function __construct(string $path = '') - { - $this->dir = $path - ?: ( - Config::get()->SYSTEMCACHE['type'] === self::class - ? Config::get()->SYSTEMCACHE['config']['path'] - : '' - ) - ?: $GLOBALS['CACHING_FILECACHE_PATH'] - ?: ($GLOBALS['TMP_PATH'] . '/' . 'studip_cache'); - $this->dir = rtrim($this->dir, '\\/') . '/'; - - if (!is_dir($this->dir) && !@mkdir($this->dir, 0700)) { - throw new \Exception('Could not create directory: ' . $this->dir); - } - - if (!is_writable($this->dir)) { - throw new \Exception('Can not write to directory: ' . $this->dir); - } - } - - /** - * get path to cache directory - * - * @return string - */ - public function getCacheDir() - { - return $this->dir; - } - - /** - * expire cache item - * - * @param string $arg - * - * @return void - * @throws Exception - * @see Cache::expire() - */ - public function expire($arg) - { - $key = $this->getCacheKey($arg); - - if ($file = $this->getPathAndFile($key)){ - @unlink($file); - } - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - rmdirr($this->dir); - } - - /** - * checks if specified cache item is expired - * if expired the cache file is deleted - * - * @param string $key a cache key to check - * - * @return array|bool the path to the cache file or false if expired - * @throws Exception - */ - private function check($key) - { - if ($file = $this->getPathAndFile($key)){ - [$id, $expire] = explode('-', basename($file)); - if (time() < $expire) { - return [$file, $expire]; - } else { - @unlink($file); - } - } - return false; - } - - /** - * get the full path to a cache file - * - * the cache files are organized in sub-folders named by - * the first two characters of the hashed cache key. - * the filename is constructed from the hashed cache key - * and the timestamp of expiration - * - * @param string $key a cache key - * @param int|null $expire expiry time in seconds - * - * @return string|bool full path to cache item or false on failure - * @throws Exception - */ - private function getPathAndFile(string $key, ?int $expire = null): bool|string - { - $id = hash('md5', $key); - $path = $this->dir . mb_substr($id, 0, 2); - if (!is_dir($path) && !@mkdir($path, 0700)) { - throw new \Exception('Could not create directory: ' . $path); - } - if (!is_null($expire)){ - return $path . '/' . $id . '-' . (time() + $expire); - } else { - $files = @glob("{$path}/{$id}*"); - if (count($files) > 0) { - return $files[0]; - } - } - return false; - } - - /** - * purges expired entries from the cache directory - * - * @param bool $be_quiet echo messages if set to false - * - * @return int the number of deleted files - */ - public function purge(bool $be_quiet = true): int - { - $now = time(); - $deleted = 0; - foreach (@glob($this->dir . '*', GLOB_ONLYDIR) as $current_dir){ - foreach (@glob("{$current_dir}/*") as $file){ - [$id, $expire] = explode('-', basename($file)); - if ($expire < $now) { - if (@unlink($file)) { - ++$deleted; - if (!$be_quiet) { - echo "File: {$file} deleted.\n"; - } - } - } else if (!$be_quiet) { - echo "File: {$file} expires on " . date('Y-m-d H:i:s', $expire) . "\n"; - } - } - } - return $deleted; - } - - /** - * Return statistics. - * - * @return array|array[] - */ - public function getStats(): array - { - return [ - __CLASS__ => [ - 'name' => _('Anzahl Einträge'), - 'value' => \DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") - ] - ]; - } - - /** - * Return the Vue component name and props that handle configuration. - * - * @return array - */ - public static function getConfig(): array - { - $currentCache = Config::get()->SYSTEMCACHE; - - // Set default config for this cache - $currentConfig = [ - 'path' => $GLOBALS['TMP_PATH'] . '/studip_cache' - ]; - - // If this cache is set as system cache, use config from global settings. - if ($currentCache['type'] == __CLASS__) { - $currentConfig = $currentCache['config']; - } - - return [ - 'component' => 'FileCacheConfig', - 'props' => $currentConfig - ]; - } - - /** - * @inheritDoc - */ - public function getItem(string $key): CacheItemInterface - { - $real_key = $this->getCacheKey($key); - - $item = new \Studip\Cache\Item($key); - - $file_data = $this->check($real_key); - if ($file_data) { - $file = $file_data[0]; - $expire = $file_data[1]; - $f = @fopen($file, 'rb'); - if ($f) { - @flock($f, LOCK_SH); - $result = stream_get_contents($f); - @fclose($f); - } - $item->setHit(); - $item->set(unserialize($result)); - $expiration = new \DateTime(); - $expiration->setTimestamp($expire); - $item->expiresAt($expiration); - } - return $item; - } - - /** - * @inheritDoc - */ - public function hasItem(string $key): bool - { - $real_key = $this->getCacheKey($key); - $file_data = $this->check($real_key); - return $file_data !== false; - } - - /** - * @inheritDoc - */ - public function save(CacheItemInterface $item): bool - { - $expiration = $this->getExpiration($item); - if ($expiration < 1) { - //The item would expire immediately. - return false; - } - - $real_key = $this->getCacheKey($item->getKey()); - $this->expire($real_key); - $file = $this->getPathAndFile($real_key, $expiration); - return @file_put_contents($file, serialize($item->get()), LOCK_EX); - } -} diff --git a/lib/classes/cache/FileCache.php b/lib/classes/cache/FileCache.php new file mode 100644 index 0000000..d760f08 --- /dev/null +++ b/lib/classes/cache/FileCache.php @@ -0,0 +1,278 @@ + + * @copyright 2007 André Noack + * @license GPL2 or any later version + */ +class FileCache extends Cache +{ + use KeyTrait; + + /** + * full path to cache directory + * + * @var string + */ + private string $dir; + + /** + * @return string A translateable display name for this cache class. + */ + public static function getDisplayName(): string + { + return _('Dateisystem'); + } + + /** + * without the 'dir' argument the cache path is taken from + * $CACHING_FILECACHE_PATH or is set to + * $TMP_PATH/studip_cache + * + * @param string $path the path to use + * @throws Exception if the directory does not exist or could not be + * created + */ + public function __construct(string $path = '') + { + $this->dir = $path + ?: ( + Config::get()->SYSTEMCACHE['type'] === self::class + ? Config::get()->SYSTEMCACHE['config']['path'] + : '' + ) + ?: $GLOBALS['CACHING_FILECACHE_PATH'] + ?: ($GLOBALS['TMP_PATH'] . '/' . 'studip_cache'); + $this->dir = rtrim($this->dir, '\\/') . '/'; + + if (!is_dir($this->dir) && !@mkdir($this->dir, 0700)) { + throw new \Exception('Could not create directory: ' . $this->dir); + } + + if (!is_writable($this->dir)) { + throw new \Exception('Can not write to directory: ' . $this->dir); + } + } + + /** + * get path to cache directory + * + * @return string + */ + public function getCacheDir() + { + return $this->dir; + } + + /** + * expire cache item + * + * @param string $arg + * + * @return void + * @throws Exception + * @see Cache::expire() + */ + public function expire($arg) + { + $key = $this->getCacheKey($arg); + + if ($file = $this->getPathAndFile($key)){ + @unlink($file); + } + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + rmdirr($this->dir); + } + + /** + * checks if specified cache item is expired + * if expired the cache file is deleted + * + * @param string $key a cache key to check + * + * @return array|bool the path to the cache file or false if expired + * @throws Exception + */ + private function check($key) + { + if ($file = $this->getPathAndFile($key)){ + [$id, $expire] = explode('-', basename($file)); + if (time() < $expire) { + return [$file, $expire]; + } else { + @unlink($file); + } + } + return false; + } + + /** + * get the full path to a cache file + * + * the cache files are organized in sub-folders named by + * the first two characters of the hashed cache key. + * the filename is constructed from the hashed cache key + * and the timestamp of expiration + * + * @param string $key a cache key + * @param int|null $expire expiry time in seconds + * + * @return string|bool full path to cache item or false on failure + * @throws Exception + */ + private function getPathAndFile(string $key, ?int $expire = null): bool|string + { + $id = hash('md5', $key); + $path = $this->dir . mb_substr($id, 0, 2); + if (!is_dir($path) && !@mkdir($path, 0700)) { + throw new \Exception('Could not create directory: ' . $path); + } + if (!is_null($expire)){ + return $path . '/' . $id . '-' . (time() + $expire); + } else { + $files = @glob("{$path}/{$id}*"); + if (count($files) > 0) { + return $files[0]; + } + } + return false; + } + + /** + * purges expired entries from the cache directory + * + * @param bool $be_quiet echo messages if set to false + * + * @return int the number of deleted files + */ + public function purge(bool $be_quiet = true): int + { + $now = time(); + $deleted = 0; + foreach (@glob($this->dir . '*', GLOB_ONLYDIR) as $current_dir){ + foreach (@glob("{$current_dir}/*") as $file){ + [$id, $expire] = explode('-', basename($file)); + if ($expire < $now) { + if (@unlink($file)) { + ++$deleted; + if (!$be_quiet) { + echo "File: {$file} deleted.\n"; + } + } + } else if (!$be_quiet) { + echo "File: {$file} expires on " . date('Y-m-d H:i:s', $expire) . "\n"; + } + } + } + return $deleted; + } + + /** + * Return statistics. + * + * @return array|array[] + */ + public function getStats(): array + { + return [ + __CLASS__ => [ + 'name' => _('Anzahl Einträge'), + 'value' => \DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") + ] + ]; + } + + /** + * Return the Vue component name and props that handle configuration. + * + * @return array + */ + public static function getConfig(): array + { + $currentCache = Config::get()->SYSTEMCACHE; + + // Set default config for this cache + $currentConfig = [ + 'path' => $GLOBALS['TMP_PATH'] . '/studip_cache' + ]; + + // If this cache is set as system cache, use config from global settings. + if ($currentCache['type'] == __CLASS__) { + $currentConfig = $currentCache['config']; + } + + return [ + 'component' => 'FileCacheConfig', + 'props' => $currentConfig + ]; + } + + /** + * @inheritDoc + */ + public function getItem(string $key): CacheItemInterface + { + $real_key = $this->getCacheKey($key); + + $item = new \Studip\Cache\Item($key); + + $file_data = $this->check($real_key); + if ($file_data) { + $file = $file_data[0]; + $expire = $file_data[1]; + $f = @fopen($file, 'rb'); + if ($f) { + @flock($f, LOCK_SH); + $result = stream_get_contents($f); + @fclose($f); + } + $item->setHit(); + $item->set(unserialize($result)); + $expiration = new \DateTime(); + $expiration->setTimestamp($expire); + $item->expiresAt($expiration); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem(string $key): bool + { + $real_key = $this->getCacheKey($key); + $file_data = $this->check($real_key); + return $file_data !== false; + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item): bool + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + //The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + $this->expire($real_key); + $file = $this->getPathAndFile($real_key, $expiration); + return @file_put_contents($file, serialize($item->get()), LOCK_EX); + } +} diff --git a/lib/classes/cache/Item.class.php b/lib/classes/cache/Item.class.php deleted file mode 100644 index 4e83d4d..0000000 --- a/lib/classes/cache/Item.class.php +++ /dev/null @@ -1,164 +0,0 @@ - - * @copyright 2024 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 6.0 - */ - -namespace Studip\Cache; - -use DateInterval; -use DateTime; -use Psr\Cache\CacheItemInterface; - -/** - * \Studip\Cache\CacheItem implements the CacheItemInterface of PSR-6. It holds the value and the - * key of a cache item and also provides additional methods to get the expiration of the item. - */ -class Item implements CacheItemInterface -{ - /** - * @var string The key of the item in the cache. - */ - protected string $key; - - /** - * @var mixed The value of the item. - */ - protected mixed $value; - - /** - * @var DateTime|null The expiration as DateTime object or null if the expiration is not defined. - */ - protected ?DateTime $expiration = null; - - /** - * @var bool An indicator whether the item has been found in the cache (true) or not (false). - */ - protected bool $cache_hit = false; - - /** - * The constructor of \Studip\Cache\CacheItem. - * - * @param string $key The key of the item in the cache. - * @param mixed $value The value of the item. - * @param int|null $expiration The expiration of the item in seconds, if applicable. - * @param bool $cache_hit Whether the item shall be constructed as cache hit (true) or not (false). - * - */ - public function __construct( - string $key, - mixed $value = null, - ?int $expiration = null, - bool $cache_hit = false - ) { - $this->key = $key; - $this->value = $value; - $this->cache_hit = $cache_hit; - $this->expiresAfter($expiration); - } - - /** - * @inheritDoc - */ - public function getKey(): string - { - return $this->key; - } - - /** - * @inheritDoc - */ - public function get(): mixed - { - return $this->value; - } - - /** - * @inheritDoc - */ - public function isHit(): bool - { - return $this->cache_hit; - } - - /** - * @inheritDoc - */ - public function set($value): static - { - $this->value = $value; - return $this; - } - - /** - * @inheritDoc - */ - public function expiresAt($expiration): static - { - $this->expiration = $expiration; - return $this; - } - - /** - * @inheritDoc - */ - public function expiresAfter($time): static - { - $this->expiration = new DateTime(); - if ($time instanceof DateInterval) { - $this->expiration = $this->expiration->add($time); - } elseif (is_integer($time)) { - $this->expiration->setTimestamp(time() + $time); - } else { - $this->expiration->setTimestamp(time() + Cache::DEFAULT_EXPIRATION); - } - return $this; - } - - // \Studip\Cache\CacheItem specific methods: - - /** - * Sets the item to be a cache hit. - * - * @return void - */ - public function setHit() : void - { - $this->cache_hit = true; - } - - /** - * Returns the expiration, if set. - * - * @return DateTime|null A DateTime object with the expiration date and time - * or null if the expiration is not defined. - */ - public function getExpiration() : ?DateTime - { - return $this->expiration; - } - - /** - * Returns the seconds from the current timestamp until the expiration of the item. - * - * @return int The seconds until the item expires - */ - public function getExpirationInSeconds() : int - { - if ($this->expiration) { - return $this->expiration->getTimestamp() - time(); - } - return 0; - } -} diff --git a/lib/classes/cache/Item.php b/lib/classes/cache/Item.php new file mode 100644 index 0000000..4e83d4d --- /dev/null +++ b/lib/classes/cache/Item.php @@ -0,0 +1,164 @@ + + * @copyright 2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 6.0 + */ + +namespace Studip\Cache; + +use DateInterval; +use DateTime; +use Psr\Cache\CacheItemInterface; + +/** + * \Studip\Cache\CacheItem implements the CacheItemInterface of PSR-6. It holds the value and the + * key of a cache item and also provides additional methods to get the expiration of the item. + */ +class Item implements CacheItemInterface +{ + /** + * @var string The key of the item in the cache. + */ + protected string $key; + + /** + * @var mixed The value of the item. + */ + protected mixed $value; + + /** + * @var DateTime|null The expiration as DateTime object or null if the expiration is not defined. + */ + protected ?DateTime $expiration = null; + + /** + * @var bool An indicator whether the item has been found in the cache (true) or not (false). + */ + protected bool $cache_hit = false; + + /** + * The constructor of \Studip\Cache\CacheItem. + * + * @param string $key The key of the item in the cache. + * @param mixed $value The value of the item. + * @param int|null $expiration The expiration of the item in seconds, if applicable. + * @param bool $cache_hit Whether the item shall be constructed as cache hit (true) or not (false). + * + */ + public function __construct( + string $key, + mixed $value = null, + ?int $expiration = null, + bool $cache_hit = false + ) { + $this->key = $key; + $this->value = $value; + $this->cache_hit = $cache_hit; + $this->expiresAfter($expiration); + } + + /** + * @inheritDoc + */ + public function getKey(): string + { + return $this->key; + } + + /** + * @inheritDoc + */ + public function get(): mixed + { + return $this->value; + } + + /** + * @inheritDoc + */ + public function isHit(): bool + { + return $this->cache_hit; + } + + /** + * @inheritDoc + */ + public function set($value): static + { + $this->value = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function expiresAt($expiration): static + { + $this->expiration = $expiration; + return $this; + } + + /** + * @inheritDoc + */ + public function expiresAfter($time): static + { + $this->expiration = new DateTime(); + if ($time instanceof DateInterval) { + $this->expiration = $this->expiration->add($time); + } elseif (is_integer($time)) { + $this->expiration->setTimestamp(time() + $time); + } else { + $this->expiration->setTimestamp(time() + Cache::DEFAULT_EXPIRATION); + } + return $this; + } + + // \Studip\Cache\CacheItem specific methods: + + /** + * Sets the item to be a cache hit. + * + * @return void + */ + public function setHit() : void + { + $this->cache_hit = true; + } + + /** + * Returns the expiration, if set. + * + * @return DateTime|null A DateTime object with the expiration date and time + * or null if the expiration is not defined. + */ + public function getExpiration() : ?DateTime + { + return $this->expiration; + } + + /** + * Returns the seconds from the current timestamp until the expiration of the item. + * + * @return int The seconds until the item expires + */ + public function getExpirationInSeconds() : int + { + if ($this->expiration) { + return $this->expiration->getTimestamp() - time(); + } + return 0; + } +} diff --git a/lib/classes/cache/MemcachedCache.class.php b/lib/classes/cache/MemcachedCache.class.php deleted file mode 100644 index 1c4b685..0000000 --- a/lib/classes/cache/MemcachedCache.class.php +++ /dev/null @@ -1,151 +0,0 @@ - - * @copyright (c) Authors - * @license GPL2 or any later version - * @since 5.0 - */ -class MemcachedCache extends Cache -{ - use KeyTrait; - - private Memcached $memcache; - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Memcached'); - } - - public function __construct($servers) - { - if (!extension_loaded('memcached')) { - throw new \Exception('Memcache extension missing.'); - } - - $prefix = \Config::get()->STUDIP_INSTALLATION_ID; - $this->memcache = new Memcached('studip' . ($prefix ? '-' . $prefix : '')); - - if (count($this->memcache->getServerList()) === 0) { - foreach ($servers as $server) { - $status = $this->memcache->addServer($server['hostname'], (int) $server['port']); - - if (!$status) { - throw new \Exception("Could not add server: {$server['hostname']} @ port {$server['port']}"); - } - } - } - } - - /** - * Expire item from the cache. - * - * Example: - * - * # expires foo - * $cache->expire('foo'); - * - * @param string $arg a single key. - * @returns void - */ - public function expire($arg) - { - $key = $this->getCacheKey($arg); - $this->memcache->delete($key); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $this->memcache->flush(); - } - - /** - * Return statistics. - * - * @StudipCache::getStats() - * - * @return array|array[] - */ - public function getStats(): array - { - return $this->memcache->getStats(); - } - - /** - * Return the Vue component name and props that handle configuration. - * - * @see Cache::getConfig() - * - * @return array - */ - public static function getConfig(): array - { - $currentCache = \Config::get()->SYSTEMCACHE; - - // Set default config for this cache - $currentConfig = [ - 'servers' => [] - ]; - - // If this cache is set as system cache, use config from global settings. - if ($currentCache['type'] == __CLASS__) { - $currentConfig = $currentCache['config']; - } - - return [ - 'component' => 'MemcachedCacheConfig', - 'props' => $currentConfig - ]; - } - - /** - * @inheritDoc - */ - public function getItem(string $key): CacheItemInterface - { - $item = new Item($key); - $value = $this->memcache->get($this->getCacheKey($key)); - if ($this->memcache->getResultCode() !== Memcached::RES_NOTFOUND) { - // Set the value, even if it is the boolean value false: - $item->setHit(); - $item->set($value); - } - return $item; - } - - /** - * @inheritDoc - */ - public function hasItem(string $key): bool - { - return $this->memcache->checkKey($this->getCacheKey($key)); - } - - /** - * @inheritDoc - */ - public function save(CacheItemInterface $item): bool - { - $expiration = $this->getExpiration($item); - if ($expiration < 1) { - // The item would expire immediately. - return false; - } - - $real_key = $this->getCacheKey($item->getKey()); - return $this->memcache->set($real_key, $item->get(), $expiration); - } -} diff --git a/lib/classes/cache/MemcachedCache.php b/lib/classes/cache/MemcachedCache.php new file mode 100644 index 0000000..1c4b685 --- /dev/null +++ b/lib/classes/cache/MemcachedCache.php @@ -0,0 +1,151 @@ + + * @copyright (c) Authors + * @license GPL2 or any later version + * @since 5.0 + */ +class MemcachedCache extends Cache +{ + use KeyTrait; + + private Memcached $memcache; + + /** + * @return string A translateable display name for this cache class. + */ + public static function getDisplayName(): string + { + return _('Memcached'); + } + + public function __construct($servers) + { + if (!extension_loaded('memcached')) { + throw new \Exception('Memcache extension missing.'); + } + + $prefix = \Config::get()->STUDIP_INSTALLATION_ID; + $this->memcache = new Memcached('studip' . ($prefix ? '-' . $prefix : '')); + + if (count($this->memcache->getServerList()) === 0) { + foreach ($servers as $server) { + $status = $this->memcache->addServer($server['hostname'], (int) $server['port']); + + if (!$status) { + throw new \Exception("Could not add server: {$server['hostname']} @ port {$server['port']}"); + } + } + } + } + + /** + * Expire item from the cache. + * + * Example: + * + * # expires foo + * $cache->expire('foo'); + * + * @param string $arg a single key. + * @returns void + */ + public function expire($arg) + { + $key = $this->getCacheKey($arg); + $this->memcache->delete($key); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $this->memcache->flush(); + } + + /** + * Return statistics. + * + * @StudipCache::getStats() + * + * @return array|array[] + */ + public function getStats(): array + { + return $this->memcache->getStats(); + } + + /** + * Return the Vue component name and props that handle configuration. + * + * @see Cache::getConfig() + * + * @return array + */ + public static function getConfig(): array + { + $currentCache = \Config::get()->SYSTEMCACHE; + + // Set default config for this cache + $currentConfig = [ + 'servers' => [] + ]; + + // If this cache is set as system cache, use config from global settings. + if ($currentCache['type'] == __CLASS__) { + $currentConfig = $currentCache['config']; + } + + return [ + 'component' => 'MemcachedCacheConfig', + 'props' => $currentConfig + ]; + } + + /** + * @inheritDoc + */ + public function getItem(string $key): CacheItemInterface + { + $item = new Item($key); + $value = $this->memcache->get($this->getCacheKey($key)); + if ($this->memcache->getResultCode() !== Memcached::RES_NOTFOUND) { + // Set the value, even if it is the boolean value false: + $item->setHit(); + $item->set($value); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem(string $key): bool + { + return $this->memcache->checkKey($this->getCacheKey($key)); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item): bool + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + return $this->memcache->set($real_key, $item->get(), $expiration); + } +} diff --git a/lib/classes/cache/MemoryCache.class.php b/lib/classes/cache/MemoryCache.class.php deleted file mode 100644 index 7c00753..0000000 --- a/lib/classes/cache/MemoryCache.class.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 5.0 - */ -class MemoryCache extends Cache -{ - protected array $memory_cache = []; - - /** - * Expires just a single key. - * - * @param string $arg the key - */ - public function expire($arg) - { - unset($this->memory_cache[$arg]); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $this->memory_cache = []; - } - - public static function getDisplayName(): string - { - return 'Memory cache'; - } - - public function getStats(): array - { - return []; - } - - public static function getConfig(): array - { - return []; - } - - /** - * @inheritDoc - */ - public function getItem(string $key): CacheItemInterface - { - $item = new Item($key); - if (!isset($this->memory_cache[$key])) { - return $item; - } - if ($this->memory_cache[$key]['expires'] < time()) { - $this->expire($key); - return $item; - } - $item->setHit(); - $item->set($this->memory_cache[$key]['data']); - if (!empty($this->memory_cache[$key]['expires'])) { - $expiration = new DateTime(); - $expiration->setTimestamp($this->memory_cache[$key]['expires']); - $item->expiresAt($expiration); - } - return $item; - } - - /** - * @inheritDoc - */ - public function hasItem(string $key): bool - { - return isset($this->memory_cache[$key]) - && $this->memory_cache[$key]['expires'] < time(); - } - - /** - * @inheritDoc - */ - public function save(CacheItemInterface $item): bool - { - $expiration = $this->getExpiration($item); - - $this->memory_cache[$item->getKey()] = [ - 'expires' => $expiration + time(), - 'data' => $item->get(), - ]; - - return true; - } -} diff --git a/lib/classes/cache/MemoryCache.php b/lib/classes/cache/MemoryCache.php new file mode 100644 index 0000000..7c00753 --- /dev/null +++ b/lib/classes/cache/MemoryCache.php @@ -0,0 +1,98 @@ + + * @license GPL2 or any later version + * @since Stud.IP 5.0 + */ +class MemoryCache extends Cache +{ + protected array $memory_cache = []; + + /** + * Expires just a single key. + * + * @param string $arg the key + */ + public function expire($arg) + { + unset($this->memory_cache[$arg]); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $this->memory_cache = []; + } + + public static function getDisplayName(): string + { + return 'Memory cache'; + } + + public function getStats(): array + { + return []; + } + + public static function getConfig(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getItem(string $key): CacheItemInterface + { + $item = new Item($key); + if (!isset($this->memory_cache[$key])) { + return $item; + } + if ($this->memory_cache[$key]['expires'] < time()) { + $this->expire($key); + return $item; + } + $item->setHit(); + $item->set($this->memory_cache[$key]['data']); + if (!empty($this->memory_cache[$key]['expires'])) { + $expiration = new DateTime(); + $expiration->setTimestamp($this->memory_cache[$key]['expires']); + $item->expiresAt($expiration); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem(string $key): bool + { + return isset($this->memory_cache[$key]) + && $this->memory_cache[$key]['expires'] < time(); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item): bool + { + $expiration = $this->getExpiration($item); + + $this->memory_cache[$item->getKey()] = [ + 'expires' => $expiration + time(), + 'data' => $item->get(), + ]; + + return true; + } +} diff --git a/lib/classes/cache/Proxy.class.php b/lib/classes/cache/Proxy.class.php deleted file mode 100644 index fef034d..0000000 --- a/lib/classes/cache/Proxy.class.php +++ /dev/null @@ -1,123 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 3.3 - */ -class Proxy extends Cache -{ - protected Cache $actual_cache; - protected array $proxy_these; - - /** - * @param Cache $cache The actual cache object - * @param mixed $proxy_these List of operations to proxy (should be an - * array but a space seperated string is also - * valid) - */ - public function __construct(Cache $cache, $proxy_these = ['expire']) - { - if (!is_array($proxy_these)) { - $proxy_these = words($proxy_these); - } - - $this->actual_cache = $cache; - $this->proxy_these = is_array($proxy_these) - ? $proxy_these - : words($proxy_these); - } - - /** - * Expires just a single key. - * - * @param string $arg The item's key - */ - public function expire($arg) - { - if (in_array('expire', $this->proxy_these)) { - try { - $operation = new StudipCacheOperation([$arg, 'expire']); - $operation->parameters = serialize([]); - $operation->store(); - } catch (\Exception) { - } - } - - return $this->actual_cache->expire($arg); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - if (in_array('flush', $this->proxy_these)) { - try { - $operation = new StudipCacheOperation(['', 'flush']); - $operation->parameters = serialize([]); - $operation->store(); - } catch (\Exception) { - } - } - - return $this->actual_cache->flush(); - } - - public static function getDisplayName(): string - { - return static::class; - } - - public function getStats(): array - { - return $this->actual_cache->getStats(); - } - - public static function getConfig(): array - { - return []; - } - - /** - * @inheritDoc - */ - public function getItem(string $key): CacheItemInterface - { - return $this->actual_cache->getItem($key); - } - - /** - * @inheritDoc - */ - public function hasItem(string $key): bool - { - return $this->actual_cache->hasItem($key); - } - - /** - * @inheritDoc - */ - public function save(CacheItemInterface $item): bool - { - if (in_array('save', $this->proxy_these)) { - try { - $operation = new StudipCacheOperation([$item->getKey(), 'save']); - $operation->parameters = serialize([$item]); - $operation->store(); - } catch (\Exception) { - } - } - - return $this->actual_cache->save($item); - } -} diff --git a/lib/classes/cache/Proxy.php b/lib/classes/cache/Proxy.php new file mode 100644 index 0000000..fef034d --- /dev/null +++ b/lib/classes/cache/Proxy.php @@ -0,0 +1,123 @@ + + * @license GPL2 or any later version + * @since Stud.IP 3.3 + */ +class Proxy extends Cache +{ + protected Cache $actual_cache; + protected array $proxy_these; + + /** + * @param Cache $cache The actual cache object + * @param mixed $proxy_these List of operations to proxy (should be an + * array but a space seperated string is also + * valid) + */ + public function __construct(Cache $cache, $proxy_these = ['expire']) + { + if (!is_array($proxy_these)) { + $proxy_these = words($proxy_these); + } + + $this->actual_cache = $cache; + $this->proxy_these = is_array($proxy_these) + ? $proxy_these + : words($proxy_these); + } + + /** + * Expires just a single key. + * + * @param string $arg The item's key + */ + public function expire($arg) + { + if (in_array('expire', $this->proxy_these)) { + try { + $operation = new StudipCacheOperation([$arg, 'expire']); + $operation->parameters = serialize([]); + $operation->store(); + } catch (\Exception) { + } + } + + return $this->actual_cache->expire($arg); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + if (in_array('flush', $this->proxy_these)) { + try { + $operation = new StudipCacheOperation(['', 'flush']); + $operation->parameters = serialize([]); + $operation->store(); + } catch (\Exception) { + } + } + + return $this->actual_cache->flush(); + } + + public static function getDisplayName(): string + { + return static::class; + } + + public function getStats(): array + { + return $this->actual_cache->getStats(); + } + + public static function getConfig(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getItem(string $key): CacheItemInterface + { + return $this->actual_cache->getItem($key); + } + + /** + * @inheritDoc + */ + public function hasItem(string $key): bool + { + return $this->actual_cache->hasItem($key); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item): bool + { + if (in_array('save', $this->proxy_these)) { + try { + $operation = new StudipCacheOperation([$item->getKey(), 'save']); + $operation->parameters = serialize([$item]); + $operation->store(); + } catch (\Exception) { + } + } + + return $this->actual_cache->save($item); + } +} diff --git a/lib/classes/cache/RedisCache.class.php b/lib/classes/cache/RedisCache.class.php deleted file mode 100644 index a78f751..0000000 --- a/lib/classes/cache/RedisCache.class.php +++ /dev/null @@ -1,198 +0,0 @@ - - * @license GPL2 or any later version - * @package studip - * @subpackage cache - * @since Stud.IP 5.0 - */ -class RedisCache extends Cache -{ - use KeyTrait; - - private $redis; - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Redis'); - } - - /** - * Construct a cache instance. - * - * @param string $hostname Hostname of redis server - * @param int $port Port of redis server - * @param string $auth Optional auth token/password - * - * @throws RedisException - */ - public function __construct($hostname, $port, string $auth = '') - { - if (!extension_loaded('redis')) { - throw new Exception('Redis extension missing.'); - } - - $this->redis = new Redis(); - $status = $this->redis->connect($hostname, $port, 1); - - if (!$status) { - throw new Exception('Could not add cache.'); - } - - if ($auth !== '') { - $this->redis->auth($auth); - } - } - - /** - * Returns the instance of the redis server connection. - * - * @return Redis instance - */ - public function getRedis() - { - return $this->redis; - } - - /** - * Expire item from the cache. - * - * Example: - * - * # expires foo - * $cache->expire('foo'); - * - * @param string $arg a single key. - */ - public function expire($arg) - { - $key = $this->getCacheKey($arg); - $this->redis->unlink($key); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $pattern = $this->getCacheKey('*'); - foreach ($this->redis->keys($pattern) as $key) { - $this->redis->unlink($key); - } - } - - /** - * @param string $method Method to call - * @param array $args Arguments to pass - * @return false|mixed - */ - public function __call($method, $args) - { - if (is_callable([$this->redis, $method])) { - return call_user_func_array([$this->redis, $method], $args); - } - throw new BadMethodCallException("Method {$method} does not exist"); - } - - /** - * Return statistics. - * - * @StudipCache::getStats() - * - * @return array|array[] - */ - public function getStats(): array - { - $stats = $this->redis->info(); - $stats['size'] = count($this->redis->keys($this->getCacheKey('*'))); - return ["{$this->redis->getHost()}:{$this->redis->getPort()}" => $stats]; - } - - /* - * Return the Vue component name and props that handle configuration. - * - * @see StudipCache::getConfig() - * - * @return array - */ - public static function getConfig(): array - { - $currentCache = Config::get()->SYSTEMCACHE; - - // Set default config for this cache - $currentConfig = [ - 'hostname' => '', - 'port' => null - ]; - - // If this cache is set as system cache, use config from global settings. - if ($currentCache['type'] == __CLASS__) { - $currentConfig = $currentCache['config']; - $currentConfig['port'] = $currentConfig['port'] ? (int) $currentConfig['port'] : null; - } - - return [ - 'component' => 'RedisCacheConfig', - 'props' => $currentConfig - ]; - } - - /** - * @inheritDoc - */ - public function getItem(string $key): CacheItemInterface - { - $item = new Item($key); - $real_key = $this->getCacheKey($key); - $result = $this->redis->get($real_key); - if ($result === null) { - return $item; - } - $item->setHit(); - $item->set(unserialize($result)); - $expiration = new DateTime(); - $expiration->setTimestamp($this->redis->expiretime($real_key)); - $item->expiresAt($expiration); - return $item; - } - - /** - * @inheritDoc - */ - public function hasItem(string $key): bool - { - $real_key = $this->getCacheKey($key); - return $this->redis->get($real_key) !== null; - } - - /** - * @inheritDoc - */ - public function save(CacheItemInterface $item): bool - { - $expiration = $this->getExpiration($item); - if ($expiration < 1) { - // The item would expire immediately. - return false; - } - - $real_key = $this->getCacheKey($item->getKey()); - return $this->redis->setEx($real_key, $expiration, serialize($item->get())); - } -} diff --git a/lib/classes/cache/RedisCache.php b/lib/classes/cache/RedisCache.php new file mode 100644 index 0000000..a78f751 --- /dev/null +++ b/lib/classes/cache/RedisCache.php @@ -0,0 +1,198 @@ + + * @license GPL2 or any later version + * @package studip + * @subpackage cache + * @since Stud.IP 5.0 + */ +class RedisCache extends Cache +{ + use KeyTrait; + + private $redis; + + /** + * @return string A translateable display name for this cache class. + */ + public static function getDisplayName(): string + { + return _('Redis'); + } + + /** + * Construct a cache instance. + * + * @param string $hostname Hostname of redis server + * @param int $port Port of redis server + * @param string $auth Optional auth token/password + * + * @throws RedisException + */ + public function __construct($hostname, $port, string $auth = '') + { + if (!extension_loaded('redis')) { + throw new Exception('Redis extension missing.'); + } + + $this->redis = new Redis(); + $status = $this->redis->connect($hostname, $port, 1); + + if (!$status) { + throw new Exception('Could not add cache.'); + } + + if ($auth !== '') { + $this->redis->auth($auth); + } + } + + /** + * Returns the instance of the redis server connection. + * + * @return Redis instance + */ + public function getRedis() + { + return $this->redis; + } + + /** + * Expire item from the cache. + * + * Example: + * + * # expires foo + * $cache->expire('foo'); + * + * @param string $arg a single key. + */ + public function expire($arg) + { + $key = $this->getCacheKey($arg); + $this->redis->unlink($key); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $pattern = $this->getCacheKey('*'); + foreach ($this->redis->keys($pattern) as $key) { + $this->redis->unlink($key); + } + } + + /** + * @param string $method Method to call + * @param array $args Arguments to pass + * @return false|mixed + */ + public function __call($method, $args) + { + if (is_callable([$this->redis, $method])) { + return call_user_func_array([$this->redis, $method], $args); + } + throw new BadMethodCallException("Method {$method} does not exist"); + } + + /** + * Return statistics. + * + * @StudipCache::getStats() + * + * @return array|array[] + */ + public function getStats(): array + { + $stats = $this->redis->info(); + $stats['size'] = count($this->redis->keys($this->getCacheKey('*'))); + return ["{$this->redis->getHost()}:{$this->redis->getPort()}" => $stats]; + } + + /* + * Return the Vue component name and props that handle configuration. + * + * @see StudipCache::getConfig() + * + * @return array + */ + public static function getConfig(): array + { + $currentCache = Config::get()->SYSTEMCACHE; + + // Set default config for this cache + $currentConfig = [ + 'hostname' => '', + 'port' => null + ]; + + // If this cache is set as system cache, use config from global settings. + if ($currentCache['type'] == __CLASS__) { + $currentConfig = $currentCache['config']; + $currentConfig['port'] = $currentConfig['port'] ? (int) $currentConfig['port'] : null; + } + + return [ + 'component' => 'RedisCacheConfig', + 'props' => $currentConfig + ]; + } + + /** + * @inheritDoc + */ + public function getItem(string $key): CacheItemInterface + { + $item = new Item($key); + $real_key = $this->getCacheKey($key); + $result = $this->redis->get($real_key); + if ($result === null) { + return $item; + } + $item->setHit(); + $item->set(unserialize($result)); + $expiration = new DateTime(); + $expiration->setTimestamp($this->redis->expiretime($real_key)); + $item->expiresAt($expiration); + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem(string $key): bool + { + $real_key = $this->getCacheKey($key); + return $this->redis->get($real_key) !== null; + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item): bool + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + return $this->redis->setEx($real_key, $expiration, serialize($item->get())); + } +} diff --git a/lib/classes/cache/Wrapper.class.php b/lib/classes/cache/Wrapper.class.php deleted file mode 100644 index 4e6342c..0000000 --- a/lib/classes/cache/Wrapper.class.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 5.4 - */ -class Wrapper extends Cache -{ - protected Cache $actual_cache; - protected MemoryCache $memory_cache; - - public function __construct(Cache $actual_cache) - { - $this->actual_cache = $actual_cache; - $this->memory_cache = new MemoryCache(); - } - - /** - * @inheritdoc - */ - public function expire($arg) - { - $this->memory_cache->expire($arg); - $this->actual_cache->expire($arg); - } - - /** - * @inheritdoc - */ - public function flush() - { - $this->memory_cache->flush(); - $this->actual_cache->flush(); - } - - public static function getDisplayName(): string - { - return static::class; - } - - public function getStats(): array - { - return $this->actual_cache->getStats(); - } - - public static function getConfig(): array - { - return []; - } - - /** - * @inheritDoc - */ - public function getItem(string $key): CacheItemInterface - { - $cached = $this->memory_cache->getItem($key); - if ($cached->isHit()) { - return $cached; - } - - $cached = $this->actual_cache->getItem($key); - if ($cached->isHit()) { - $this->memory_cache->save($cached); - } - return $cached; - } - - /** - * @inheritDoc - */ - public function hasItem(string $key): bool - { - return $this->actual_cache->hasItem($key); - } - - /** - * @inheritDoc - */ - public function save(CacheItemInterface $item): bool - { - if ($this->actual_cache->save($item)) { - return $this->memory_cache->save($item); - } else { - return false; - } - } -} diff --git a/lib/classes/cache/Wrapper.php b/lib/classes/cache/Wrapper.php new file mode 100644 index 0000000..4e6342c --- /dev/null +++ b/lib/classes/cache/Wrapper.php @@ -0,0 +1,95 @@ + + * @license GPL2 or any later version + * @since Stud.IP 5.4 + */ +class Wrapper extends Cache +{ + protected Cache $actual_cache; + protected MemoryCache $memory_cache; + + public function __construct(Cache $actual_cache) + { + $this->actual_cache = $actual_cache; + $this->memory_cache = new MemoryCache(); + } + + /** + * @inheritdoc + */ + public function expire($arg) + { + $this->memory_cache->expire($arg); + $this->actual_cache->expire($arg); + } + + /** + * @inheritdoc + */ + public function flush() + { + $this->memory_cache->flush(); + $this->actual_cache->flush(); + } + + public static function getDisplayName(): string + { + return static::class; + } + + public function getStats(): array + { + return $this->actual_cache->getStats(); + } + + public static function getConfig(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getItem(string $key): CacheItemInterface + { + $cached = $this->memory_cache->getItem($key); + if ($cached->isHit()) { + return $cached; + } + + $cached = $this->actual_cache->getItem($key); + if ($cached->isHit()) { + $this->memory_cache->save($cached); + } + return $cached; + } + + /** + * @inheritDoc + */ + public function hasItem(string $key): bool + { + return $this->actual_cache->hasItem($key); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item): bool + { + if ($this->actual_cache->save($item)) { + return $this->memory_cache->save($item); + } else { + return false; + } + } +} diff --git a/lib/classes/calendar/EventData.class.php b/lib/classes/calendar/EventData.class.php deleted file mode 100644 index 95e89b0..0000000 --- a/lib/classes/calendar/EventData.class.php +++ /dev/null @@ -1,114 +0,0 @@ -begin = $begin; - $this->end = $end; - $this->title = $title; - $this->event_classes = $event_classes; - $this->text_colour = $text_colour; - $this->background_colour = $background_colour; - $this->editable = $editable; - $this->object_class = $object_class; - $this->object_id = $object_id; - $this->parent_object_class = $parent_object_class; - $this->parent_object_id = $parent_object_id; - $this->range_type = $range_type; - $this->range_id = $range_id; - $this->view_urls = $view_urls; - $this->api_urls = $api_urls; - $this->icon = $icon; - $this->border_colour = $border_colour ?: $background_colour; - $this->all_day = $all_day; - } - - - public function toFullcalendarEvent() - { - // Note: The timezone must not be transmitted or - // the events may be shifted when there is a timezone - // or daylight saving time difference between the server - // and the client! - // To display all-day events correctly in fullcalendar - // reduce the start and end to date without time and add one day - // to the end date... - if ($this->all_day) { - $fc_date = [ - 'allDay' => true, - 'start' => $this->begin->format('Y-m-d'), - 'end' => $this->end->modify('+1 day')->format('Y-m-d') - ]; - } else { - $fc_date = [ - 'allDay' => false, - 'start' => $this->begin->format('Y-m-d\TH:i:s'), - 'end' => $this->end->format('Y-m-d\TH:i:s') - ]; - } - - return $fc_date + [ - 'resourceId' => $this->range_id, - 'title' => $this->title, - 'classNames' => $this->event_classes, - 'textColor' => $this->text_colour, - 'color' => $this->background_colour, - 'borderColor' => $this->border_colour, - 'editable' => $this->editable, - 'studip_weekday_begin' => $this->begin->format('N'), - 'studip_weekday_end' => $this->end->format('N'), - 'studip_object_class' => $this->object_class, - 'studip_object_id' => $this->object_id, - 'studip_parent_object_class' => $this->parent_object_class, - 'studip_parent_object_id' => $this->parent_object_id, - 'studip_range_type' => $this->range_type, - 'studip_range_id' => $this->range_id, - 'studip_view_urls' => $this->view_urls, - 'studip_api_urls' => $this->api_urls, - 'icon' => $this->icon - ]; - } -} diff --git a/lib/classes/calendar/EventData.php b/lib/classes/calendar/EventData.php new file mode 100644 index 0000000..95e89b0 --- /dev/null +++ b/lib/classes/calendar/EventData.php @@ -0,0 +1,114 @@ +begin = $begin; + $this->end = $end; + $this->title = $title; + $this->event_classes = $event_classes; + $this->text_colour = $text_colour; + $this->background_colour = $background_colour; + $this->editable = $editable; + $this->object_class = $object_class; + $this->object_id = $object_id; + $this->parent_object_class = $parent_object_class; + $this->parent_object_id = $parent_object_id; + $this->range_type = $range_type; + $this->range_id = $range_id; + $this->view_urls = $view_urls; + $this->api_urls = $api_urls; + $this->icon = $icon; + $this->border_colour = $border_colour ?: $background_colour; + $this->all_day = $all_day; + } + + + public function toFullcalendarEvent() + { + // Note: The timezone must not be transmitted or + // the events may be shifted when there is a timezone + // or daylight saving time difference between the server + // and the client! + // To display all-day events correctly in fullcalendar + // reduce the start and end to date without time and add one day + // to the end date... + if ($this->all_day) { + $fc_date = [ + 'allDay' => true, + 'start' => $this->begin->format('Y-m-d'), + 'end' => $this->end->modify('+1 day')->format('Y-m-d') + ]; + } else { + $fc_date = [ + 'allDay' => false, + 'start' => $this->begin->format('Y-m-d\TH:i:s'), + 'end' => $this->end->format('Y-m-d\TH:i:s') + ]; + } + + return $fc_date + [ + 'resourceId' => $this->range_id, + 'title' => $this->title, + 'classNames' => $this->event_classes, + 'textColor' => $this->text_colour, + 'color' => $this->background_colour, + 'borderColor' => $this->border_colour, + 'editable' => $this->editable, + 'studip_weekday_begin' => $this->begin->format('N'), + 'studip_weekday_end' => $this->end->format('N'), + 'studip_object_class' => $this->object_class, + 'studip_object_id' => $this->object_id, + 'studip_parent_object_class' => $this->parent_object_class, + 'studip_parent_object_id' => $this->parent_object_id, + 'studip_range_type' => $this->range_type, + 'studip_range_id' => $this->range_id, + 'studip_view_urls' => $this->view_urls, + 'studip_api_urls' => $this->api_urls, + 'icon' => $this->icon + ]; + } +} diff --git a/lib/classes/calendar/EventSource.interface.php b/lib/classes/calendar/EventSource.interface.php deleted file mode 100644 index 48506d7..0000000 --- a/lib/classes/calendar/EventSource.interface.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @author Moritz Strohm - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 5.5 - */ - - -class ICalendarExport -{ - /** - * Line break used in iCalendar - */ - const NEWLINE = "\r\n"; - - /** - * Default start of the week - */ - const WEEKSTART = 'MO'; - - /** - * Holds the time (as unix timestamp) used for - * the timestamp in every exported iCalendar object. - * - * @var int $time - */ - private $time = 0; - - public function __construct() - { - $this->default_filename_suffix = "ics"; - $this->format = "iCalendar"; - } - - public function exportCalendarDates(string $range_id, DateTimeInterface $start, DateTimeInterface $end): string - { - if ($this->time === 0) { - $this->time = time(); - } - $dates = CalendarDate::findBySQL( - "LEFT JOIN `calendar_date_assignments` - ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` - WHERE - `calendar_date_assignments`.`range_id` = :range_id - AND ( - (`calendar_dates`.`begin` <= :end - AND `calendar_dates`.`end` >= :begin) - OR (`calendar_dates`.`repetition_type` != 'SINGLE' - AND (`calendar_dates`.`repetition_end` >= :begin - OR `calendar_dates`.`repetition_end` = 0) - AND `calendar_dates`.`begin` < :end))", - [ - ':range_id' => $range_id, - ':begin' => $start->getTimestamp(), - ':end' => $end->getTimestamp(), - ] - ); - $ical = ''; - foreach ($dates as $date) { - $ical .= $this->writeICalEvent($this->prepareCalendarDate($date)); - } - return $ical; - } - - public function exportCourseDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end) - { - if ($this->time === 0) { - $this->time = time(); - } - $dates = CalendarCourseDate::getEvents($start, $end, $user_id); - $ical = ''; - foreach ($dates as $date) { - $ical .= $this->writeICalEvent($this->prepareCourseDate($date)); - } - return $ical; - } - - public function exportCourseExDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end) - { - if ($this->time === 0) { - $this->time = time(); - } - $dates = CalendarCourseExDate::getEvents($start, $end, $user_id); - $ical = ''; - foreach ($dates as $date) { - $ical .= $this->writeICalEvent($this->prepareCourseDate($date)); - } - return $ical; - } - - /** - * @param CalendarDate $date The calendar date to export. - * @return array Calendar date data prepared for export. - */ - public function prepareCalendarDate(CalendarDate $date): array - { - return [ - 'SUMMARY' => $date->title, - 'DESCRIPTION' => $date->description, - 'LOCATION' => $date->location, - 'CATEGORIES' => $date->getCategoryAsString(), - 'LAST-MODIFIED' => $date->chdate, - 'CREATED' => $date->mkdate, - 'DTSTAMP' => $this->time, - 'DTSTART' => $date->begin, - 'DTEND' => $date->end, - 'EXDATE' => implode(',', $date->exceptions->pluck('date')), - 'PRIORITY' => 5, - 'RRULE' => [ - 'type' => $date->repetition_type, - 'offset' => $date->offset, - 'interval' => $date->interval, - 'days' => $date->days, - 'count' => $date->number_of_dates, - 'expire' => $date->repetition_end, - 'month' => $date->month - ], - 'UID' => $date->unique_id - ]; - } - - /** - * @param CourseDate | CourseExDate $date The course date to export. - * @return array Course date data prepared for export. - */ - public function prepareCourseDate($date): array - { - $summary = $date->course->getFullName(); - $categories = $date->getTypeName(); - if ($date instanceof CourseExDate) { - $summary .= ' ' . _('(fällt aus)'); - $categories = ''; - $description = $date->content; - } else { - $description = implode("\n", $date->topics->pluck('title')); - } - return [ - 'SUMMARY' => $summary, - 'DESCRIPTION' => $description, - 'LOCATION' => $date->getRoomName(), - 'CATEGORIES' => $categories, - 'LAST-MODIFIED' => $date->chdate, - 'CREATED' => $date->mkdate, - 'DTSTAMP' => $this->time, - 'DTSTART' => $date->date, - 'DTEND' => $date->end_time, - 'PRIORITY' => '', - 'UID' => 'Stud.IP-SEM-' . $date->id . '@' . ($_SERVER['SERVER_NAME'] ?? '') - ]; - } - - /** - * Returns an iCalendar header with a rudimentary time zone definition. - * - * @return string The iCalendar header. - */ - public function writeHeader() - { - // Default values - $header = "BEGIN:VCALENDAR" . self::NEWLINE; - $header .= "VERSION:2.0" . self::NEWLINE; - if (isset($this->client_identifier)) { - $header .= "PRODID:" . $this->client_identifier . self::NEWLINE; - } else { - $server_name = $_SERVER['SERVER_NAME'] ?? 'unknown'; - - $header .= "PRODID:-//Stud.IP@{$server_name}//Stud.IP_iCalendar Library"; - $header .= " //EN" . self::NEWLINE; - } - $header .= "METHOD:PUBLISH" . self::NEWLINE; - - // time zone definition CET/CEST - $header .= 'CALSCALE:GREGORIAN' . self::NEWLINE - . 'BEGIN:VTIMEZONE' . self::NEWLINE - . 'TZID:Europe/Berlin' . self::NEWLINE - . 'BEGIN:DAYLIGHT' . self::NEWLINE - . 'TZOFFSETFROM:+0100' . self::NEWLINE - . 'TZOFFSETTO:+0200' . self::NEWLINE - . 'TZNAME:CEST' . self::NEWLINE - . 'DTSTART:19700329T020000' . self::NEWLINE - . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3' . self::NEWLINE - . 'END:DAYLIGHT' . self::NEWLINE - . 'BEGIN:STANDARD' . self::NEWLINE - . 'TZOFFSETFROM:+0200' . self::NEWLINE - . 'TZOFFSETTO:+0100' . self::NEWLINE - . 'TZNAME:CET' . self::NEWLINE - . 'DTSTART:19701025T030000' . self::NEWLINE - . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10' . self::NEWLINE - . 'END:STANDARD' . self::NEWLINE - . 'END:VTIMEZONE' .self::NEWLINE; - - return $header; - } - - /** - * Returns the footer. - * - * @return string - */ - public function writeFooter() - { - return "END:VCALENDAR" . self::NEWLINE; - } - - /** - * Export prepared calendar data as iCalendar. - * - * @param array $properties The event to export. - * @return string iCalendar formatted data - */ - public function writeICalEvent(array $properties): string - { - $result = "BEGIN:VEVENT" . self::NEWLINE; - - foreach ($properties as $name => $value) { - $params = []; - $params_str = ''; - if ($value === '' || is_null($value)) { - continue; - } - switch ($name) { - // not supported event properties - case 'SEMNAME': - continue 2; - - // Text fields - case 'SUMMARY': - $value = $this->quoteText($value); - break; - case 'DESCRIPTION': - $value = $this->quoteText($value); - break; - case 'LOCATION': - $value = $this->quoteText($value); - break; - case 'CATEGORIES': - $value = $this->quoteText($value); - break; - - // Date fields - case 'LAST-MODIFIED': - case 'CREATED': - case 'COMPLETED': - $value = $this->_exportDateTime($value, true); - break; - - case 'DTSTAMP': - $value = $this->_exportDateTime(time(), true); - break; - - case 'DTSTART': - $exdate_time = $value; - case 'DTEND': - case 'DUE': - case 'RECURRENCE-ID': - if (array_key_exists('VALUE', $params)) { - if ($params['VALUE'] === 'DATE') { - $value = $this->_exportDate($value); - } else { - $value = $this->_exportDateTime($value); - $params_str = ';TZID=Europe/Berlin'; - } - } else { - $value = $this->_exportDateTime($value); - $params_str = ';TZID=Europe/Berlin'; - } - break; - - case 'EXDATE': - if (array_key_exists('VALUE', $params) && $params['VALUE'] === 'DATE') { - $value = $this->exportExDate($value); - } else { - $value = $this->exportExDateTime($value, $exdate_time); - $params_str = ';TZID=Europe/Berlin'; - } - break; - - // Integer fields - case 'PERCENT-COMPLETE': - case 'REPEAT': - case 'SEQUENCE': - $value = "$value"; - break; - - case 'PRIORITY': - switch ($value) { - case 1: - $value = '1'; - break; - case 2: - $value = '5'; - break; - case 3: - $value = '9'; - break; - default: - $value = '0'; - } - break; - - // Geo fields - case 'GEO': - $value = $value['latitude'] . ',' . $value['longitude']; - break; - - // Recursion fields - case 'EXRULE': - case 'RRULE': - if ($value['type'] !== 'SINGLE' && $value['type'] !== '') { - $value = $this->_exportRecurrence($value); - } - break; - - case "UID": - $value = "$value"; - } - if ($name && !is_array($value)) { - $attr_string = $name . $params_str . ':' . $value; - $result .= $this->foldLine($attr_string) . self::NEWLINE; - } - } - if (isset($properties['GROUP_EVENT'])) { - $result .= $this->exportGroupEventProperties($properties['GROUP_EVENT']); - } - $result .= "END:VEVENT" . self::NEWLINE; - - return $result; - } - - /** - * Quotes some characters accordingly to iCalendar format. - * - * @param string $text The text to quote. - * @return string The quoted text. - */ - public function quoteText(string $text): string - { - $match = ['\\', '\n', ';', ',']; - $replace = ['\\\\', '\\n', '\;', '\,']; - return str_replace($match, $replace, $text); - } - - /** - * Export a DateTime field - * - * @param int $value Unix timestamp - * @return String Date and time (UTC) iCalendar formatted - */ - public function _exportDateTime($value, $utc = false) - { - $date_time = new DateTime(); - $date_time->setTimestamp(intval($value)); - //transform local time to UTC - if ($utc) { - $tz_utc = new DateTimeZone('UTC'); - $date_time->setTimezone($tz_utc); - return $date_time->format('Ymd\THis\Z'); - } - return $date_time->format('Ymd\THis'); - } - - /** - * Export a Time field - * - * @param int $value Unix timestamp - * @return String Time (UTC) iCalendar formatted - */ - public function _exportTime($value, $utc = false) - { - $time = date("His", $value); - if ($utc) { - $time .= 'Z'; - } - - return $time; - } - - /** - * Export a Date field - */ - public function _exportDate($value) - { - return date("Ymd", $value); - } - - /** - * Export a recurrence rule - */ - public function _exportRecurrence($value) - { - $rrule = []; - // the last day of week in a MONTHLY or YEARLY recurrence in the - // Stud.IP calendar is 5, in iCalendar it is -1 - if ($value['offset'] == '5') { - $value['offset'] = '-1'; - } - - if ($value['count'] > 1) { - unset($value['expire']); - } - - foreach ($value as $r_param => $r_value) { - if ($r_value) { - switch ($r_param) { - case 'type': - $rrule[] = 'FREQ=' . $r_value; - break; - case 'expire': - if ($r_value) - $rrule[] = 'UNTIL=' . $this->_exportDateTime($r_value, true); - break; - case 'interval': - $rrule[] = 'INTERVAL=' . $r_value; - break; - case 'days': - switch ($value['type']) { - case 'WEEKLY': - $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value); - break; - // Some CUAs (e.g. Outlook) don't understand the nWDAY syntax - // (where n is the nth ocurrence of the day in a given period of - // time and WDAY is the day of week) the RRULE uses the BYSETPOS - // rule. - case 'MONTHLY': - case 'YEARLY': - $rrule[] = 'BYDAY=' . $value['offset'] . $this->_exportWdays($r_value); - $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value); - if ($value['offset']) { - $rrule[] = 'BYSETPOS=' . $value['offset']; - } - break; - } - break; - case 'day': - $rrule[] = 'BYMONTHDAY=' . $r_value; - break; - case 'month': - $rrule[] = 'BYMONTH=' . $r_value; - break; - case 'count': - if ($r_value > 1) { - $rrule[] = 'COUNT=' . $r_value; - } - break; - } - } - } - - if ($value['type'] === 'WEEKLY' && self::WEEKSTART != 'MO') { - $rrule[] = 'WKST=' . self::WEEKSTART; - } - - return implode(';', $rrule); - } - - /** - * Return the days from CalendarDate::days as attribute of a event recurrence. - * - * @param string $value - * @return string - */ - public function _exportWdays(string $value): string - { - $wdays_map = ['1' => 'MO', '2' => 'TU', '3' => 'WE', '4' => 'TH', '5' => 'FR', - '6' => 'SA', '7' => 'SU']; - $wdays = []; - preg_match_all('/(\d)/', $value, $matches); - foreach ($matches[1] as $match) { - $wdays[] = $wdays_map[$match]; - } - return implode(',', $wdays); - } - - - /** - * Formats dates of exception. - * - * @param string $value Date values (Y-m-d) as csv list. - * @return string The formatted Exceptions. - */ - public function exportExDate(string $value): string - { - $ex_dates = []; - $dates = explode(',', $value); - foreach ($dates as $date) { - $ex_datetime = $date . ' 12:00:00'; - $ex_date = DateTime::createFromFormat('Y-m-d H:i:s', $ex_datetime); - $ex_dates[] = $this->_exportDate($ex_date->getTimestamp()); - } - - return implode(',', $ex_dates); - } - - /** - * Formats date times of exception. - * - * @param string $value Date values (Y-m-d) as csv list. - * @param int $begin Start date of event as unix timestamp. - * @return string The formatted Exceptions. - */ - public function exportExDateTime(string $value, int $begin): string - { - $ex_dates = []; - $dates = explode(',', $value); - foreach ($dates as $date) { - $ex_datetime = $date . date(' H:i:s', $begin); - $date_time = DateTime::createFromFormat('Y-m-d H:i:s', $ex_datetime); - $ex_dates[] = $date_time->format('Ymd\THis'); - } - return implode(',', $ex_dates); - } - - /** - * Returns iCalendar group event properties if the date has mor than one attendee. - * - * @param CalendarDate $date The date object to extract the group data from. - * @return string The formatted group event properties. - */ - private function exportGroupEventProperties(CalendarDate $date): string - { - if (!count($date->calendars)) { - return ''; - } - $organizer = $date->author; - if ($organizer) { - $properties = $this->foldLine('ORGANIZER;CN="' - . $organizer->getFullName() - . '":mailto:' . $organizer->Email) - . self::NEWLINE; - } else { - $properties = $this->foldLine('ORGANIZER;CN="' - . _('unbekannt') - . '":mailto:' . $GLOBALS['user']->email) - . self::NEWLINE; - } - foreach ($date->calendars as $calendar) { - if ($date->author_id === $calendar->range_id) { - if ($calendar->user) { - $properties .= $this->foldLine('ATTENDEE;' - . 'ROLE=REQ-PARTICIPANT;' - . 'CN="' . $calendar->user->getFullName() - . '":mailto:' . $calendar->user->Email) - . self::NEWLINE; - } else { - $properties = ''; - } - } else { - if ($calendar->user) { - switch ($calendar->participation) { - case 'ACCEPTED' : - $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' - . ';PARTSTAT=ACCEPTED'; - break; - case 'ACKNOWLEDGED' : - $attendee = 'ATTENDEE;ROLE=NON-PARTICIPANT' - . ';PARTSTAT=ACCEPTED' - . ';DELEGATED-TO="mailto:' - . $this->getFacultyEmail($organizer->id) - . '"'; - break; - case 'DECLINED' : - $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' - . ';PARTSTAT=DECLINED'; - break; - default : - $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'; - $attendee .= ';PARTSTAT=TENTATIVE'; - $attendee .= ';RSVP=TRUE'; - - } - $attendee .= ';CN="' . $calendar->user->getFullName() - . '":mailto:' . $calendar->user->Email; - $properties .= $this->foldLine($attendee) . self::NEWLINE; - } - } - } - return $properties; - } - - /** - * @param string $user_id - * @return string - */ - private function getFacultyEmail(string $user_id): string - { - return DBManager::get()->fetchColumn(' - SELECT `email` - FROM `Institute` - LEFT JOIN `user_inst` USING(`institut_id`) - WHERE `Institute`.`Institut_id` = `fakultaets_id` - AND `user_id` = ?', [$user_id]); - } - - /** - * Returns the folded version of a text line. - * - * @param string $line - * @return string - */ - private function foldLine(string $line): string - { - $line = preg_replace('/(\r\n|\n|\r)/', '\n', $line); - if (mb_strlen($line) > 75) { - $foldedline = ''; - while ($line !== '') { - $maxLine = mb_substr($line, 0, 75); - $cutPoint = max(60, max(mb_strrpos($maxLine, ';'), mb_strrpos($maxLine, ':')) + 1); - - $foldedline .= ( empty($foldedline)) ? - mb_substr($line, 0, $cutPoint) : - self::NEWLINE . ' ' . mb_substr($line, 0, $cutPoint); - - $line = (mb_strlen($line) <= $cutPoint) ? '' : mb_substr($line, $cutPoint); - } - return $foldedline; - } - return $line; - } -} diff --git a/lib/classes/calendar/ICalendarExport.php b/lib/classes/calendar/ICalendarExport.php new file mode 100644 index 0000000..00147ef --- /dev/null +++ b/lib/classes/calendar/ICalendarExport.php @@ -0,0 +1,628 @@ + + * @author Moritz Strohm + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 5.5 + */ + + +class ICalendarExport +{ + /** + * Line break used in iCalendar + */ + const NEWLINE = "\r\n"; + + /** + * Default start of the week + */ + const WEEKSTART = 'MO'; + + /** + * Holds the time (as unix timestamp) used for + * the timestamp in every exported iCalendar object. + * + * @var int $time + */ + private $time = 0; + + public function __construct() + { + $this->default_filename_suffix = "ics"; + $this->format = "iCalendar"; + } + + public function exportCalendarDates(string $range_id, DateTimeInterface $start, DateTimeInterface $end): string + { + if ($this->time === 0) { + $this->time = time(); + } + $dates = CalendarDate::findBySQL( + "LEFT JOIN `calendar_date_assignments` + ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` + WHERE + `calendar_date_assignments`.`range_id` = :range_id + AND ( + (`calendar_dates`.`begin` <= :end + AND `calendar_dates`.`end` >= :begin) + OR (`calendar_dates`.`repetition_type` != 'SINGLE' + AND (`calendar_dates`.`repetition_end` >= :begin + OR `calendar_dates`.`repetition_end` = 0) + AND `calendar_dates`.`begin` < :end))", + [ + ':range_id' => $range_id, + ':begin' => $start->getTimestamp(), + ':end' => $end->getTimestamp(), + ] + ); + $ical = ''; + foreach ($dates as $date) { + $ical .= $this->writeICalEvent($this->prepareCalendarDate($date)); + } + return $ical; + } + + public function exportCourseDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end) + { + if ($this->time === 0) { + $this->time = time(); + } + $dates = CalendarCourseDate::getEvents($start, $end, $user_id); + $ical = ''; + foreach ($dates as $date) { + $ical .= $this->writeICalEvent($this->prepareCourseDate($date)); + } + return $ical; + } + + public function exportCourseExDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end) + { + if ($this->time === 0) { + $this->time = time(); + } + $dates = CalendarCourseExDate::getEvents($start, $end, $user_id); + $ical = ''; + foreach ($dates as $date) { + $ical .= $this->writeICalEvent($this->prepareCourseDate($date)); + } + return $ical; + } + + /** + * @param CalendarDate $date The calendar date to export. + * @return array Calendar date data prepared for export. + */ + public function prepareCalendarDate(CalendarDate $date): array + { + return [ + 'SUMMARY' => $date->title, + 'DESCRIPTION' => $date->description, + 'LOCATION' => $date->location, + 'CATEGORIES' => $date->getCategoryAsString(), + 'LAST-MODIFIED' => $date->chdate, + 'CREATED' => $date->mkdate, + 'DTSTAMP' => $this->time, + 'DTSTART' => $date->begin, + 'DTEND' => $date->end, + 'EXDATE' => implode(',', $date->exceptions->pluck('date')), + 'PRIORITY' => 5, + 'RRULE' => [ + 'type' => $date->repetition_type, + 'offset' => $date->offset, + 'interval' => $date->interval, + 'days' => $date->days, + 'count' => $date->number_of_dates, + 'expire' => $date->repetition_end, + 'month' => $date->month + ], + 'UID' => $date->unique_id + ]; + } + + /** + * @param CourseDate | CourseExDate $date The course date to export. + * @return array Course date data prepared for export. + */ + public function prepareCourseDate($date): array + { + $summary = $date->course->getFullName(); + $categories = $date->getTypeName(); + if ($date instanceof CourseExDate) { + $summary .= ' ' . _('(fällt aus)'); + $categories = ''; + $description = $date->content; + } else { + $description = implode("\n", $date->topics->pluck('title')); + } + return [ + 'SUMMARY' => $summary, + 'DESCRIPTION' => $description, + 'LOCATION' => $date->getRoomName(), + 'CATEGORIES' => $categories, + 'LAST-MODIFIED' => $date->chdate, + 'CREATED' => $date->mkdate, + 'DTSTAMP' => $this->time, + 'DTSTART' => $date->date, + 'DTEND' => $date->end_time, + 'PRIORITY' => '', + 'UID' => 'Stud.IP-SEM-' . $date->id . '@' . ($_SERVER['SERVER_NAME'] ?? '') + ]; + } + + /** + * Returns an iCalendar header with a rudimentary time zone definition. + * + * @return string The iCalendar header. + */ + public function writeHeader() + { + // Default values + $header = "BEGIN:VCALENDAR" . self::NEWLINE; + $header .= "VERSION:2.0" . self::NEWLINE; + if (isset($this->client_identifier)) { + $header .= "PRODID:" . $this->client_identifier . self::NEWLINE; + } else { + $server_name = $_SERVER['SERVER_NAME'] ?? 'unknown'; + + $header .= "PRODID:-//Stud.IP@{$server_name}//Stud.IP_iCalendar Library"; + $header .= " //EN" . self::NEWLINE; + } + $header .= "METHOD:PUBLISH" . self::NEWLINE; + + // time zone definition CET/CEST + $header .= 'CALSCALE:GREGORIAN' . self::NEWLINE + . 'BEGIN:VTIMEZONE' . self::NEWLINE + . 'TZID:Europe/Berlin' . self::NEWLINE + . 'BEGIN:DAYLIGHT' . self::NEWLINE + . 'TZOFFSETFROM:+0100' . self::NEWLINE + . 'TZOFFSETTO:+0200' . self::NEWLINE + . 'TZNAME:CEST' . self::NEWLINE + . 'DTSTART:19700329T020000' . self::NEWLINE + . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3' . self::NEWLINE + . 'END:DAYLIGHT' . self::NEWLINE + . 'BEGIN:STANDARD' . self::NEWLINE + . 'TZOFFSETFROM:+0200' . self::NEWLINE + . 'TZOFFSETTO:+0100' . self::NEWLINE + . 'TZNAME:CET' . self::NEWLINE + . 'DTSTART:19701025T030000' . self::NEWLINE + . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10' . self::NEWLINE + . 'END:STANDARD' . self::NEWLINE + . 'END:VTIMEZONE' .self::NEWLINE; + + return $header; + } + + /** + * Returns the footer. + * + * @return string + */ + public function writeFooter() + { + return "END:VCALENDAR" . self::NEWLINE; + } + + /** + * Export prepared calendar data as iCalendar. + * + * @param array $properties The event to export. + * @return string iCalendar formatted data + */ + public function writeICalEvent(array $properties): string + { + $result = "BEGIN:VEVENT" . self::NEWLINE; + + foreach ($properties as $name => $value) { + $params = []; + $params_str = ''; + if ($value === '' || is_null($value)) { + continue; + } + switch ($name) { + // not supported event properties + case 'SEMNAME': + continue 2; + + // Text fields + case 'SUMMARY': + $value = $this->quoteText($value); + break; + case 'DESCRIPTION': + $value = $this->quoteText($value); + break; + case 'LOCATION': + $value = $this->quoteText($value); + break; + case 'CATEGORIES': + $value = $this->quoteText($value); + break; + + // Date fields + case 'LAST-MODIFIED': + case 'CREATED': + case 'COMPLETED': + $value = $this->_exportDateTime($value, true); + break; + + case 'DTSTAMP': + $value = $this->_exportDateTime(time(), true); + break; + + case 'DTSTART': + $exdate_time = $value; + case 'DTEND': + case 'DUE': + case 'RECURRENCE-ID': + if (array_key_exists('VALUE', $params)) { + if ($params['VALUE'] === 'DATE') { + $value = $this->_exportDate($value); + } else { + $value = $this->_exportDateTime($value); + $params_str = ';TZID=Europe/Berlin'; + } + } else { + $value = $this->_exportDateTime($value); + $params_str = ';TZID=Europe/Berlin'; + } + break; + + case 'EXDATE': + if (array_key_exists('VALUE', $params) && $params['VALUE'] === 'DATE') { + $value = $this->exportExDate($value); + } else { + $value = $this->exportExDateTime($value, $exdate_time); + $params_str = ';TZID=Europe/Berlin'; + } + break; + + // Integer fields + case 'PERCENT-COMPLETE': + case 'REPEAT': + case 'SEQUENCE': + $value = "$value"; + break; + + case 'PRIORITY': + switch ($value) { + case 1: + $value = '1'; + break; + case 2: + $value = '5'; + break; + case 3: + $value = '9'; + break; + default: + $value = '0'; + } + break; + + // Geo fields + case 'GEO': + $value = $value['latitude'] . ',' . $value['longitude']; + break; + + // Recursion fields + case 'EXRULE': + case 'RRULE': + if ($value['type'] !== 'SINGLE' && $value['type'] !== '') { + $value = $this->_exportRecurrence($value); + } + break; + + case "UID": + $value = "$value"; + } + if ($name && !is_array($value)) { + $attr_string = $name . $params_str . ':' . $value; + $result .= $this->foldLine($attr_string) . self::NEWLINE; + } + } + if (isset($properties['GROUP_EVENT'])) { + $result .= $this->exportGroupEventProperties($properties['GROUP_EVENT']); + } + $result .= "END:VEVENT" . self::NEWLINE; + + return $result; + } + + /** + * Quotes some characters accordingly to iCalendar format. + * + * @param string $text The text to quote. + * @return string The quoted text. + */ + public function quoteText(string $text): string + { + $match = ['\\', '\n', ';', ',']; + $replace = ['\\\\', '\\n', '\;', '\,']; + return str_replace($match, $replace, $text); + } + + /** + * Export a DateTime field + * + * @param int $value Unix timestamp + * @return String Date and time (UTC) iCalendar formatted + */ + public function _exportDateTime($value, $utc = false) + { + $date_time = new DateTime(); + $date_time->setTimestamp(intval($value)); + //transform local time to UTC + if ($utc) { + $tz_utc = new DateTimeZone('UTC'); + $date_time->setTimezone($tz_utc); + return $date_time->format('Ymd\THis\Z'); + } + return $date_time->format('Ymd\THis'); + } + + /** + * Export a Time field + * + * @param int $value Unix timestamp + * @return String Time (UTC) iCalendar formatted + */ + public function _exportTime($value, $utc = false) + { + $time = date("His", $value); + if ($utc) { + $time .= 'Z'; + } + + return $time; + } + + /** + * Export a Date field + */ + public function _exportDate($value) + { + return date("Ymd", $value); + } + + /** + * Export a recurrence rule + */ + public function _exportRecurrence($value) + { + $rrule = []; + // the last day of week in a MONTHLY or YEARLY recurrence in the + // Stud.IP calendar is 5, in iCalendar it is -1 + if ($value['offset'] == '5') { + $value['offset'] = '-1'; + } + + if ($value['count'] > 1) { + unset($value['expire']); + } + + foreach ($value as $r_param => $r_value) { + if ($r_value) { + switch ($r_param) { + case 'type': + $rrule[] = 'FREQ=' . $r_value; + break; + case 'expire': + if ($r_value) + $rrule[] = 'UNTIL=' . $this->_exportDateTime($r_value, true); + break; + case 'interval': + $rrule[] = 'INTERVAL=' . $r_value; + break; + case 'days': + switch ($value['type']) { + case 'WEEKLY': + $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value); + break; + // Some CUAs (e.g. Outlook) don't understand the nWDAY syntax + // (where n is the nth ocurrence of the day in a given period of + // time and WDAY is the day of week) the RRULE uses the BYSETPOS + // rule. + case 'MONTHLY': + case 'YEARLY': + $rrule[] = 'BYDAY=' . $value['offset'] . $this->_exportWdays($r_value); + $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value); + if ($value['offset']) { + $rrule[] = 'BYSETPOS=' . $value['offset']; + } + break; + } + break; + case 'day': + $rrule[] = 'BYMONTHDAY=' . $r_value; + break; + case 'month': + $rrule[] = 'BYMONTH=' . $r_value; + break; + case 'count': + if ($r_value > 1) { + $rrule[] = 'COUNT=' . $r_value; + } + break; + } + } + } + + if ($value['type'] === 'WEEKLY' && self::WEEKSTART != 'MO') { + $rrule[] = 'WKST=' . self::WEEKSTART; + } + + return implode(';', $rrule); + } + + /** + * Return the days from CalendarDate::days as attribute of a event recurrence. + * + * @param string $value + * @return string + */ + public function _exportWdays(string $value): string + { + $wdays_map = ['1' => 'MO', '2' => 'TU', '3' => 'WE', '4' => 'TH', '5' => 'FR', + '6' => 'SA', '7' => 'SU']; + $wdays = []; + preg_match_all('/(\d)/', $value, $matches); + foreach ($matches[1] as $match) { + $wdays[] = $wdays_map[$match]; + } + return implode(',', $wdays); + } + + + /** + * Formats dates of exception. + * + * @param string $value Date values (Y-m-d) as csv list. + * @return string The formatted Exceptions. + */ + public function exportExDate(string $value): string + { + $ex_dates = []; + $dates = explode(',', $value); + foreach ($dates as $date) { + $ex_datetime = $date . ' 12:00:00'; + $ex_date = DateTime::createFromFormat('Y-m-d H:i:s', $ex_datetime); + $ex_dates[] = $this->_exportDate($ex_date->getTimestamp()); + } + + return implode(',', $ex_dates); + } + + /** + * Formats date times of exception. + * + * @param string $value Date values (Y-m-d) as csv list. + * @param int $begin Start date of event as unix timestamp. + * @return string The formatted Exceptions. + */ + public function exportExDateTime(string $value, int $begin): string + { + $ex_dates = []; + $dates = explode(',', $value); + foreach ($dates as $date) { + $ex_datetime = $date . date(' H:i:s', $begin); + $date_time = DateTime::createFromFormat('Y-m-d H:i:s', $ex_datetime); + $ex_dates[] = $date_time->format('Ymd\THis'); + } + return implode(',', $ex_dates); + } + + /** + * Returns iCalendar group event properties if the date has mor than one attendee. + * + * @param CalendarDate $date The date object to extract the group data from. + * @return string The formatted group event properties. + */ + private function exportGroupEventProperties(CalendarDate $date): string + { + if (!count($date->calendars)) { + return ''; + } + $organizer = $date->author; + if ($organizer) { + $properties = $this->foldLine('ORGANIZER;CN="' + . $organizer->getFullName() + . '":mailto:' . $organizer->Email) + . self::NEWLINE; + } else { + $properties = $this->foldLine('ORGANIZER;CN="' + . _('unbekannt') + . '":mailto:' . $GLOBALS['user']->email) + . self::NEWLINE; + } + foreach ($date->calendars as $calendar) { + if ($date->author_id === $calendar->range_id) { + if ($calendar->user) { + $properties .= $this->foldLine('ATTENDEE;' + . 'ROLE=REQ-PARTICIPANT;' + . 'CN="' . $calendar->user->getFullName() + . '":mailto:' . $calendar->user->Email) + . self::NEWLINE; + } else { + $properties = ''; + } + } else { + if ($calendar->user) { + switch ($calendar->participation) { + case 'ACCEPTED' : + $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' + . ';PARTSTAT=ACCEPTED'; + break; + case 'ACKNOWLEDGED' : + $attendee = 'ATTENDEE;ROLE=NON-PARTICIPANT' + . ';PARTSTAT=ACCEPTED' + . ';DELEGATED-TO="mailto:' + . $this->getFacultyEmail($organizer->id) + . '"'; + break; + case 'DECLINED' : + $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' + . ';PARTSTAT=DECLINED'; + break; + default : + $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'; + $attendee .= ';PARTSTAT=TENTATIVE'; + $attendee .= ';RSVP=TRUE'; + + } + $attendee .= ';CN="' . $calendar->user->getFullName() + . '":mailto:' . $calendar->user->Email; + $properties .= $this->foldLine($attendee) . self::NEWLINE; + } + } + } + return $properties; + } + + /** + * @param string $user_id + * @return string + */ + private function getFacultyEmail(string $user_id): string + { + return DBManager::get()->fetchColumn(' + SELECT `email` + FROM `Institute` + LEFT JOIN `user_inst` USING(`institut_id`) + WHERE `Institute`.`Institut_id` = `fakultaets_id` + AND `user_id` = ?', [$user_id]); + } + + /** + * Returns the folded version of a text line. + * + * @param string $line + * @return string + */ + private function foldLine(string $line): string + { + $line = preg_replace('/(\r\n|\n|\r)/', '\n', $line); + if (mb_strlen($line) > 75) { + $foldedline = ''; + while ($line !== '') { + $maxLine = mb_substr($line, 0, 75); + $cutPoint = max(60, max(mb_strrpos($maxLine, ';'), mb_strrpos($maxLine, ':')) + 1); + + $foldedline .= ( empty($foldedline)) ? + mb_substr($line, 0, $cutPoint) : + self::NEWLINE . ' ' . mb_substr($line, 0, $cutPoint); + + $line = (mb_strlen($line) <= $cutPoint) ? '' : mb_substr($line, $cutPoint); + } + return $foldedline; + } + return $line; + } +} diff --git a/lib/classes/calendar/ICalendarImport.class.php b/lib/classes/calendar/ICalendarImport.class.php deleted file mode 100644 index e78696d..0000000 --- a/lib/classes/calendar/ICalendarImport.class.php +++ /dev/null @@ -1,678 +0,0 @@ -range_id = $range_id; - $this->import_time = time(); - } - - public function import($ical_data) - { - $this->parse($ical_data); - } - - public function countEvents($ical_data) - { - $matches = []; - if (is_null($this->count)) { - // Unfold any folded lines - $data = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $ical_data); - preg_match_all('/(BEGIN:VEVENT(\r\n|\r|\n)[\W\w]*?END:VEVENT\r?\n?)/', $ical_data, $matches); - $this->count = sizeof($matches[1]); - } - - return $this->count; - } - - public function getCountEvents() : int - { - return (int) $this->count; - } - - public function convertPublicToPrivate(bool $to_private = true) : void - { - $this->convert_to_private = $to_private; - } - - /** - * Parse a string containing vCalendar data. - * - * @access private - * @param string $data The data to parse - */ - public function parse(string $data) - { - // match categories - $studip_categories = []; - $i = 1; - foreach ($GLOBALS['PERS_TERMIN_KAT'] as $cat) { - $studip_categories[mb_strtolower($cat['name'])] = $i++; - } - - // Unfold any folded lines - // the CR is optional for files imported from Korganizer (non-standard) - $data = $this->unfoldLine($data); - - if (!preg_match('/BEGIN:VCALENDAR(\r\n|\r|\n)([\W\w]*)END:VCALENDAR\r?\n?/', $data, $matches)) { - throw new UnexpectedValueException(); - } - - // client identifier - if (!$this->parseClientIdentifier($matches[2])) { - throw new UnexpectedValueException(); - } - - // All sub components - if (!preg_match_all('/BEGIN:VEVENT(\r\n|\r|\n)([\w\W]*?)END:VEVENT(\r\n|\r|\n)/', $matches[2], $v_events)) { - // _("Die importierte Datei enthält keine Termine.") - throw new UnexpectedValueException(); - } - - if ($this->count) { - $this->count = 0; - } - foreach ($v_events[2] as $v_event) { - - if (preg_match_all('/(.*):(.*)(\r|\n)+/', $v_event, $matches)) { - $properties = []; - $check = []; - foreach ($matches[0] as $property) { - preg_match('/([^;^:]*)((;[^:]*)?):(.*)/', $property, $parts); - $tag = $parts[1]; - $value = $parts[4]; - $params = []; - - // skip seminar events - if ((!$this->import_sem) && $tag == 'UID') { - if (mb_strpos($value, 'Stud.IP-SEM') === 0) { - continue 2; - } - } - - if (!empty($parts[2])) { - preg_match_all('/;(([^;=]*)(=([^;]*))?)/', $parts[2], $param_parts); - foreach ($param_parts[2] as $key => $param_name) - $params[mb_strtoupper($param_name)] = mb_strtoupper($param_parts[4][$key]); - - if ($params['ENCODING']) { - switch ($params['ENCODING']) { - case 'QUOTED-PRINTABLE': - $value = $this->qp_decode($value); - break; - - case 'BASE64': - $value = base64_decode($value); - break; - } - } - } - - switch ($tag) { - // text fields - case 'DESCRIPTION': - case 'SUMMARY': - case 'LOCATION': - $value = preg_replace('/\\\\,/', ',', $value); - $value = preg_replace('/\\\\n/', "\n", $value); - $properties[$tag] = trim($value); - break; - - case 'CATEGORIES': - $categories = []; - $properties['STUDIP_CATEGORY'] = null; - foreach (explode(',', $value) as $category) { - if (!$studip_categories[mb_strtolower($category)]) { - $categories[] = $category; - } else if (!$properties['STUDIP_CATEGORY']) { - $properties['STUDIP_CATEGORY'] - = $studip_categories[mb_strtolower($category)]; - } - } - $properties[$tag] = implode(',', $categories); - break; - - // Date fields - case 'DCREATED': // vCalendar property name for "CREATED" - case 'DTSTAMP': - case 'COMPLETED': - case 'CREATED': - case 'LAST-MODIFIED': - $properties[$tag] = $this->parseDateTime($value); - break; - - case 'DTSTART': - case 'DTEND': - // checking for day events - if ($params['VALUE'] == 'DATE') - $check['DAY_EVENT'] = true; - case 'DUE': - case 'RECURRENCE-ID': - $properties[$tag] = $this->parseDateTime($value); - break; - - case 'RDATE': - if (array_key_exists('VALUE', $params)) { - if ($params['VALUE'] == 'PERIOD') { - $properties[$tag] = $this->parsePeriod($value); - } else { - $properties[$tag] = $this->parseDateTime($value); - } - } else { - $properties[$tag] = $this->parseDateTime($value); - } - break; - - case 'TRIGGER': - if (array_key_exists('VALUE', $params)) { - if ($params['VALUE'] == 'DATE-TIME') { - $properties[$tag] = $this->parseDateTime($value); - } else { - $properties[$tag] = $this->parseDuration($value); - } - } else { - $properties[$tag] = $this->parseDuration($value); - } - break; - - case 'EXDATE': - $properties[$tag] = []; - // comma seperated dates - $values = []; - $dates = []; - preg_match_all('/,([^,]*)/', ',' . $value, $values); - foreach ($values[1] as $value) { - if (array_key_exists('VALUE', $params)) { - if ($params['VALUE'] == 'DATE-TIME') { - $dates[] = $this->parseDateTime($value); - } else if ($params['VALUE'] == 'DATE') { - $dates[] = $this->parseDate($value); - } - } else { - $dates[] = $this->parseDateTime($value); - } - } - // some iCalendar exports (e.g. KOrganizer) use an EXDATE-entry for every - // exception, so we have to merge them - array_merge($properties[$tag], $dates); - break; - - // Duration fields - case 'DURATION': - $attibutes[$tag] = $this->parseDuration($value); - break; - - // Period of time fields - case 'FREEBUSY': - $values = []; - $periods = []; - preg_match_all('/,([^,]*)/', ',' . $value, $values); - foreach ($values[1] as $value) { - $periods[] = $this->parsePeriod($value); - } - - $properties[$tag] = $periods; - break; - - // UTC offset fields - case 'TZOFFSETFROM': - case 'TZOFFSETTO': - $properties[$tag] = $this->parseUtcOffset($value); - break; - - case 'PRIORITY': - $properties[$tag] = $this->parsePriority($value); - break; - - case 'CLASS': - switch (trim($value)) { - case 'PUBLIC': - $properties[$tag] = 'PUBLIC'; - break; - case 'CONFIDENTIAL': - $properties[$tag] = 'CONFIDENTIAL'; - break; - default: - $properties[$tag] = 'PRIVATE'; - } - break; - - // Integer fields - case 'PERCENT-COMPLETE': - case 'REPEAT': - case 'SEQUENCE': - $properties[$tag] = intval($value); - break; - - // Geo fields - case 'GEO': - $floats = explode(';', $value); - $value['latitude'] = floatval($floats[0]); - $value['longitude'] = floatval($floats[1]); - $properties[$tag] = $value; - break; - - // Recursion fields - case 'EXRULE': - case 'RRULE': - $properties[$tag] = $this->parseRecurrence($value); - break; - - default: - // string fields - $properties[$tag] = trim($value); - break; - } - } - - if (!$properties['RRULE']['rtype']) { - $properties['RRULE'] = ['rtype' => 'SINGLE']; - } - - if (!$properties['LAST-MODIFIED']) { - $properties['LAST-MODIFIED'] = $properties['DTSTAMP'] ?: $properties['CREATED'] ?? time(); - } - - if (!$properties['DTSTART'] || ($properties['EXDATE'] && !$properties['RRULE'])) { - // _("Die Datei ist keine gültige iCalendar-Datei!") - throw new UnexpectedValueException(); - } - - if (!$properties['DTEND']) { - $properties['DTEND'] = $properties['DTSTART']; - } - - // day events starts at 00:00:00 and ends at 23:59:59 - if ($check['DAY_EVENT']) - $properties['DTEND']--; - - // default: all imported events are set to private - if (!$properties['CLASS'] - || ($this->convert_to_private && $properties['CLASS'] == 'PUBLIC')) { - $properties['CLASS'] = 'PRIVATE'; - } - - /* - if (isset($studip_categories[$properties['CATEGORIES']])) { - $properties['STUDIP_CATEGORY'] = $studip_categories[$properties['CATEGORIES']]; - $properties['CATEGORIES'] = ''; - } - * - */ - - $this->createDateFromProperties($properties); - } else { - // _("Die Datei ist keine gültige iCalendar-Datei!") - throw new InvalidValuesException(); - } - $this->count++; - } - - return true; - } - - private function createDateFromProperties($properties) - { - $date = CalendarDate::findOneBySQL( - 'LEFT JOIN `calendar_date_assignments` - ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` - WHERE `calendar_dates`.`unique_id` = :uid - AND `calendar_date_assignments`.`range_id` = :range_id', - [ - ':uid' => $properties['UID'], - ':range_id' => $this->range_id - ] - ); - - if (!$date) { - $date = new CalendarDate(); - $date->id = $date->getNewId(); - $date->author_id = $this->range_id; - $date->editor_id = $this->range_id; - $range_date = new CalendarDateAssignment(); - $range_date->range_id = $this->range_id; - $range_date->participation = ''; - $date->calendars[] = $range_date; - } - - $date->begin = $properties['DTSTART']->getTimestamp(); - $date->end = $properties['DTEND']->getTimestamp(); - $date->title = $properties['SUMMARY']; - $date->description = $properties['DESCRIPTION']; - $date->access = $properties['CLASS'] ?? 'PRIVATE'; - $date->user_category = $properties['CATEGORIES']; - $date->category = $properties['STUDIP_CATEGORY'] ?: 1; - $date->priority = $properties['PRIORITY'] ?? ''; - $date->location = $properties['LOCATION']; - if (is_array($properties['EXDATE'])) { - foreach ($properties['EXDATE'] as $exdate) { - $exception = new CalendarDateException(); - $exception->date = $exdate->format('Y-m-d'); - $date->exceptions[] = $exception; - } - } - $date->mkdate = $properties['CREATED'] ? $properties['CREATED']->getTimestamp() : time(); - if (isset($properties['LAST-MODIFIED'])) { - $date->chdate = $properties['LAST-MODIFIED']->getTimestamp(); - } else { - $date->chdate = $date->mkdate; - } - $date->import_date = $this->import_time; - $date->unique_id = $properties['UID']; - - $this->setRecurrenceRule($date, $properties['RRULE']); - $date->store(); - } - - private function setRecurrenceRule(CalendarDate $date, $rrule) - { - $date->interval = $rrule['linterval'] ?? 1; - if (strlen($rrule['wdays'] ?? '')) { - $date->offset = $rrule['sinterval'] ?? 0; - $date->days = $rrule['wdays'] ?? null; - } else { - $date->offset = $rrule['day'] ?? 0; - $date->days = $rrule['sinterval'] ?? null; - } - $date->month = $rrule['month'] ?? null; - $date->repetition_type = $rrule['rtype'] ?? 'SINGLE'; - $date->number_of_dates = $rrule['count'] ?? 1; - $date->repetition_end = $rrule['expire'] ?? 0; - } - - private function unfoldLine($data) - { - return preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data); - } - - /** - * Parse a UTC Offset field - */ - private function parseUtcOffset($offset_text) - { - $offset = 0; - if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $offset_text, $matches)) { - $offset += 3600 * intval($matches[2]); - $offset += 60 * intval($matches[3]); - $offset *= ( $matches[1] == '+' ? 1 : -1); - if (array_key_exists(4, $matches)) { - $offset += intval($matches[4]); - } - } - return $offset; - } - - /** - * Parse a Time Period field - */ - private function parsePeriod($period_text): array - { - $matches = explode('/', $period_text); - - $start = $this->parseDateTime($matches[0]); - - if ($duration = $this->parseDuration($matches[1])) { - return ['start' => $start, 'duration' => $duration]; - } else if ($end = $this->parseDateTime($matches[1])) { - return ['start' => $start, 'end' => $end]; - } - return []; - } - - /** - * Parse a DateTime field - */ - private function parseDateTime(String $date_time) - { - $parts = explode('T', $date_time); - if (count($parts) != 2) { - // not a date time string but may be just a date string - $date = $this->parseDate($date_time); - return DateTimeImmutable::createFromFormat('YmdHis', implode('', $date) . '000000'); - } - - $date = $this->parseDate($parts[0]); - $time = $this->parseTime($parts[1]); - - if ($time['zone'] == 'UTC') { - $time_zone = new DateTimeZone('UTC'); - } else { - $time_zone = new DateTimeZone('Europe/Berlin'); - } - return DateTimeImmutable::createFromFormat( - 'YmdHis', - implode('', $date) . $time['hour'] . $time['minute'] . $time['second'], - $time_zone - ); - } - - /** - * Parse a Time field - */ - private function parseTime($time_text): array - { - $matches = []; - if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $time_text, $matches)) { - $time['hour'] = $matches[1]; - $time['minute'] = $matches[2]; - $time['second'] = $matches[3]; - if (array_key_exists(4, $matches)) { - $time['zone'] = 'UTC'; - } else { - $time['zone'] = 'LOCAL'; - } - return $time; - } - throw new InvalidValuesException(); - } - - /** - * Parse a Date field - */ - private function parseDate($date_text): array - { - $matches = []; - if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})/', $date_text, $matches)) { - $date['year'] = $matches[1]; - $date['month'] = $matches[2]; - $date['mday'] = $matches[3]; - return $date; - } - throw new InvalidValuesException(); - } - - /** - * Parse a Duration Value field - */ - private function parseDuration($interval_text): DateInterval - { - return new DateInterval($interval_text); - } - - private function parsePriority($value) - { - $value = intval($value); - if ($value > 0 && $value < 5) { - return 'HIGH'; - } - - if ($value == 5) { - return 'MEDIUM'; - } - - if ($value > 5 && $value < 10) { - return 'LOW'; - } - - return ''; - } - - /** - * Parse a recurrence rule. - * - * @param $text string The text of the recurrence rule. - * @return array The translated recurrence rule as array. - * @throws InvalidValuesException - */ - private function parseRecurrence($text): array - { - global $_calendar_error; - - if (preg_match_all('/([A-Za-z]*?)=([^;]*);?/', $text, $matches, PREG_SET_ORDER)) { - $r_rule = []; - - foreach ($matches as $match) { - switch ($match[1]) { - case 'FREQ' : - switch (trim($match[2])) { - case 'DAILY' : - case 'WEEKLY' : - case 'MONTHLY' : - case 'YEARLY' : - $r_rule['rtype'] = trim($match[2]); - break; - default: - throw new InvalidValuesException( - _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.") - ); - } - break; - - case 'UNTIL' : - $r_rule['expire'] = $this->parseDateTime($match[2]); - break; - - case 'COUNT' : - $r_rule['count'] = intval($match[2]); - break; - - case 'INTERVAL' : - $r_rule['linterval'] = intval($match[2]); - break; - - case 'BYSECOND' : - case 'BYMINUTE' : - case 'BYHOUR' : - case 'BYWEEKNO' : - case 'BYYEARDAY' : - throw new InvalidValuesException( - _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.") - ); - case 'BYDAY' : - $byday = $this->parseByDay($match[2]); - $r_rule['wdays'] = $byday['wdays']; - if ($byday['sinterval']) - $r_rule['sinterval'] = $byday['sinterval']; - break; - - case 'BYMONTH' : - $r_rule['month'] = $this->parseByMonth($match[2]); - break; - - case 'BYMONTHDAY' : - $r_rule['day'] = $this->parseByMonthDay($match[2]); - break; - - case 'BYSETPOS': - $r_rule['sinterval'] = intval($match[2]); - break; - - case 'WKST' : - break; - } - } - } - - return $r_rule; - } - - private function parseByDay($text) - { - global $_calendar_error; - - preg_match_all('/(-?\d{1,2})?(MO|TU|WE|TH|FR|SA|SU),?/', $text, $matches, PREG_SET_ORDER); - $wdays_map = ['MO' => '1', 'TU' => '2', 'WE' => '3', 'TH' => '4', 'FR' => '5', - 'SA' => '6', 'SU' => '7']; - $wdays = ""; - $sinterval = null; - foreach ($matches as $match) { - $wdays .= $wdays_map[$match[2]]; - if ($match[1]) { - if (!$sinterval && ((int) $match[1]) > 0 || $match[1] == '-1') { - if ($match[1] == '-1') { - $sinterval = '5'; - } else { - $sinterval = $match[1]; - } - } else { - throw new InvalidValuesException( - _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.") - ); - } - } - } - - return $wdays ? ['wdays' => $wdays, 'sinterval' => $sinterval] : false; - } - - private function parseByMonthDay($text) - { - $days = explode(',', $text); - if (count($days) > 1 || ((int) $days[0]) < 0) { - return false; - } - - return $days[0]; - } - - private function parseByMonth($text) - { - $months = explode(',', $text); - if (count($months) > 1) { - return false; - } - - return $months[0]; - } - - private function qp_decode($value) - { - return preg_replace_callback("/=([0-9A-F]{2})/", function ($m) {return chr(hexdec($m[1]));}, $value); - } - - private function parseClientIdentifier(&$data) - { - global $_calendar_error; - - if ($this->client_identifier == '') { - if (!preg_match('/PRODID((;[\W\w]*)*):([\W\w]+?)(\r\n|\r|\n)/', $data, $matches) - || !trim($matches[3])) { - // _("Die Datei ist keine gültige iCalendar-Datei!") - throw new InvalidValuesException(); - } else { - $this->client_identifier = trim($matches[3]); - } - } - return true; - } - - public function getClientIdentifier($data = null) - { - if (!is_null($data)) { - $this->parseClientIdentifier($data); - } - - return $this->client_identifier; - } - -} diff --git a/lib/classes/calendar/ICalendarImport.php b/lib/classes/calendar/ICalendarImport.php new file mode 100644 index 0000000..e78696d --- /dev/null +++ b/lib/classes/calendar/ICalendarImport.php @@ -0,0 +1,678 @@ +range_id = $range_id; + $this->import_time = time(); + } + + public function import($ical_data) + { + $this->parse($ical_data); + } + + public function countEvents($ical_data) + { + $matches = []; + if (is_null($this->count)) { + // Unfold any folded lines + $data = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $ical_data); + preg_match_all('/(BEGIN:VEVENT(\r\n|\r|\n)[\W\w]*?END:VEVENT\r?\n?)/', $ical_data, $matches); + $this->count = sizeof($matches[1]); + } + + return $this->count; + } + + public function getCountEvents() : int + { + return (int) $this->count; + } + + public function convertPublicToPrivate(bool $to_private = true) : void + { + $this->convert_to_private = $to_private; + } + + /** + * Parse a string containing vCalendar data. + * + * @access private + * @param string $data The data to parse + */ + public function parse(string $data) + { + // match categories + $studip_categories = []; + $i = 1; + foreach ($GLOBALS['PERS_TERMIN_KAT'] as $cat) { + $studip_categories[mb_strtolower($cat['name'])] = $i++; + } + + // Unfold any folded lines + // the CR is optional for files imported from Korganizer (non-standard) + $data = $this->unfoldLine($data); + + if (!preg_match('/BEGIN:VCALENDAR(\r\n|\r|\n)([\W\w]*)END:VCALENDAR\r?\n?/', $data, $matches)) { + throw new UnexpectedValueException(); + } + + // client identifier + if (!$this->parseClientIdentifier($matches[2])) { + throw new UnexpectedValueException(); + } + + // All sub components + if (!preg_match_all('/BEGIN:VEVENT(\r\n|\r|\n)([\w\W]*?)END:VEVENT(\r\n|\r|\n)/', $matches[2], $v_events)) { + // _("Die importierte Datei enthält keine Termine.") + throw new UnexpectedValueException(); + } + + if ($this->count) { + $this->count = 0; + } + foreach ($v_events[2] as $v_event) { + + if (preg_match_all('/(.*):(.*)(\r|\n)+/', $v_event, $matches)) { + $properties = []; + $check = []; + foreach ($matches[0] as $property) { + preg_match('/([^;^:]*)((;[^:]*)?):(.*)/', $property, $parts); + $tag = $parts[1]; + $value = $parts[4]; + $params = []; + + // skip seminar events + if ((!$this->import_sem) && $tag == 'UID') { + if (mb_strpos($value, 'Stud.IP-SEM') === 0) { + continue 2; + } + } + + if (!empty($parts[2])) { + preg_match_all('/;(([^;=]*)(=([^;]*))?)/', $parts[2], $param_parts); + foreach ($param_parts[2] as $key => $param_name) + $params[mb_strtoupper($param_name)] = mb_strtoupper($param_parts[4][$key]); + + if ($params['ENCODING']) { + switch ($params['ENCODING']) { + case 'QUOTED-PRINTABLE': + $value = $this->qp_decode($value); + break; + + case 'BASE64': + $value = base64_decode($value); + break; + } + } + } + + switch ($tag) { + // text fields + case 'DESCRIPTION': + case 'SUMMARY': + case 'LOCATION': + $value = preg_replace('/\\\\,/', ',', $value); + $value = preg_replace('/\\\\n/', "\n", $value); + $properties[$tag] = trim($value); + break; + + case 'CATEGORIES': + $categories = []; + $properties['STUDIP_CATEGORY'] = null; + foreach (explode(',', $value) as $category) { + if (!$studip_categories[mb_strtolower($category)]) { + $categories[] = $category; + } else if (!$properties['STUDIP_CATEGORY']) { + $properties['STUDIP_CATEGORY'] + = $studip_categories[mb_strtolower($category)]; + } + } + $properties[$tag] = implode(',', $categories); + break; + + // Date fields + case 'DCREATED': // vCalendar property name for "CREATED" + case 'DTSTAMP': + case 'COMPLETED': + case 'CREATED': + case 'LAST-MODIFIED': + $properties[$tag] = $this->parseDateTime($value); + break; + + case 'DTSTART': + case 'DTEND': + // checking for day events + if ($params['VALUE'] == 'DATE') + $check['DAY_EVENT'] = true; + case 'DUE': + case 'RECURRENCE-ID': + $properties[$tag] = $this->parseDateTime($value); + break; + + case 'RDATE': + if (array_key_exists('VALUE', $params)) { + if ($params['VALUE'] == 'PERIOD') { + $properties[$tag] = $this->parsePeriod($value); + } else { + $properties[$tag] = $this->parseDateTime($value); + } + } else { + $properties[$tag] = $this->parseDateTime($value); + } + break; + + case 'TRIGGER': + if (array_key_exists('VALUE', $params)) { + if ($params['VALUE'] == 'DATE-TIME') { + $properties[$tag] = $this->parseDateTime($value); + } else { + $properties[$tag] = $this->parseDuration($value); + } + } else { + $properties[$tag] = $this->parseDuration($value); + } + break; + + case 'EXDATE': + $properties[$tag] = []; + // comma seperated dates + $values = []; + $dates = []; + preg_match_all('/,([^,]*)/', ',' . $value, $values); + foreach ($values[1] as $value) { + if (array_key_exists('VALUE', $params)) { + if ($params['VALUE'] == 'DATE-TIME') { + $dates[] = $this->parseDateTime($value); + } else if ($params['VALUE'] == 'DATE') { + $dates[] = $this->parseDate($value); + } + } else { + $dates[] = $this->parseDateTime($value); + } + } + // some iCalendar exports (e.g. KOrganizer) use an EXDATE-entry for every + // exception, so we have to merge them + array_merge($properties[$tag], $dates); + break; + + // Duration fields + case 'DURATION': + $attibutes[$tag] = $this->parseDuration($value); + break; + + // Period of time fields + case 'FREEBUSY': + $values = []; + $periods = []; + preg_match_all('/,([^,]*)/', ',' . $value, $values); + foreach ($values[1] as $value) { + $periods[] = $this->parsePeriod($value); + } + + $properties[$tag] = $periods; + break; + + // UTC offset fields + case 'TZOFFSETFROM': + case 'TZOFFSETTO': + $properties[$tag] = $this->parseUtcOffset($value); + break; + + case 'PRIORITY': + $properties[$tag] = $this->parsePriority($value); + break; + + case 'CLASS': + switch (trim($value)) { + case 'PUBLIC': + $properties[$tag] = 'PUBLIC'; + break; + case 'CONFIDENTIAL': + $properties[$tag] = 'CONFIDENTIAL'; + break; + default: + $properties[$tag] = 'PRIVATE'; + } + break; + + // Integer fields + case 'PERCENT-COMPLETE': + case 'REPEAT': + case 'SEQUENCE': + $properties[$tag] = intval($value); + break; + + // Geo fields + case 'GEO': + $floats = explode(';', $value); + $value['latitude'] = floatval($floats[0]); + $value['longitude'] = floatval($floats[1]); + $properties[$tag] = $value; + break; + + // Recursion fields + case 'EXRULE': + case 'RRULE': + $properties[$tag] = $this->parseRecurrence($value); + break; + + default: + // string fields + $properties[$tag] = trim($value); + break; + } + } + + if (!$properties['RRULE']['rtype']) { + $properties['RRULE'] = ['rtype' => 'SINGLE']; + } + + if (!$properties['LAST-MODIFIED']) { + $properties['LAST-MODIFIED'] = $properties['DTSTAMP'] ?: $properties['CREATED'] ?? time(); + } + + if (!$properties['DTSTART'] || ($properties['EXDATE'] && !$properties['RRULE'])) { + // _("Die Datei ist keine gültige iCalendar-Datei!") + throw new UnexpectedValueException(); + } + + if (!$properties['DTEND']) { + $properties['DTEND'] = $properties['DTSTART']; + } + + // day events starts at 00:00:00 and ends at 23:59:59 + if ($check['DAY_EVENT']) + $properties['DTEND']--; + + // default: all imported events are set to private + if (!$properties['CLASS'] + || ($this->convert_to_private && $properties['CLASS'] == 'PUBLIC')) { + $properties['CLASS'] = 'PRIVATE'; + } + + /* + if (isset($studip_categories[$properties['CATEGORIES']])) { + $properties['STUDIP_CATEGORY'] = $studip_categories[$properties['CATEGORIES']]; + $properties['CATEGORIES'] = ''; + } + * + */ + + $this->createDateFromProperties($properties); + } else { + // _("Die Datei ist keine gültige iCalendar-Datei!") + throw new InvalidValuesException(); + } + $this->count++; + } + + return true; + } + + private function createDateFromProperties($properties) + { + $date = CalendarDate::findOneBySQL( + 'LEFT JOIN `calendar_date_assignments` + ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` + WHERE `calendar_dates`.`unique_id` = :uid + AND `calendar_date_assignments`.`range_id` = :range_id', + [ + ':uid' => $properties['UID'], + ':range_id' => $this->range_id + ] + ); + + if (!$date) { + $date = new CalendarDate(); + $date->id = $date->getNewId(); + $date->author_id = $this->range_id; + $date->editor_id = $this->range_id; + $range_date = new CalendarDateAssignment(); + $range_date->range_id = $this->range_id; + $range_date->participation = ''; + $date->calendars[] = $range_date; + } + + $date->begin = $properties['DTSTART']->getTimestamp(); + $date->end = $properties['DTEND']->getTimestamp(); + $date->title = $properties['SUMMARY']; + $date->description = $properties['DESCRIPTION']; + $date->access = $properties['CLASS'] ?? 'PRIVATE'; + $date->user_category = $properties['CATEGORIES']; + $date->category = $properties['STUDIP_CATEGORY'] ?: 1; + $date->priority = $properties['PRIORITY'] ?? ''; + $date->location = $properties['LOCATION']; + if (is_array($properties['EXDATE'])) { + foreach ($properties['EXDATE'] as $exdate) { + $exception = new CalendarDateException(); + $exception->date = $exdate->format('Y-m-d'); + $date->exceptions[] = $exception; + } + } + $date->mkdate = $properties['CREATED'] ? $properties['CREATED']->getTimestamp() : time(); + if (isset($properties['LAST-MODIFIED'])) { + $date->chdate = $properties['LAST-MODIFIED']->getTimestamp(); + } else { + $date->chdate = $date->mkdate; + } + $date->import_date = $this->import_time; + $date->unique_id = $properties['UID']; + + $this->setRecurrenceRule($date, $properties['RRULE']); + $date->store(); + } + + private function setRecurrenceRule(CalendarDate $date, $rrule) + { + $date->interval = $rrule['linterval'] ?? 1; + if (strlen($rrule['wdays'] ?? '')) { + $date->offset = $rrule['sinterval'] ?? 0; + $date->days = $rrule['wdays'] ?? null; + } else { + $date->offset = $rrule['day'] ?? 0; + $date->days = $rrule['sinterval'] ?? null; + } + $date->month = $rrule['month'] ?? null; + $date->repetition_type = $rrule['rtype'] ?? 'SINGLE'; + $date->number_of_dates = $rrule['count'] ?? 1; + $date->repetition_end = $rrule['expire'] ?? 0; + } + + private function unfoldLine($data) + { + return preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data); + } + + /** + * Parse a UTC Offset field + */ + private function parseUtcOffset($offset_text) + { + $offset = 0; + if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $offset_text, $matches)) { + $offset += 3600 * intval($matches[2]); + $offset += 60 * intval($matches[3]); + $offset *= ( $matches[1] == '+' ? 1 : -1); + if (array_key_exists(4, $matches)) { + $offset += intval($matches[4]); + } + } + return $offset; + } + + /** + * Parse a Time Period field + */ + private function parsePeriod($period_text): array + { + $matches = explode('/', $period_text); + + $start = $this->parseDateTime($matches[0]); + + if ($duration = $this->parseDuration($matches[1])) { + return ['start' => $start, 'duration' => $duration]; + } else if ($end = $this->parseDateTime($matches[1])) { + return ['start' => $start, 'end' => $end]; + } + return []; + } + + /** + * Parse a DateTime field + */ + private function parseDateTime(String $date_time) + { + $parts = explode('T', $date_time); + if (count($parts) != 2) { + // not a date time string but may be just a date string + $date = $this->parseDate($date_time); + return DateTimeImmutable::createFromFormat('YmdHis', implode('', $date) . '000000'); + } + + $date = $this->parseDate($parts[0]); + $time = $this->parseTime($parts[1]); + + if ($time['zone'] == 'UTC') { + $time_zone = new DateTimeZone('UTC'); + } else { + $time_zone = new DateTimeZone('Europe/Berlin'); + } + return DateTimeImmutable::createFromFormat( + 'YmdHis', + implode('', $date) . $time['hour'] . $time['minute'] . $time['second'], + $time_zone + ); + } + + /** + * Parse a Time field + */ + private function parseTime($time_text): array + { + $matches = []; + if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $time_text, $matches)) { + $time['hour'] = $matches[1]; + $time['minute'] = $matches[2]; + $time['second'] = $matches[3]; + if (array_key_exists(4, $matches)) { + $time['zone'] = 'UTC'; + } else { + $time['zone'] = 'LOCAL'; + } + return $time; + } + throw new InvalidValuesException(); + } + + /** + * Parse a Date field + */ + private function parseDate($date_text): array + { + $matches = []; + if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})/', $date_text, $matches)) { + $date['year'] = $matches[1]; + $date['month'] = $matches[2]; + $date['mday'] = $matches[3]; + return $date; + } + throw new InvalidValuesException(); + } + + /** + * Parse a Duration Value field + */ + private function parseDuration($interval_text): DateInterval + { + return new DateInterval($interval_text); + } + + private function parsePriority($value) + { + $value = intval($value); + if ($value > 0 && $value < 5) { + return 'HIGH'; + } + + if ($value == 5) { + return 'MEDIUM'; + } + + if ($value > 5 && $value < 10) { + return 'LOW'; + } + + return ''; + } + + /** + * Parse a recurrence rule. + * + * @param $text string The text of the recurrence rule. + * @return array The translated recurrence rule as array. + * @throws InvalidValuesException + */ + private function parseRecurrence($text): array + { + global $_calendar_error; + + if (preg_match_all('/([A-Za-z]*?)=([^;]*);?/', $text, $matches, PREG_SET_ORDER)) { + $r_rule = []; + + foreach ($matches as $match) { + switch ($match[1]) { + case 'FREQ' : + switch (trim($match[2])) { + case 'DAILY' : + case 'WEEKLY' : + case 'MONTHLY' : + case 'YEARLY' : + $r_rule['rtype'] = trim($match[2]); + break; + default: + throw new InvalidValuesException( + _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.") + ); + } + break; + + case 'UNTIL' : + $r_rule['expire'] = $this->parseDateTime($match[2]); + break; + + case 'COUNT' : + $r_rule['count'] = intval($match[2]); + break; + + case 'INTERVAL' : + $r_rule['linterval'] = intval($match[2]); + break; + + case 'BYSECOND' : + case 'BYMINUTE' : + case 'BYHOUR' : + case 'BYWEEKNO' : + case 'BYYEARDAY' : + throw new InvalidValuesException( + _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.") + ); + case 'BYDAY' : + $byday = $this->parseByDay($match[2]); + $r_rule['wdays'] = $byday['wdays']; + if ($byday['sinterval']) + $r_rule['sinterval'] = $byday['sinterval']; + break; + + case 'BYMONTH' : + $r_rule['month'] = $this->parseByMonth($match[2]); + break; + + case 'BYMONTHDAY' : + $r_rule['day'] = $this->parseByMonthDay($match[2]); + break; + + case 'BYSETPOS': + $r_rule['sinterval'] = intval($match[2]); + break; + + case 'WKST' : + break; + } + } + } + + return $r_rule; + } + + private function parseByDay($text) + { + global $_calendar_error; + + preg_match_all('/(-?\d{1,2})?(MO|TU|WE|TH|FR|SA|SU),?/', $text, $matches, PREG_SET_ORDER); + $wdays_map = ['MO' => '1', 'TU' => '2', 'WE' => '3', 'TH' => '4', 'FR' => '5', + 'SA' => '6', 'SU' => '7']; + $wdays = ""; + $sinterval = null; + foreach ($matches as $match) { + $wdays .= $wdays_map[$match[2]]; + if ($match[1]) { + if (!$sinterval && ((int) $match[1]) > 0 || $match[1] == '-1') { + if ($match[1] == '-1') { + $sinterval = '5'; + } else { + $sinterval = $match[1]; + } + } else { + throw new InvalidValuesException( + _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.") + ); + } + } + } + + return $wdays ? ['wdays' => $wdays, 'sinterval' => $sinterval] : false; + } + + private function parseByMonthDay($text) + { + $days = explode(',', $text); + if (count($days) > 1 || ((int) $days[0]) < 0) { + return false; + } + + return $days[0]; + } + + private function parseByMonth($text) + { + $months = explode(',', $text); + if (count($months) > 1) { + return false; + } + + return $months[0]; + } + + private function qp_decode($value) + { + return preg_replace_callback("/=([0-9A-F]{2})/", function ($m) {return chr(hexdec($m[1]));}, $value); + } + + private function parseClientIdentifier(&$data) + { + global $_calendar_error; + + if ($this->client_identifier == '') { + if (!preg_match('/PRODID((;[\W\w]*)*):([\W\w]+?)(\r\n|\r|\n)/', $data, $matches) + || !trim($matches[3])) { + // _("Die Datei ist keine gültige iCalendar-Datei!") + throw new InvalidValuesException(); + } else { + $this->client_identifier = trim($matches[3]); + } + } + return true; + } + + public function getClientIdentifier($data = null) + { + if (!is_null($data)) { + $this->parseClientIdentifier($data); + } + + return $this->client_identifier; + } + +} diff --git a/lib/classes/calendar/Owner.interface.php b/lib/classes/calendar/Owner.interface.php deleted file mode 100644 index a7c2519..0000000 --- a/lib/classes/calendar/Owner.interface.php +++ /dev/null @@ -1,40 +0,0 @@ -addPage(); - * $doc->addContent('Hallo, %%wir%% benutzen :studip: -Formatierung.'); - * $doc->dispatch("test_pdf"); - * //lines following dispatch won't be accessed anymor, because dispatch - * //cancels all other output. - * - */ -interface ExportDocument { - - /** - * Adding a new page to write new content on it. Must be called at least once - * before any call of addContent($text). - */ - public function addPage(); - - /** - * Adding an area of Stud.IP formatted content. - */ - public function addContent($content); - - /** - * Outputs the content as a file with MIME-type and aborts any other output. - * @param string $filename name of the future file without the extension. - */ - public function dispatch($filename); - - /** - * Saves the content as a file in the filesystem and returns a Stud.IP-document object. - * @param string $filename name of the future file without the extension. - * @param mixed $folder_id md5-id of a given folder in database or null for nothing - * @return StudipDocument of the exported file or false if creation of StudipDocument failed. - */ - public function save($filename, $folder_id = null); - -} - diff --git a/lib/classes/exportdocument/ExportDocument.php b/lib/classes/exportdocument/ExportDocument.php new file mode 100644 index 0000000..27aaee2 --- /dev/null +++ b/lib/classes/exportdocument/ExportDocument.php @@ -0,0 +1,59 @@ +addPage(); + * $doc->addContent('Hallo, %%wir%% benutzen :studip: -Formatierung.'); + * $doc->dispatch("test_pdf"); + * //lines following dispatch won't be accessed anymor, because dispatch + * //cancels all other output. + * + */ +interface ExportDocument { + + /** + * Adding a new page to write new content on it. Must be called at least once + * before any call of addContent($text). + */ + public function addPage(); + + /** + * Adding an area of Stud.IP formatted content. + */ + public function addContent($content); + + /** + * Outputs the content as a file with MIME-type and aborts any other output. + * @param string $filename name of the future file without the extension. + */ + public function dispatch($filename); + + /** + * Saves the content as a file in the filesystem and returns a Stud.IP-document object. + * @param string $filename name of the future file without the extension. + * @param mixed $folder_id md5-id of a given folder in database or null for nothing + * @return StudipDocument of the exported file or false if creation of StudipDocument failed. + */ + public function save($filename, $folder_id = null); + +} + diff --git a/lib/classes/exportdocument/ExportPDF.class.php b/lib/classes/exportdocument/ExportPDF.class.php deleted file mode 100644 index 915cfde..0000000 --- a/lib/classes/exportdocument/ExportPDF.class.php +++ /dev/null @@ -1,369 +0,0 @@ -addPage(); - * $doc->addContent('Hallo, %%wir%% benutzen :studip:-Formatierung.'); - * $doc->dispatch("test_pdf"); - * //lines following dispatch won't be accessed anymor, because dispatch - * //cancels all other output. - * - */ -class ExportPDF extends TCPDF implements ExportDocument -{ - private $media_proxy = NULL; - private $config; - private $defaults = false; - private $page_added = false; - private $h_title = ''; - private $h_string = ''; - private $domains; - static protected $countEndnote = 0; - - /** - * Create a basic document (without any content so far). - * @param string $orientation page orientation. Possible values are (case insensitive):
  • P or Portrait (default)
  • L or Landscape
  • '' (empty string) for automatic orientation
- * @param string $unit User measure unit. Possible values are:
  • pt: point
  • mm: millimeter (default)
  • cm: centimeter
  • in: inch

A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. - * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). - * @param boolean $unicode TRUE means that the input text is unicode (default = true) - * @param String $encoding charset encoding; default is UTF-8 - */ - public function __construct($orientation = 'P', $unit = 'mm', $format = 'A4', $unicode = true, $encoding = 'UTF-8') - { - $this->config = Config::GetInstance(); - if ($this->config->getValue('LOAD_EXTERNAL_MEDIA') == 'proxy') { - $this->media_proxy = new MediaProxy(); - } - parent::__construct($orientation, $unit, $format, $unicode, $encoding, false); - $this->getDomains(); - $this->setDefaults(); - } - - /** - * Adding a new page to the document. This page can contain even more content - * than for just one page. The pagebreak will be managed by tcpdf. But this function - * will create a new pagebreak. Needs to be called at least once to addContent. - * @param string $orientation page orientation. Possible values are (case insensitive):
  • P or Portrait (default)
  • L or Landscape
  • '' (empty string) for automatic orientation
- * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). - * @param boolean $keepmargins if true overwrites the default page margins with the current margins - * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content). - */ - public function addPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false) - { - $this->page_added = true; - parent::AddPage($orientation, $format, $keepmargins, $tocpage); - } - - /** - * Adding Stud.IP formatted code to the current page of the pdf. - * Remember to call addPage first. - * @param string $content Stud.IP formatted code - */ - public function addContent($content) - { - $endnote = ""; - preg_match_all("#\[comment(=.*)?\](.*)\[/comment\]#msU", $content, $matches); - if (count($matches[0])) { - $endnote .= "

"._("Kommentare")."
"; - for ($i=0; $i < count($matches[0]); $i++) { - $endnote .= ($i+1).") ".htmlReady(mb_substr($matches[1][$i], 1)).": ".htmlReady($matches[2][$i])."
"; - } - } - $content = preg_replace_callback("#\[comment(=.*)?\](.*)\[/comment\]#msU", function ($m) {return $this->addEndnote($m[1], $m[2]);}, $content); - $content = formatReady($content, true, true, true, null); - $content = str_replace("]+src="(.*?)"[^>]*>/', function ($match) { - $url = $match[1]; - - // Detect possible html entities in url and remove them - if (mb_strpos($url, '&') !== false) { - $url = html_entity_decode($url); - } - - // Handle optional media proxy - if (Config::GetInstance()->LOAD_EXTERNAL_MEDIA) { - $parsed = parse_url($url); - // Detect media proxy - if (mb_strpos($parsed['path'], 'media_proxy') !== false && mb_strpos($parsed['query'], 'url=') !== false) { - // Remove media proxy - parse_str($parsed['query'], $parameters); - $url = $parameters['url']; - } - } - - // Fetch headers from url, handle possible redirects - do { - $headers = get_headers($url, true, get_default_http_stream_context($url)); - if (!$headers) { - break; - } - list(, $status) = explode(' ', $headers[0]); - - $url = $headers['Location'] ?? $headers['location'] ?? $url; - } while (in_array($status, [300, 301, 302, 303, 305, 307])); - - $status = $status ?? 404; - - // Replace image with link on error (and not internal), otherwise return sainitized - // url - return ((!is_internal_url($url) || $status == 404) && $status >= 400) - ? sprintf('[%s]', $url, basename($url)) - : str_replace($match[1], $url, $match[0]); - }, $content); - - $this->writeHTML($content.$endnote); - } - - /** - * - * @param $commented_by - * @param $text - * @return - */ - public function addEndnote($commented_by, $text) - { - self::$countEndnote++; - return ">>"._("Kommentar")." ".self::$countEndnote.">>"; - } - - /** - * Dispatches the PDF to the user and cancels all other output of Stud.IP. - * @param string $filename name of the future file without the extension. - */ - public function dispatch($filename) - { - $this->Output($filename.".pdf", 'I'); - } - - /** - * Saves the content as a file in the filesystem and returns a FileRef object. - * - * @param string $filename name of the future file without the extension. - * @param mixed $folder_id md5-id of a given folder in database or null for nothing - * @return FileRef of the exported file or false if creation of the FileRef or its associated File object failed. - */ - public function save($filename, $folder_id = null) - { - global $user; - - //get folder: - $folder = Folder::find($folder_id); - if(!$folder) { - return false; - } - $folder = $folder->getTypedFolder(); - - //Create a File: - $file = new File(); - $file->user_id = $user->id; - $file->mime_type = 'application/pdf'; - $file->name = FileManager::cleanFileName($filename); - $file->storage = 'disk'; - if(!$file->store()) { - return false; - } - - //...and a FileRef: - $file_ref = new FileRef(); - $file_ref->file_id = $file->id; - $file_ref->folder_id = $folder->getId(); - $file_ref->user_id = $user->id; - $file_ref->name = $file->name; - if(!$file_ref->store()) { - return false; - } - - //Now we can create the PDF file and store it in the file's path: - $path = $file->getPath(); - $this->Output($path, 'F'); - $file->size = filesize($path); - if($file->store()) { - return $file_ref; - } - - return false; - } - - /** - * Sets some default-values for the document, that tcpdf needs. - */ - private function setDefaults () - { - $this->defaults = true; - - // setting defaults - $this->SetCreator('Stud.IP - ' . $this->config->getValue('UNI_NAME_CLEAN')); - // set header and footer fonts - $this->setHeaderFont([PDF_FONT_NAME_MAIN, '', 8]); - $this->setFooterFont([PDF_FONT_NAME_DATA, '', 8]); - // set default monospaced font - $this->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED); - //set margins - $this->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); - $this->SetHeaderMargin(PDF_MARGIN_HEADER); - $this->SetFooterMargin(PDF_MARGIN_FOOTER); - //set auto page breaks - $this->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM); - //set image scale factor - $this->setImageScale(PDF_IMAGE_SCALE_RATIO); - // set default font subsetting mode - $this->setFontSubsetting(true); - // Set font - //$this->SetFont('helvetica', '', 10, '', true); - - // set default page header - $this->setHeaderData(); - - } - - /** - * Sets the title of the header of each page. - * @param string $title title of the head - */ - public function setHeaderTitle ($title) - { - $this->h_title = $title; - $this->setHeaderData(); - } - - /** - * Sets the subtitle of the header of each page. - * @param string $subtitle subtitle of the head - */ - public function setHeaderSubtitle ($subtitle) - { - $this->h_string = $subtitle; - $this->setHeaderData(); - } - - /** - * Creates a header for each page with a custom logo defined - * @param string $ln header image logo - * @param int $lw header image logo width in mm - * @param string $ht string to print as title on document header - * @param string $hs string to print on document header - */ - public function setHeaderData($ln = '', $lw = 0, $ht = '', $hs = '', $tc = [], $lc = []) { - if (!$ln) { - $ln = Config::get()->PDF_LOGO ?: 'logos/logoklein.png'; - } - $lw = 30; - $ht = ($ht == '' ? $this->h_title : $ht); - $hs = ($hs == '' ? $this->h_string : $hs); - - parent::resetHeaderTemplate(); - - parent::setHeaderData($ln, $lw, $ht, $hs); - } - - /** - * Overrides writeHTML-method of tcpdf to convert image-urls, so that they - * aren't accessed via proxy but directly. - * @param string $html text to display - * @param boolean $ln if true add a new line after text (default = true) - * @param boolean $fill Indicates if the background must be painted (true) or transparent (false). - * @param boolean $reseth if true reset the last cell height (default false). - * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false). - * @param string $align Allows to center or align the text. Possible values are:
  • L : left align
  • C : center
  • R : right align
  • '' : empty string : left for LTR or right for RTL
- */ - public function writeHTML ($html, $ln = true, $fill = false, $reseth = false, $cell = false, $align = '') - { - $html = preg_replace_callback('/src="([^@].*)"/U', function ($m) {return $this->convertURL($m[1]);}, $html); - parent::writeHTML($html, $ln, $fill, $reseth, $cell, $align); - } - - /** - * Converts URLs in images so that the webserver can access them without proxy. - * @param string $url of an image - * @return string " src=\"".$converted_url."\"" - */ - protected function convertURL($url) - { - $convurl = $url; - $url_elements = @parse_url($url); - $url = $url_elements['path']; - if (isset($url_elements['query'])) { - $url .= "?{$url_elements['query']}"; - } - if (mb_strpos(implode('#', $this->domains), $url_elements['host']) !== false) { - if (mb_strpos($url, 'dispatch.php/media_proxy?url=') !== false) { - $targeturl = urldecode(mb_substr($url, 4)); - try { - // is file in cache? - if (!$metadata = $this->media_proxy->getMetaData($targeturl)) { - $convurl = $targeturl; - } else { - $convurl = $this->config->getValue('MEDIA_CACHE_PATH') . '/' . md5($targeturl); - } - } catch (Exception $e) { - $convurl = ''; - } - } else if (mb_stripos($url, 'dispatch.php/document/download') !== false) { - if (preg_match('#([a-f0-9]{32})#', $url, $matches)) { - $file_ref = FileRef::find($matches[1]); - $folder = $file_ref->folder->getTypedFolder(); - if($folder->isFileDownloadable($file_ref->id, $GLOBALS['user']->id)) { - $convurl = $file_ref->file->getPath(); - } - } - } else if (mb_stripos($url, 'download') !== false - || mb_stripos($url, 'sendfile.php') !== false) { - //// get file id - if (preg_match('#([a-f0-9]{32})#', $url, $matches)) { - $file_ref = FileRef::find($matches[1]); - $folder = $file_ref->folder->getTypedFolder(); - if($folder->isFileDownloadable($file_ref->id, $GLOBALS['user']->id)) { - $convurl = $file_ref->file->getPath(); - } else { - $convurl = Assets::image_path('messagebox/exception.png'); - } - } - } - } - - $src = 'src=""'; - $file_content = @file_get_contents($convurl, false, get_default_http_stream_context($convurl)); - if ($file_content) { - $img_size = @getimagesizefromstring($file_content); - if (is_array($img_size) && $img_size[0] > 0) { - $src = 'src="@' . base64_encode($file_content) . '"'; - } - } - return $src; - } - - /** - * finds an array with all domains of this Stud.IP and stores it in $this->domains - */ - protected function getDomains() - { - $this->domains = []; - $host_url_parsed = @parse_url($GLOBALS['ABSOLUTE_URI_STUDIP']); - if (isset($GLOBALS['STUDIP_DOMAINS'])) { - $this->domains = $GLOBALS['STUDIP_DOMAINS']; - } - $this->domains[] = $host_url_parsed['host']; - } - -} diff --git a/lib/classes/exportdocument/ExportPDF.php b/lib/classes/exportdocument/ExportPDF.php new file mode 100644 index 0000000..915cfde --- /dev/null +++ b/lib/classes/exportdocument/ExportPDF.php @@ -0,0 +1,369 @@ +addPage(); + * $doc->addContent('Hallo, %%wir%% benutzen :studip:-Formatierung.'); + * $doc->dispatch("test_pdf"); + * //lines following dispatch won't be accessed anymor, because dispatch + * //cancels all other output. + * + */ +class ExportPDF extends TCPDF implements ExportDocument +{ + private $media_proxy = NULL; + private $config; + private $defaults = false; + private $page_added = false; + private $h_title = ''; + private $h_string = ''; + private $domains; + static protected $countEndnote = 0; + + /** + * Create a basic document (without any content so far). + * @param string $orientation page orientation. Possible values are (case insensitive):
  • P or Portrait (default)
  • L or Landscape
  • '' (empty string) for automatic orientation
+ * @param string $unit User measure unit. Possible values are:
  • pt: point
  • mm: millimeter (default)
  • cm: centimeter
  • in: inch

A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. + * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). + * @param boolean $unicode TRUE means that the input text is unicode (default = true) + * @param String $encoding charset encoding; default is UTF-8 + */ + public function __construct($orientation = 'P', $unit = 'mm', $format = 'A4', $unicode = true, $encoding = 'UTF-8') + { + $this->config = Config::GetInstance(); + if ($this->config->getValue('LOAD_EXTERNAL_MEDIA') == 'proxy') { + $this->media_proxy = new MediaProxy(); + } + parent::__construct($orientation, $unit, $format, $unicode, $encoding, false); + $this->getDomains(); + $this->setDefaults(); + } + + /** + * Adding a new page to the document. This page can contain even more content + * than for just one page. The pagebreak will be managed by tcpdf. But this function + * will create a new pagebreak. Needs to be called at least once to addContent. + * @param string $orientation page orientation. Possible values are (case insensitive):
  • P or Portrait (default)
  • L or Landscape
  • '' (empty string) for automatic orientation
+ * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). + * @param boolean $keepmargins if true overwrites the default page margins with the current margins + * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content). + */ + public function addPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false) + { + $this->page_added = true; + parent::AddPage($orientation, $format, $keepmargins, $tocpage); + } + + /** + * Adding Stud.IP formatted code to the current page of the pdf. + * Remember to call addPage first. + * @param string $content Stud.IP formatted code + */ + public function addContent($content) + { + $endnote = ""; + preg_match_all("#\[comment(=.*)?\](.*)\[/comment\]#msU", $content, $matches); + if (count($matches[0])) { + $endnote .= "

"._("Kommentare")."
"; + for ($i=0; $i < count($matches[0]); $i++) { + $endnote .= ($i+1).") ".htmlReady(mb_substr($matches[1][$i], 1)).": ".htmlReady($matches[2][$i])."
"; + } + } + $content = preg_replace_callback("#\[comment(=.*)?\](.*)\[/comment\]#msU", function ($m) {return $this->addEndnote($m[1], $m[2]);}, $content); + $content = formatReady($content, true, true, true, null); + $content = str_replace("]+src="(.*?)"[^>]*>/', function ($match) { + $url = $match[1]; + + // Detect possible html entities in url and remove them + if (mb_strpos($url, '&') !== false) { + $url = html_entity_decode($url); + } + + // Handle optional media proxy + if (Config::GetInstance()->LOAD_EXTERNAL_MEDIA) { + $parsed = parse_url($url); + // Detect media proxy + if (mb_strpos($parsed['path'], 'media_proxy') !== false && mb_strpos($parsed['query'], 'url=') !== false) { + // Remove media proxy + parse_str($parsed['query'], $parameters); + $url = $parameters['url']; + } + } + + // Fetch headers from url, handle possible redirects + do { + $headers = get_headers($url, true, get_default_http_stream_context($url)); + if (!$headers) { + break; + } + list(, $status) = explode(' ', $headers[0]); + + $url = $headers['Location'] ?? $headers['location'] ?? $url; + } while (in_array($status, [300, 301, 302, 303, 305, 307])); + + $status = $status ?? 404; + + // Replace image with link on error (and not internal), otherwise return sainitized + // url + return ((!is_internal_url($url) || $status == 404) && $status >= 400) + ? sprintf('[%s]', $url, basename($url)) + : str_replace($match[1], $url, $match[0]); + }, $content); + + $this->writeHTML($content.$endnote); + } + + /** + * + * @param $commented_by + * @param $text + * @return + */ + public function addEndnote($commented_by, $text) + { + self::$countEndnote++; + return ">>"._("Kommentar")." ".self::$countEndnote.">>"; + } + + /** + * Dispatches the PDF to the user and cancels all other output of Stud.IP. + * @param string $filename name of the future file without the extension. + */ + public function dispatch($filename) + { + $this->Output($filename.".pdf", 'I'); + } + + /** + * Saves the content as a file in the filesystem and returns a FileRef object. + * + * @param string $filename name of the future file without the extension. + * @param mixed $folder_id md5-id of a given folder in database or null for nothing + * @return FileRef of the exported file or false if creation of the FileRef or its associated File object failed. + */ + public function save($filename, $folder_id = null) + { + global $user; + + //get folder: + $folder = Folder::find($folder_id); + if(!$folder) { + return false; + } + $folder = $folder->getTypedFolder(); + + //Create a File: + $file = new File(); + $file->user_id = $user->id; + $file->mime_type = 'application/pdf'; + $file->name = FileManager::cleanFileName($filename); + $file->storage = 'disk'; + if(!$file->store()) { + return false; + } + + //...and a FileRef: + $file_ref = new FileRef(); + $file_ref->file_id = $file->id; + $file_ref->folder_id = $folder->getId(); + $file_ref->user_id = $user->id; + $file_ref->name = $file->name; + if(!$file_ref->store()) { + return false; + } + + //Now we can create the PDF file and store it in the file's path: + $path = $file->getPath(); + $this->Output($path, 'F'); + $file->size = filesize($path); + if($file->store()) { + return $file_ref; + } + + return false; + } + + /** + * Sets some default-values for the document, that tcpdf needs. + */ + private function setDefaults () + { + $this->defaults = true; + + // setting defaults + $this->SetCreator('Stud.IP - ' . $this->config->getValue('UNI_NAME_CLEAN')); + // set header and footer fonts + $this->setHeaderFont([PDF_FONT_NAME_MAIN, '', 8]); + $this->setFooterFont([PDF_FONT_NAME_DATA, '', 8]); + // set default monospaced font + $this->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED); + //set margins + $this->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); + $this->SetHeaderMargin(PDF_MARGIN_HEADER); + $this->SetFooterMargin(PDF_MARGIN_FOOTER); + //set auto page breaks + $this->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM); + //set image scale factor + $this->setImageScale(PDF_IMAGE_SCALE_RATIO); + // set default font subsetting mode + $this->setFontSubsetting(true); + // Set font + //$this->SetFont('helvetica', '', 10, '', true); + + // set default page header + $this->setHeaderData(); + + } + + /** + * Sets the title of the header of each page. + * @param string $title title of the head + */ + public function setHeaderTitle ($title) + { + $this->h_title = $title; + $this->setHeaderData(); + } + + /** + * Sets the subtitle of the header of each page. + * @param string $subtitle subtitle of the head + */ + public function setHeaderSubtitle ($subtitle) + { + $this->h_string = $subtitle; + $this->setHeaderData(); + } + + /** + * Creates a header for each page with a custom logo defined + * @param string $ln header image logo + * @param int $lw header image logo width in mm + * @param string $ht string to print as title on document header + * @param string $hs string to print on document header + */ + public function setHeaderData($ln = '', $lw = 0, $ht = '', $hs = '', $tc = [], $lc = []) { + if (!$ln) { + $ln = Config::get()->PDF_LOGO ?: 'logos/logoklein.png'; + } + $lw = 30; + $ht = ($ht == '' ? $this->h_title : $ht); + $hs = ($hs == '' ? $this->h_string : $hs); + + parent::resetHeaderTemplate(); + + parent::setHeaderData($ln, $lw, $ht, $hs); + } + + /** + * Overrides writeHTML-method of tcpdf to convert image-urls, so that they + * aren't accessed via proxy but directly. + * @param string $html text to display + * @param boolean $ln if true add a new line after text (default = true) + * @param boolean $fill Indicates if the background must be painted (true) or transparent (false). + * @param boolean $reseth if true reset the last cell height (default false). + * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false). + * @param string $align Allows to center or align the text. Possible values are:
  • L : left align
  • C : center
  • R : right align
  • '' : empty string : left for LTR or right for RTL
+ */ + public function writeHTML ($html, $ln = true, $fill = false, $reseth = false, $cell = false, $align = '') + { + $html = preg_replace_callback('/src="([^@].*)"/U', function ($m) {return $this->convertURL($m[1]);}, $html); + parent::writeHTML($html, $ln, $fill, $reseth, $cell, $align); + } + + /** + * Converts URLs in images so that the webserver can access them without proxy. + * @param string $url of an image + * @return string " src=\"".$converted_url."\"" + */ + protected function convertURL($url) + { + $convurl = $url; + $url_elements = @parse_url($url); + $url = $url_elements['path']; + if (isset($url_elements['query'])) { + $url .= "?{$url_elements['query']}"; + } + if (mb_strpos(implode('#', $this->domains), $url_elements['host']) !== false) { + if (mb_strpos($url, 'dispatch.php/media_proxy?url=') !== false) { + $targeturl = urldecode(mb_substr($url, 4)); + try { + // is file in cache? + if (!$metadata = $this->media_proxy->getMetaData($targeturl)) { + $convurl = $targeturl; + } else { + $convurl = $this->config->getValue('MEDIA_CACHE_PATH') . '/' . md5($targeturl); + } + } catch (Exception $e) { + $convurl = ''; + } + } else if (mb_stripos($url, 'dispatch.php/document/download') !== false) { + if (preg_match('#([a-f0-9]{32})#', $url, $matches)) { + $file_ref = FileRef::find($matches[1]); + $folder = $file_ref->folder->getTypedFolder(); + if($folder->isFileDownloadable($file_ref->id, $GLOBALS['user']->id)) { + $convurl = $file_ref->file->getPath(); + } + } + } else if (mb_stripos($url, 'download') !== false + || mb_stripos($url, 'sendfile.php') !== false) { + //// get file id + if (preg_match('#([a-f0-9]{32})#', $url, $matches)) { + $file_ref = FileRef::find($matches[1]); + $folder = $file_ref->folder->getTypedFolder(); + if($folder->isFileDownloadable($file_ref->id, $GLOBALS['user']->id)) { + $convurl = $file_ref->file->getPath(); + } else { + $convurl = Assets::image_path('messagebox/exception.png'); + } + } + } + } + + $src = 'src=""'; + $file_content = @file_get_contents($convurl, false, get_default_http_stream_context($convurl)); + if ($file_content) { + $img_size = @getimagesizefromstring($file_content); + if (is_array($img_size) && $img_size[0] > 0) { + $src = 'src="@' . base64_encode($file_content) . '"'; + } + } + return $src; + } + + /** + * finds an array with all domains of this Stud.IP and stores it in $this->domains + */ + protected function getDomains() + { + $this->domains = []; + $host_url_parsed = @parse_url($GLOBALS['ABSOLUTE_URI_STUDIP']); + if (isset($GLOBALS['STUDIP_DOMAINS'])) { + $this->domains = $GLOBALS['STUDIP_DOMAINS']; + } + $this->domains[] = $host_url_parsed['host']; + } + +} diff --git a/lib/classes/librarysearch/LibraryDocument.class.php b/lib/classes/librarysearch/LibraryDocument.class.php deleted file mode 100644 index c1d297e..0000000 --- a/lib/classes/librarysearch/LibraryDocument.class.php +++ /dev/null @@ -1,442 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - -/** - * This class represents a document from a library. - */ -class LibraryDocument -{ - /** - * An unique ID of the document. - */ - public $id = ''; - - - /** - * The OPAC ID of the document. - */ - public $opac_document_id = ''; - - - /** - * The link to the document page in the OPAC system. - */ - public $opac_link = ''; - - - /** - * The CSL document type. - */ - public $type = ''; - - - /** - * The CSL datafields. - */ - public $csl_data = []; - - - /** - * The name of the catalog from which the document has been retrieved. - */ - public $catalog = ''; - - - /** - * Other data that cannot be stored in the $csl_data array. - */ - public $datafields; - - - /** - * The search parameter that have been used to retrieve this item. - */ - public $search_params = []; - - - /** - * Generates an ID for the document. - */ - public function getId() - { - if (!$this->id) { - $this->id = md5(uniqid('LibraryDocument' . $this->getTitle())); - } - return $this->id; - } - - - /** - * Returns the type of the document as string. - * - * @returns string The type of the document. - */ - public function getType($format = 'name'): string - { - global $LIBRARY_DOCUMENT_TYPES; - - if ($format === 'name') { - return $this->type; - } - if ($format === 'display_name') { - $ldt = SimpleCollection::createFromArray($LIBRARY_DOCUMENT_TYPES); - $found = $ldt->findOneBy('name', $this->type); - $lang = in_array($_SESSION['_language'], ['de_DE', 'en_GB']) ? $_SESSION['_language'] : 'de_DE'; - if ($found) { - return $found['display_name'][$lang]; - } - } - - return ''; - } - - - /** - * Returns the title of the document as string. - * - * @param string $format The format of the title. - * 'short' means that only the title is returned. - * 'long' means that the author, the year and the title are - * concatenated into one string. - * 'long-comma' means that the author, the title and the year - * are concatenated in that order, only separated by a comma. - * - * @returns string The title of the document. - */ - public function getTitle($format = 'short'): string - { - if ($format == 'long') { - $long_title = ''; - if (isset($this->csl_data['issued'], $this->csl_data['author'])) { - $first_author_last_name = $this->csl_data['author'][0]['family']; - $year = $this->getIssueDate(true); - if ($year) { - $long_title = sprintf('%1$s (%2$s) - ', $first_author_last_name, $year); - } else { - $long_title = sprintf('%s - ', $first_author_last_name); - } - } elseif (isset($this->csl_data['author'])) { - $first_author_last_name = $this->csl_data['author'][0]['family']; - $long_title = sprintf('%s - ', $first_author_last_name); - } - $long_title .= $this->csl_data['title'] ?? ''; - return $long_title; - } elseif ($format == 'long-comma') { - $data = []; - $first_author_last_name = trim($this->csl_data['author'][0]['family']); - $year = trim($this->getIssueDate(true)); - if ($first_author_last_name) { - $data[] = $first_author_last_name; - } - $data[] = $this->csl_data['title'] ?? ''; - if ($year) { - $data[] = $year; - } - return implode(', ', $data); - } else { - return $this->csl_data['title'] ?? ''; - } - } - - - /** - * @returns string A list with all author names. - */ - public function getAuthorNames(): string - { - if (empty($this->csl_data['author'])) { - return ''; - } - - $names = []; - foreach ($this->csl_data['author'] as $author) { - $names[] = sprintf('%1$s, %2$s', $author['family'], $author['given']); - } - return implode('; ', $names); - } - - - /** - * Returns a string with the issue date. - * - * @param bool $year_only Whether to return only the year of the date (true) - * or the whole date (false). Defaults to false. - * - * @returns string A string representing the issue date. - */ - public function getIssueDate($year_only = false): string - { - if (!$this->csl_data['issued']) { - return ''; - } - if ($year_only) { - $year = @$this->csl_data['issued']['date-parts'][0][0]; - return $year ?: ''; - } - return implode('-', $this->csl_data['issued']['date-parts'][0]) ?: ''; - } - - - /** - * @returns string A string with identifiers of the document (ISBN, URL, ...). - */ - public function getIdentifiers(): string - { - $identifiers = []; - if ($this->csl_data['ISBN']) { - $identifiers[] = sprintf(_('ISBN: %s'), $this->csl_data['ISBN']); - } - if ($this->csl_data['ISSN']) { - $identifiers[] = sprintf(_('ISSN: %s'), $this->csl_data['ISSN']); - } - return implode('; ', $identifiers); - } - - - /** - * Filters the CSL data fields by the document type. - * Only those CSL fields that are specified in library_config.inc.php for - * the document type are kept. - */ - public function filterCslFieldsByType() - { - if (!$this->type) { - return; - } - - $doc_type_config = null; - foreach ($GLOBALS['LIBRARY_DOCUMENT_TYPES'] as $doc_config) { - if ($doc_config['name'] == $this->type) { - $doc_type_config = $doc_config; - break; - } - } - if ($doc_type_config == null) { - return; - } - $this->csl_data = array_filter( - $this->csl_data, - function ($field) use ($doc_type_config) { - return in_array($field, $doc_type_config['properties']); - }, - ARRAY_FILTER_USE_KEY - ); - } - - - /** - * Converts this LibraryDocument to an associative array. - * - * @returns array An associative array containing the data of this - * LibraryDocument instance. - */ - public function toArray(): array - { - $data = [ - 'id' => $this->getId(), - 'type' => $this->getType(), - 'csl_data' => $this->csl_data, - 'datafields' => $this->datafields, - 'search_params' => $this->search_params, - 'catalog' => $this->catalog, - 'opac_link' => $this->opac_link - ]; - return $data; - } - - - /** - * Converts this LibraryDocument to JSON data. - * - * @returns string A string containing the JSON encoded version of this - * LibraryDocument instance. - */ - public function toJson(): string - { - return json_encode($this->toArray()); - } - - - /** - * Fills or creates empty or missing CSL data fields that may be required - * when rendering the CSL data. - * - * @returns array The "enriched" CSL data from this document. - */ - public function fillEmptyCslFields(): array - { - $enriched_data = []; - foreach ($this->csl_data as $key => $field) { - if ($key == 'author') { - if (!$field[0]['family']) { - $field[0]['family'] = ' '; - } - } - $enriched_data[$key] = $field; - } - //Make sure all "mandatory" fields are there: - if (!array_key_exists('author', $enriched_data)) { - $enriched_data['author'] = [ - [ - 'given' => '', - 'family' => ' ', - 'suffix' => '' - ] - ]; - } - return $enriched_data; - } - - - /** - * Creates a LibraryDocument instance from an associative array. - * - * @param array $data An associative array containing data for - * a LibraryDocument instance. - * - * @returns LibraryDocument|null A LibraryDocument instance on success - * or null on failure. - */ - public static function createFromArray(array $data = []) - { - if (!$data) { - return null; - } - $doc = new LibraryDocument(); - $doc->id = $data['id']; - $doc->type = $data['type']; - $doc->csl_data = $data['csl_data']; - $doc->datafields = $data['datafields']; - $doc->search_params = $data['search_params']; - $doc->catalog = $data['catalog'] ?? null; - $doc->opac_link = $data['opac_link'] ?? null; - return $doc; - } - - - /** - * Creates a LibraryDocument instance from JSON data. - * - * @param string $json_string A JSON string containing data for - * a LibraryDocument instance. - * - * @returns LibraryDocument|null A LibraryDocument instance on success - * or null on failure. - */ - public static function createFromJson(string $json_string = "") - { - if (!$json_string) { - return null; - } - $data = json_decode($json_string); - if (!$data) { - return null; - } - return self::createFromArray($data); - } - - - /** - * Determines if this document is equal to another document. - * Equality is determined by comparing various ID fields - * and as a last resort, the title, author and year are compared. - * - * @param LibraryDocument $other Another library document that shall be - * compared to this document. - * - * @returns bool True, if this document is equal to the other document, - * false otherwise. - */ - public function isEqualTo(LibraryDocument $other): bool - { - if ($this->type != $other->type) { - //No need to do any further checks. - return false; - } - if ($this->id && ($this->id == $other->id)) { - return true; - } elseif ($this->csl_data['ISSN'] && ($this->csl_data['ISSN'] == $other->csl_data['ISSN'])) { - return true; - } elseif ($this->csl_data['ISBN'] && ($this->csl_data['ISBN'] == $other->csl_data['ISBN'])) { - return true; - } elseif ($this->csl_data['DOI'] && ($this->csl_data['DOI'] == $other->csl_data['DOI'])) { - return true; - } elseif ($this->csl_data['title'] && $this->csl_data['author'] && $this->csl_data['issued'] - && ($this->csl_data['title'] == $other->csl_data['title']) - && ($this->csl_data['author'] == $other->csl_data['author']) - && ($this->csl_data['issued'] == $other->csl_data['issued'])) { - return true; - } - return false; - } - - - /** - * @returns Flexi\Template A template containing information about the - * the document. - */ - public function getInfoTemplate($format = 'short') - { - $factory = new Flexi\Factory( - $GLOBALS['STUDIP_BASE_PATH'] . '/templates/library/' - ); - $template = $factory->open('library_document_info'); - $template->set_attribute('document', $this); - $template->set_attribute('format', $format); - return $template; - } - - - /** - * Creates a descriptive text of the search parameters that lead to the - * retrieval of this document. - * - * @returns string[] An array with a textual representation of all - * used search parameters. - */ - public function getSearchDescription() - { - $description = []; - if (isset($this->search_params[LibrarySearch::AUTHOR])) { - $description[] = sprintf(_('Autor: „%s“'), $this->search_params[LibrarySearch::AUTHOR]); - } - if (isset($this->search_params[LibrarySearch::YEAR])) { - $description[] = sprintf(_('Jahr: „%s“'), $this->search_params[LibrarySearch::YEAR]); - } - if (isset($this->search_params[LibrarySearch::TITLE])) { - $description[] = sprintf(_('Titel: „%s“'), $this->search_params[LibrarySearch::TITLE]); - } - if (isset($this->search_params[LibrarySearch::NUMBER])) { - $description[] = sprintf(_('Nummer: „%s“'), $this->search_params[LibrarySearch::NUMBER]); - } - if (isset($this->search_params[LibrarySearch::PUBLICATION])) { - $description[] = sprintf(_('Zeitschrift: „%s“'), $this->search_params[LibrarySearch::PUBLICATION]); - } - if (isset($this->search_params[LibrarySearch::SIGNATURE])) { - $description[] = sprintf(_('Signatur: „%s“'), $this->search_params[LibrarySearch::SIGNATURE]); - } - return $description; - } - - public function getIcon() - { - global $LIBRARY_DOCUMENT_TYPES; - $ldt = SimpleCollection::createFromArray($LIBRARY_DOCUMENT_TYPES); - $found = $ldt->findOneBy('name', $this->type); - $shape = $found['icon'] ?? 'literature-request'; - return Icon::create($shape); - } -} diff --git a/lib/classes/librarysearch/LibraryDocument.php b/lib/classes/librarysearch/LibraryDocument.php new file mode 100644 index 0000000..c1d297e --- /dev/null +++ b/lib/classes/librarysearch/LibraryDocument.php @@ -0,0 +1,442 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + +/** + * This class represents a document from a library. + */ +class LibraryDocument +{ + /** + * An unique ID of the document. + */ + public $id = ''; + + + /** + * The OPAC ID of the document. + */ + public $opac_document_id = ''; + + + /** + * The link to the document page in the OPAC system. + */ + public $opac_link = ''; + + + /** + * The CSL document type. + */ + public $type = ''; + + + /** + * The CSL datafields. + */ + public $csl_data = []; + + + /** + * The name of the catalog from which the document has been retrieved. + */ + public $catalog = ''; + + + /** + * Other data that cannot be stored in the $csl_data array. + */ + public $datafields; + + + /** + * The search parameter that have been used to retrieve this item. + */ + public $search_params = []; + + + /** + * Generates an ID for the document. + */ + public function getId() + { + if (!$this->id) { + $this->id = md5(uniqid('LibraryDocument' . $this->getTitle())); + } + return $this->id; + } + + + /** + * Returns the type of the document as string. + * + * @returns string The type of the document. + */ + public function getType($format = 'name'): string + { + global $LIBRARY_DOCUMENT_TYPES; + + if ($format === 'name') { + return $this->type; + } + if ($format === 'display_name') { + $ldt = SimpleCollection::createFromArray($LIBRARY_DOCUMENT_TYPES); + $found = $ldt->findOneBy('name', $this->type); + $lang = in_array($_SESSION['_language'], ['de_DE', 'en_GB']) ? $_SESSION['_language'] : 'de_DE'; + if ($found) { + return $found['display_name'][$lang]; + } + } + + return ''; + } + + + /** + * Returns the title of the document as string. + * + * @param string $format The format of the title. + * 'short' means that only the title is returned. + * 'long' means that the author, the year and the title are + * concatenated into one string. + * 'long-comma' means that the author, the title and the year + * are concatenated in that order, only separated by a comma. + * + * @returns string The title of the document. + */ + public function getTitle($format = 'short'): string + { + if ($format == 'long') { + $long_title = ''; + if (isset($this->csl_data['issued'], $this->csl_data['author'])) { + $first_author_last_name = $this->csl_data['author'][0]['family']; + $year = $this->getIssueDate(true); + if ($year) { + $long_title = sprintf('%1$s (%2$s) - ', $first_author_last_name, $year); + } else { + $long_title = sprintf('%s - ', $first_author_last_name); + } + } elseif (isset($this->csl_data['author'])) { + $first_author_last_name = $this->csl_data['author'][0]['family']; + $long_title = sprintf('%s - ', $first_author_last_name); + } + $long_title .= $this->csl_data['title'] ?? ''; + return $long_title; + } elseif ($format == 'long-comma') { + $data = []; + $first_author_last_name = trim($this->csl_data['author'][0]['family']); + $year = trim($this->getIssueDate(true)); + if ($first_author_last_name) { + $data[] = $first_author_last_name; + } + $data[] = $this->csl_data['title'] ?? ''; + if ($year) { + $data[] = $year; + } + return implode(', ', $data); + } else { + return $this->csl_data['title'] ?? ''; + } + } + + + /** + * @returns string A list with all author names. + */ + public function getAuthorNames(): string + { + if (empty($this->csl_data['author'])) { + return ''; + } + + $names = []; + foreach ($this->csl_data['author'] as $author) { + $names[] = sprintf('%1$s, %2$s', $author['family'], $author['given']); + } + return implode('; ', $names); + } + + + /** + * Returns a string with the issue date. + * + * @param bool $year_only Whether to return only the year of the date (true) + * or the whole date (false). Defaults to false. + * + * @returns string A string representing the issue date. + */ + public function getIssueDate($year_only = false): string + { + if (!$this->csl_data['issued']) { + return ''; + } + if ($year_only) { + $year = @$this->csl_data['issued']['date-parts'][0][0]; + return $year ?: ''; + } + return implode('-', $this->csl_data['issued']['date-parts'][0]) ?: ''; + } + + + /** + * @returns string A string with identifiers of the document (ISBN, URL, ...). + */ + public function getIdentifiers(): string + { + $identifiers = []; + if ($this->csl_data['ISBN']) { + $identifiers[] = sprintf(_('ISBN: %s'), $this->csl_data['ISBN']); + } + if ($this->csl_data['ISSN']) { + $identifiers[] = sprintf(_('ISSN: %s'), $this->csl_data['ISSN']); + } + return implode('; ', $identifiers); + } + + + /** + * Filters the CSL data fields by the document type. + * Only those CSL fields that are specified in library_config.inc.php for + * the document type are kept. + */ + public function filterCslFieldsByType() + { + if (!$this->type) { + return; + } + + $doc_type_config = null; + foreach ($GLOBALS['LIBRARY_DOCUMENT_TYPES'] as $doc_config) { + if ($doc_config['name'] == $this->type) { + $doc_type_config = $doc_config; + break; + } + } + if ($doc_type_config == null) { + return; + } + $this->csl_data = array_filter( + $this->csl_data, + function ($field) use ($doc_type_config) { + return in_array($field, $doc_type_config['properties']); + }, + ARRAY_FILTER_USE_KEY + ); + } + + + /** + * Converts this LibraryDocument to an associative array. + * + * @returns array An associative array containing the data of this + * LibraryDocument instance. + */ + public function toArray(): array + { + $data = [ + 'id' => $this->getId(), + 'type' => $this->getType(), + 'csl_data' => $this->csl_data, + 'datafields' => $this->datafields, + 'search_params' => $this->search_params, + 'catalog' => $this->catalog, + 'opac_link' => $this->opac_link + ]; + return $data; + } + + + /** + * Converts this LibraryDocument to JSON data. + * + * @returns string A string containing the JSON encoded version of this + * LibraryDocument instance. + */ + public function toJson(): string + { + return json_encode($this->toArray()); + } + + + /** + * Fills or creates empty or missing CSL data fields that may be required + * when rendering the CSL data. + * + * @returns array The "enriched" CSL data from this document. + */ + public function fillEmptyCslFields(): array + { + $enriched_data = []; + foreach ($this->csl_data as $key => $field) { + if ($key == 'author') { + if (!$field[0]['family']) { + $field[0]['family'] = ' '; + } + } + $enriched_data[$key] = $field; + } + //Make sure all "mandatory" fields are there: + if (!array_key_exists('author', $enriched_data)) { + $enriched_data['author'] = [ + [ + 'given' => '', + 'family' => ' ', + 'suffix' => '' + ] + ]; + } + return $enriched_data; + } + + + /** + * Creates a LibraryDocument instance from an associative array. + * + * @param array $data An associative array containing data for + * a LibraryDocument instance. + * + * @returns LibraryDocument|null A LibraryDocument instance on success + * or null on failure. + */ + public static function createFromArray(array $data = []) + { + if (!$data) { + return null; + } + $doc = new LibraryDocument(); + $doc->id = $data['id']; + $doc->type = $data['type']; + $doc->csl_data = $data['csl_data']; + $doc->datafields = $data['datafields']; + $doc->search_params = $data['search_params']; + $doc->catalog = $data['catalog'] ?? null; + $doc->opac_link = $data['opac_link'] ?? null; + return $doc; + } + + + /** + * Creates a LibraryDocument instance from JSON data. + * + * @param string $json_string A JSON string containing data for + * a LibraryDocument instance. + * + * @returns LibraryDocument|null A LibraryDocument instance on success + * or null on failure. + */ + public static function createFromJson(string $json_string = "") + { + if (!$json_string) { + return null; + } + $data = json_decode($json_string); + if (!$data) { + return null; + } + return self::createFromArray($data); + } + + + /** + * Determines if this document is equal to another document. + * Equality is determined by comparing various ID fields + * and as a last resort, the title, author and year are compared. + * + * @param LibraryDocument $other Another library document that shall be + * compared to this document. + * + * @returns bool True, if this document is equal to the other document, + * false otherwise. + */ + public function isEqualTo(LibraryDocument $other): bool + { + if ($this->type != $other->type) { + //No need to do any further checks. + return false; + } + if ($this->id && ($this->id == $other->id)) { + return true; + } elseif ($this->csl_data['ISSN'] && ($this->csl_data['ISSN'] == $other->csl_data['ISSN'])) { + return true; + } elseif ($this->csl_data['ISBN'] && ($this->csl_data['ISBN'] == $other->csl_data['ISBN'])) { + return true; + } elseif ($this->csl_data['DOI'] && ($this->csl_data['DOI'] == $other->csl_data['DOI'])) { + return true; + } elseif ($this->csl_data['title'] && $this->csl_data['author'] && $this->csl_data['issued'] + && ($this->csl_data['title'] == $other->csl_data['title']) + && ($this->csl_data['author'] == $other->csl_data['author']) + && ($this->csl_data['issued'] == $other->csl_data['issued'])) { + return true; + } + return false; + } + + + /** + * @returns Flexi\Template A template containing information about the + * the document. + */ + public function getInfoTemplate($format = 'short') + { + $factory = new Flexi\Factory( + $GLOBALS['STUDIP_BASE_PATH'] . '/templates/library/' + ); + $template = $factory->open('library_document_info'); + $template->set_attribute('document', $this); + $template->set_attribute('format', $format); + return $template; + } + + + /** + * Creates a descriptive text of the search parameters that lead to the + * retrieval of this document. + * + * @returns string[] An array with a textual representation of all + * used search parameters. + */ + public function getSearchDescription() + { + $description = []; + if (isset($this->search_params[LibrarySearch::AUTHOR])) { + $description[] = sprintf(_('Autor: „%s“'), $this->search_params[LibrarySearch::AUTHOR]); + } + if (isset($this->search_params[LibrarySearch::YEAR])) { + $description[] = sprintf(_('Jahr: „%s“'), $this->search_params[LibrarySearch::YEAR]); + } + if (isset($this->search_params[LibrarySearch::TITLE])) { + $description[] = sprintf(_('Titel: „%s“'), $this->search_params[LibrarySearch::TITLE]); + } + if (isset($this->search_params[LibrarySearch::NUMBER])) { + $description[] = sprintf(_('Nummer: „%s“'), $this->search_params[LibrarySearch::NUMBER]); + } + if (isset($this->search_params[LibrarySearch::PUBLICATION])) { + $description[] = sprintf(_('Zeitschrift: „%s“'), $this->search_params[LibrarySearch::PUBLICATION]); + } + if (isset($this->search_params[LibrarySearch::SIGNATURE])) { + $description[] = sprintf(_('Signatur: „%s“'), $this->search_params[LibrarySearch::SIGNATURE]); + } + return $description; + } + + public function getIcon() + { + global $LIBRARY_DOCUMENT_TYPES; + $ldt = SimpleCollection::createFromArray($LIBRARY_DOCUMENT_TYPES); + $found = $ldt->findOneBy('name', $this->type); + $shape = $found['icon'] ?? 'literature-request'; + return Icon::create($shape); + } +} diff --git a/lib/classes/librarysearch/LibraryResultParser.interface.php b/lib/classes/librarysearch/LibraryResultParser.interface.php deleted file mode 100644 index eae45a4..0000000 --- a/lib/classes/librarysearch/LibraryResultParser.interface.php +++ /dev/null @@ -1,40 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This interface defines methods for reading search results from a - * library catalog. - */ -interface LibraryResultParser -{ - /** - * Read a set of search results from raw data. - * - * @returns LibraryDocument[] An array with all LibraryDocument - * instances that could be read from the raw data. - */ - public function readResultSet($data = '') : array; - - - /** - * Reads one search result record from raw data. - * - * @returns LibraryDocument|null The read data as LibraryDocument. - * null is returned if no document could be read. - */ - public function readRecord($data = '') : LibraryDocument; -} diff --git a/lib/classes/librarysearch/LibraryResultParser.php b/lib/classes/librarysearch/LibraryResultParser.php new file mode 100644 index 0000000..eae45a4 --- /dev/null +++ b/lib/classes/librarysearch/LibraryResultParser.php @@ -0,0 +1,40 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This interface defines methods for reading search results from a + * library catalog. + */ +interface LibraryResultParser +{ + /** + * Read a set of search results from raw data. + * + * @returns LibraryDocument[] An array with all LibraryDocument + * instances that could be read from the raw data. + */ + public function readResultSet($data = '') : array; + + + /** + * Reads one search result record from raw data. + * + * @returns LibraryDocument|null The read data as LibraryDocument. + * null is returned if no document could be read. + */ + public function readRecord($data = '') : LibraryDocument; +} diff --git a/lib/classes/librarysearch/LibrarySearch.class.php b/lib/classes/librarysearch/LibrarySearch.class.php deleted file mode 100644 index 473eacf..0000000 --- a/lib/classes/librarysearch/LibrarySearch.class.php +++ /dev/null @@ -1,150 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This class contains basic methods for querying a library catalog - * using standardised search parameters. - */ -abstract class LibrarySearch -{ - //The following constants define the strings for the - //standardised field names for the query method. These can be - //converted to library-specific field names. - const TITLE = 'title'; - const AUTHOR = 'author'; - const YEAR = 'year'; - const NUMBER = 'number'; - const ISSN = 'issn'; - const ISBN = 'isbn'; - const PUBLICATION = 'publication'; - const SIGNATURE = 'signature'; - - //Constants for the ordering of results. - const ORDER_BY_RELEVANCE = 'relevance'; - const ORDER_BY_YEAR = 'year'; - - - /** - * The base URL for the HTTP request to retrieve data. - */ - protected $request_base_url = ''; - - - /** - * Additional URL parameters for the HTTP request to retrieve data. - */ - protected $request_url_parameters = []; - - - /** - * Implementation-specific configuration that can define the behavior - * of the LibrarySearch implementation. - */ - protected $settings = []; - - - /** - * A basic constructor. - * - * @param array $configuration The configuration for the LibrarySearch - * implementation. It should be an associative array with the following - * keys: - * - base_url: The base URL for retrieving data. - * - additional_url_parameters: Additional URL parameters for the base URL. - * - settings: Implementation-specific configuration. This should also - * be an associative array. - */ - public function __construct(array $configuration = []) - { - if ($configuration['base_url']) { - $this->request_base_url = $configuration['base_url']; - } - if (is_array($configuration['additional_url_parameters'])) { - $this->request_url_parameters = $configuration['additional_url_parameters']; - } - if (is_array($configuration['settings'])) { - $this->settings = $configuration['settings']; - } - } - - - /** - * This method shall replace the generalised search query fields with the - * implementation specific query fields. - * - * @param array $query_fields An array with query parameters using the - * generalised query fields. - * - * @returns array The translated version of the $query_fields array. - */ - abstract protected function translateQueryFields(array $query_fields = []) : array; - - - /** - * A common method for the libcurl code to request data from an URL so that - * LibrarySearch implementations don't have to include their own libcurl - * code to get data. - * - * @param string $base_url The base URL to request data from. - * - * @param array $url_parameters URL parameters for the request. The array - * should consist of an associative array with keys representing - * the parameter name and the values representing the parameter values. - * - * @returns string|bool The result of the request. If the base URL is empty - * or no data could be retrieved due to an error, false is returned. - * In case of success, a string with the retrieved data is returned. - */ - protected function requestData(string $base_url = '', array $url_parameters = []) - { - if (!$base_url) { - return false; - } - $full_url = $base_url; - if ($url_parameters) { - $full_url .= '?' . http_build_query($url_parameters); - } - - $data = file_get_contents($full_url, false, get_default_http_stream_context($base_url)); - return $data; - } - - - /** - * Starts a query to a library catalogue using the specified - * parameters. If standardised parameters as defined in the FIELD_ - * constants of this class are used as keys in the $search_parameters array, - * their keys may be converted to library-specific search keys. - * - * @param array $search_parameters The search parameters to be used. - * The array must be an associative array where the keys represent - * the fields. - * - * @param string $order_by - * - * @param int $limit The maximum amount of items that shall be retrieved - * from the catalog. - * - * @returns LibrarySearchResult[] An array of LibrarySearchResult items - * if entries matching the search could be found in the library. - */ - abstract public function query( - array $search_parameters = [], - string $order_by = self::ORDER_BY_RELEVANCE, - int $limit = 200 - ) : array; -} diff --git a/lib/classes/librarysearch/LibrarySearch.php b/lib/classes/librarysearch/LibrarySearch.php new file mode 100644 index 0000000..473eacf --- /dev/null +++ b/lib/classes/librarysearch/LibrarySearch.php @@ -0,0 +1,150 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This class contains basic methods for querying a library catalog + * using standardised search parameters. + */ +abstract class LibrarySearch +{ + //The following constants define the strings for the + //standardised field names for the query method. These can be + //converted to library-specific field names. + const TITLE = 'title'; + const AUTHOR = 'author'; + const YEAR = 'year'; + const NUMBER = 'number'; + const ISSN = 'issn'; + const ISBN = 'isbn'; + const PUBLICATION = 'publication'; + const SIGNATURE = 'signature'; + + //Constants for the ordering of results. + const ORDER_BY_RELEVANCE = 'relevance'; + const ORDER_BY_YEAR = 'year'; + + + /** + * The base URL for the HTTP request to retrieve data. + */ + protected $request_base_url = ''; + + + /** + * Additional URL parameters for the HTTP request to retrieve data. + */ + protected $request_url_parameters = []; + + + /** + * Implementation-specific configuration that can define the behavior + * of the LibrarySearch implementation. + */ + protected $settings = []; + + + /** + * A basic constructor. + * + * @param array $configuration The configuration for the LibrarySearch + * implementation. It should be an associative array with the following + * keys: + * - base_url: The base URL for retrieving data. + * - additional_url_parameters: Additional URL parameters for the base URL. + * - settings: Implementation-specific configuration. This should also + * be an associative array. + */ + public function __construct(array $configuration = []) + { + if ($configuration['base_url']) { + $this->request_base_url = $configuration['base_url']; + } + if (is_array($configuration['additional_url_parameters'])) { + $this->request_url_parameters = $configuration['additional_url_parameters']; + } + if (is_array($configuration['settings'])) { + $this->settings = $configuration['settings']; + } + } + + + /** + * This method shall replace the generalised search query fields with the + * implementation specific query fields. + * + * @param array $query_fields An array with query parameters using the + * generalised query fields. + * + * @returns array The translated version of the $query_fields array. + */ + abstract protected function translateQueryFields(array $query_fields = []) : array; + + + /** + * A common method for the libcurl code to request data from an URL so that + * LibrarySearch implementations don't have to include their own libcurl + * code to get data. + * + * @param string $base_url The base URL to request data from. + * + * @param array $url_parameters URL parameters for the request. The array + * should consist of an associative array with keys representing + * the parameter name and the values representing the parameter values. + * + * @returns string|bool The result of the request. If the base URL is empty + * or no data could be retrieved due to an error, false is returned. + * In case of success, a string with the retrieved data is returned. + */ + protected function requestData(string $base_url = '', array $url_parameters = []) + { + if (!$base_url) { + return false; + } + $full_url = $base_url; + if ($url_parameters) { + $full_url .= '?' . http_build_query($url_parameters); + } + + $data = file_get_contents($full_url, false, get_default_http_stream_context($base_url)); + return $data; + } + + + /** + * Starts a query to a library catalogue using the specified + * parameters. If standardised parameters as defined in the FIELD_ + * constants of this class are used as keys in the $search_parameters array, + * their keys may be converted to library-specific search keys. + * + * @param array $search_parameters The search parameters to be used. + * The array must be an associative array where the keys represent + * the fields. + * + * @param string $order_by + * + * @param int $limit The maximum amount of items that shall be retrieved + * from the catalog. + * + * @returns LibrarySearchResult[] An array of LibrarySearchResult items + * if entries matching the search could be found in the library. + */ + abstract public function query( + array $search_parameters = [], + string $order_by = self::ORDER_BY_RELEVANCE, + int $limit = 200 + ) : array; +} diff --git a/lib/classes/librarysearch/LibrarySearchManager.class.php b/lib/classes/librarysearch/LibrarySearchManager.class.php deleted file mode 100644 index ee9dc75..0000000 --- a/lib/classes/librarysearch/LibrarySearchManager.class.php +++ /dev/null @@ -1,174 +0,0 @@ - 0; - } - - - /** - * Starts a search in the configured library catalogs. - * If a local catalog is configured, its results are compared to - * the other catalogs results to identify matches that are available - * in the local catalog. - * - * @param array $search_parameters The search parameters to be used. - * @see The LibrarySearch class for standardised field names. - * - * @param string $order_by The ordering of the search results. - * @see The LibrarySearch class for allowed order names. - * - * @param int $limit The maximum amount of results for each catalog. - * - * @returns LibraryDocument[][] A two-dimensional array of - * LibraryDocument instances where the first dimension represents - * a catalog and the second dimension represents the search results. - * - * @throws Exception If no library catalogs are configured. - */ - public static function search( - array $search_parameters = [], - string $order_by = LibrarySearch::ORDER_BY_RELEVANCE, - int $limit = 100 - ) : array - { - //Get the set of activated library catalogs: - $activated_catalogs = $GLOBALS['LIBRARY_CATALOGS']; - if (!$activated_catalogs) { - throw new Exception( - _('In dieser Stud.IP-Installation sind keine Bibliothekskataloge aktiviert!') - ); - } - - $result_sets = []; - $local_catalog_result_set = []; - foreach ($activated_catalogs as $catalog_data) { - if (is_a($catalog_data['class_name'], 'LibrarySearch', true)) { - $catalog_class = $catalog_data['class_name']; - $catalog_config = [ - 'base_url' => $catalog_data['base_url'], - 'additional_url_parameters' => $catalog_data['additional_url_parameters'], - 'settings' => $catalog_data['settings'] - ]; - $search = new $catalog_class($catalog_config); - if ($catalog_data['local_catalog']) { - $local_catalog_result_set = $search->query( - $search_parameters, - $order_by, - $limit - ); - foreach ($local_catalog_result_set as $result) { - $result->catalog = $catalog_data['name']; - if ($result->csl_data['id']) { - $result->opac_document_id = $result->csl_data['id']; - if (isset($catalog_data['opac_link_template'])) { - $result->opac_link = str_replace( - '{opac_document_id}', - htmlReady($result->opac_document_id), - $catalog_data['opac_link_template'] - ); - } - } - } - } else { - $result_sets[$catalog_class] = $search->query( - $search_parameters, - $order_by, - $limit - ); - foreach ($result_sets[$catalog_class] as $result) { - $result->catalog = $catalog_data['name']; - } - } - } - } - - //Build the sorted result set by rotating over each unsorted - //result set until the end of each result set is reached. - //This way, we get the top results for each catalog as first - //entries in the result set. - //Furthermore, filter out all entries that are already present - //in the local catalog. - $all_empty = false; - $result_c = 0; - $merged_results = []; - $iterators = []; - foreach ($result_sets as $set) { - $iterators[] = new ArrayIterator($set); - } - if (count($local_catalog_result_set)) { - foreach ($local_catalog_result_set as $result) { - $result->search_params = $search_parameters; - $merged_results[$result->getId()] = $result; - } - } - while (!$all_empty) { - $all_empty = true; - foreach ($iterators as $iterator) { - $result = $iterator->current(); - if ($result instanceof LibraryDocument) { - $result_c++; - $all_empty = false; - $found_in_local_catalog = false; - foreach ($local_catalog_result_set as $key => $local_result) { - if ($local_result->isEqualTo($result)) { - //The result is in the local catalog. - unset($local_catalog_result_set[$key]); - $found_in_local_catalog = true; - break; - } - } - if (!$found_in_local_catalog) { - //Store the result in the cache. - //We need it in the create_library action. - //Put the search parameters into the result before adding it - //to the cache: - $result->search_params = $search_parameters; - $merged_results[$result->getId()] = $result; - } - } - if ($iterator->valid()) { - $all_empty = false; - $iterator->next(); - } - } - } - - - //At this point, the search results are sorted. - return $merged_results; - } -} diff --git a/lib/classes/librarysearch/LibrarySearchManager.php b/lib/classes/librarysearch/LibrarySearchManager.php new file mode 100644 index 0000000..ee9dc75 --- /dev/null +++ b/lib/classes/librarysearch/LibrarySearchManager.php @@ -0,0 +1,174 @@ + 0; + } + + + /** + * Starts a search in the configured library catalogs. + * If a local catalog is configured, its results are compared to + * the other catalogs results to identify matches that are available + * in the local catalog. + * + * @param array $search_parameters The search parameters to be used. + * @see The LibrarySearch class for standardised field names. + * + * @param string $order_by The ordering of the search results. + * @see The LibrarySearch class for allowed order names. + * + * @param int $limit The maximum amount of results for each catalog. + * + * @returns LibraryDocument[][] A two-dimensional array of + * LibraryDocument instances where the first dimension represents + * a catalog and the second dimension represents the search results. + * + * @throws Exception If no library catalogs are configured. + */ + public static function search( + array $search_parameters = [], + string $order_by = LibrarySearch::ORDER_BY_RELEVANCE, + int $limit = 100 + ) : array + { + //Get the set of activated library catalogs: + $activated_catalogs = $GLOBALS['LIBRARY_CATALOGS']; + if (!$activated_catalogs) { + throw new Exception( + _('In dieser Stud.IP-Installation sind keine Bibliothekskataloge aktiviert!') + ); + } + + $result_sets = []; + $local_catalog_result_set = []; + foreach ($activated_catalogs as $catalog_data) { + if (is_a($catalog_data['class_name'], 'LibrarySearch', true)) { + $catalog_class = $catalog_data['class_name']; + $catalog_config = [ + 'base_url' => $catalog_data['base_url'], + 'additional_url_parameters' => $catalog_data['additional_url_parameters'], + 'settings' => $catalog_data['settings'] + ]; + $search = new $catalog_class($catalog_config); + if ($catalog_data['local_catalog']) { + $local_catalog_result_set = $search->query( + $search_parameters, + $order_by, + $limit + ); + foreach ($local_catalog_result_set as $result) { + $result->catalog = $catalog_data['name']; + if ($result->csl_data['id']) { + $result->opac_document_id = $result->csl_data['id']; + if (isset($catalog_data['opac_link_template'])) { + $result->opac_link = str_replace( + '{opac_document_id}', + htmlReady($result->opac_document_id), + $catalog_data['opac_link_template'] + ); + } + } + } + } else { + $result_sets[$catalog_class] = $search->query( + $search_parameters, + $order_by, + $limit + ); + foreach ($result_sets[$catalog_class] as $result) { + $result->catalog = $catalog_data['name']; + } + } + } + } + + //Build the sorted result set by rotating over each unsorted + //result set until the end of each result set is reached. + //This way, we get the top results for each catalog as first + //entries in the result set. + //Furthermore, filter out all entries that are already present + //in the local catalog. + $all_empty = false; + $result_c = 0; + $merged_results = []; + $iterators = []; + foreach ($result_sets as $set) { + $iterators[] = new ArrayIterator($set); + } + if (count($local_catalog_result_set)) { + foreach ($local_catalog_result_set as $result) { + $result->search_params = $search_parameters; + $merged_results[$result->getId()] = $result; + } + } + while (!$all_empty) { + $all_empty = true; + foreach ($iterators as $iterator) { + $result = $iterator->current(); + if ($result instanceof LibraryDocument) { + $result_c++; + $all_empty = false; + $found_in_local_catalog = false; + foreach ($local_catalog_result_set as $key => $local_result) { + if ($local_result->isEqualTo($result)) { + //The result is in the local catalog. + unset($local_catalog_result_set[$key]); + $found_in_local_catalog = true; + break; + } + } + if (!$found_in_local_catalog) { + //Store the result in the cache. + //We need it in the create_library action. + //Put the search parameters into the result before adding it + //to the cache: + $result->search_params = $search_parameters; + $merged_results[$result->getId()] = $result; + } + } + if ($iterator->valid()) { + $all_empty = false; + $iterator->next(); + } + } + } + + + //At this point, the search results are sorted. + return $merged_results; + } +} diff --git a/lib/classes/librarysearch/resultparsers/BASELibraryResultParser.class.php b/lib/classes/librarysearch/resultparsers/BASELibraryResultParser.class.php deleted file mode 100644 index b92628f..0000000 --- a/lib/classes/librarysearch/resultparsers/BASELibraryResultParser.class.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This is a LibraryResultParser implementation for the BASE catalog. - * - * @see LibraryResultParser - */ -class BASELibraryResultParser implements LibraryResultParser -{ - /** - * This is a helper method to index the child nodes of a node which makes - * accessing them easier. The index is like the "name" attribute - * of the DOM child node. - * - * @param DOMElement $node The node whose child nodes shall be indexed. - * - * @returns DOMElement[] An array of DOMElement nodes reperesenting - * the indexed child nodes of the supplied node. - */ - protected function indexChildren(\DOMElement $node) - { - $indexed_children = []; - $children = $node->childNodes; - foreach ($children as $child) { - $index = $child->getAttribute('name'); - $indexed_children[$index] = $child; - } - return $indexed_children; - } - - - /** - * Reads the XML nodes of one record and creates a LibraryDocument - * out of it. - * - * @param DOMElement $node The XML node of one record. - * - * @returns LibraryDocument The LibraryDocument instance that could be read - * from the data. - */ - protected function readXMLRecord(\DOMElement $node) : LibraryDocument - { - $result = new LibraryDocument(); - - $children = $this->indexChildren($node); - - foreach ($children as $name => $child) { - if ($child instanceof \DOMElement) { - if ($name == 'dctypenorm') { - //Set the document type by the value of this field. - $base_typeid = trim($child->textContent); - if ($base_typeid == '11' || $base_typeid == '111') { - $result->type = 'book'; - } elseif ($base_typeid == '12' || $base_typeid == '121' || $base_typeid == '122') { - $result->type = 'article'; - } elseif ($base_typeid == '13') { - $result->type = 'paper-conference'; - } elseif ($base_typeid == '14') { - $result->type = 'report'; - } else { - $result->type = $base_typeid; - } - } elseif ($name == 'dctitle') { - $result->csl_data['title'] = $child->textContent; - } elseif ($name == 'dccreator') { - $authors = $child->getElementsByTagName('str'); - $csl_authors = []; - foreach ($authors as $author) { - $author_names = explode(', ', $author->textContent); - $csl_authors[] = [ - 'family' => $author_names[0], - 'given' => $author_names[1], - 'suffix' => '' - ]; - } - $result->csl_data['author'] = $csl_authors; - } elseif ($name == 'dcdate') { - $date_and_time = explode('T', $child->textContent); - $date_parts = explode('-', $date_and_time[0]); - $result->csl_data['issued'] = ['date-parts' => [$date_parts]]; - } elseif (($name == 'dcyear') && !($result->csl_data['issued'])) { - $result->csl_data['issued'] = ['date-parts' => [$child->textContent]]; - } elseif ($name == 'dcdescription') { - $result->csl_data['abstract'] = $child->textContent; - } elseif ($name == 'dcpublisher') { - $str = $child->childNodes[0]; - if ($str instanceof \DOMElement) { - $result->csl_data['publisher'] = $str->textContent; - } - } elseif ($name == 'dclink') { - $result->csl_data['URL'] = $child->textContent; - } elseif ($name == 'dcdocid') { - $result->csl_data['DOI'] = $child->textContent; - } else { - //All other field values are stored in the datafields array. - $result->datafields[$name] = $child->textContent; - } - } - } - return $result; - } - - - /** - * @see LibraryResultParser::readResultSet - */ - public function readResultSet($data = '') : array - { - if (!$data) { - return []; - } - $dom = new \DOMDocument(); - @$dom->loadXML($data); - $result = $dom->getElementsByTagName('response')[0]; - if (!$result) { - //Wrong document type. - return []; - } - - $documents = $result->getElementsByTagName('doc'); - $result_set = []; - foreach ($documents as $document) { - $result_set[] = $this->readXMLRecord($document); - } - return $result_set; - } - - - /** - * @see LibraryResultParser::readRecord - */ - public function readRecord($data = ''): LibraryDocument - { - $dom = new DOMDocument(); - @$dom->loadXML($data); - $record = $dom->getElementsByTagName('doc')[0]; - if ($record) { - return $this->readXMLRecord($record); - } - - throw new Exception('Could not read record'); - } -} diff --git a/lib/classes/librarysearch/resultparsers/BASELibraryResultParser.php b/lib/classes/librarysearch/resultparsers/BASELibraryResultParser.php new file mode 100644 index 0000000..b92628f --- /dev/null +++ b/lib/classes/librarysearch/resultparsers/BASELibraryResultParser.php @@ -0,0 +1,158 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This is a LibraryResultParser implementation for the BASE catalog. + * + * @see LibraryResultParser + */ +class BASELibraryResultParser implements LibraryResultParser +{ + /** + * This is a helper method to index the child nodes of a node which makes + * accessing them easier. The index is like the "name" attribute + * of the DOM child node. + * + * @param DOMElement $node The node whose child nodes shall be indexed. + * + * @returns DOMElement[] An array of DOMElement nodes reperesenting + * the indexed child nodes of the supplied node. + */ + protected function indexChildren(\DOMElement $node) + { + $indexed_children = []; + $children = $node->childNodes; + foreach ($children as $child) { + $index = $child->getAttribute('name'); + $indexed_children[$index] = $child; + } + return $indexed_children; + } + + + /** + * Reads the XML nodes of one record and creates a LibraryDocument + * out of it. + * + * @param DOMElement $node The XML node of one record. + * + * @returns LibraryDocument The LibraryDocument instance that could be read + * from the data. + */ + protected function readXMLRecord(\DOMElement $node) : LibraryDocument + { + $result = new LibraryDocument(); + + $children = $this->indexChildren($node); + + foreach ($children as $name => $child) { + if ($child instanceof \DOMElement) { + if ($name == 'dctypenorm') { + //Set the document type by the value of this field. + $base_typeid = trim($child->textContent); + if ($base_typeid == '11' || $base_typeid == '111') { + $result->type = 'book'; + } elseif ($base_typeid == '12' || $base_typeid == '121' || $base_typeid == '122') { + $result->type = 'article'; + } elseif ($base_typeid == '13') { + $result->type = 'paper-conference'; + } elseif ($base_typeid == '14') { + $result->type = 'report'; + } else { + $result->type = $base_typeid; + } + } elseif ($name == 'dctitle') { + $result->csl_data['title'] = $child->textContent; + } elseif ($name == 'dccreator') { + $authors = $child->getElementsByTagName('str'); + $csl_authors = []; + foreach ($authors as $author) { + $author_names = explode(', ', $author->textContent); + $csl_authors[] = [ + 'family' => $author_names[0], + 'given' => $author_names[1], + 'suffix' => '' + ]; + } + $result->csl_data['author'] = $csl_authors; + } elseif ($name == 'dcdate') { + $date_and_time = explode('T', $child->textContent); + $date_parts = explode('-', $date_and_time[0]); + $result->csl_data['issued'] = ['date-parts' => [$date_parts]]; + } elseif (($name == 'dcyear') && !($result->csl_data['issued'])) { + $result->csl_data['issued'] = ['date-parts' => [$child->textContent]]; + } elseif ($name == 'dcdescription') { + $result->csl_data['abstract'] = $child->textContent; + } elseif ($name == 'dcpublisher') { + $str = $child->childNodes[0]; + if ($str instanceof \DOMElement) { + $result->csl_data['publisher'] = $str->textContent; + } + } elseif ($name == 'dclink') { + $result->csl_data['URL'] = $child->textContent; + } elseif ($name == 'dcdocid') { + $result->csl_data['DOI'] = $child->textContent; + } else { + //All other field values are stored in the datafields array. + $result->datafields[$name] = $child->textContent; + } + } + } + return $result; + } + + + /** + * @see LibraryResultParser::readResultSet + */ + public function readResultSet($data = '') : array + { + if (!$data) { + return []; + } + $dom = new \DOMDocument(); + @$dom->loadXML($data); + $result = $dom->getElementsByTagName('response')[0]; + if (!$result) { + //Wrong document type. + return []; + } + + $documents = $result->getElementsByTagName('doc'); + $result_set = []; + foreach ($documents as $document) { + $result_set[] = $this->readXMLRecord($document); + } + return $result_set; + } + + + /** + * @see LibraryResultParser::readRecord + */ + public function readRecord($data = ''): LibraryDocument + { + $dom = new DOMDocument(); + @$dom->loadXML($data); + $record = $dom->getElementsByTagName('doc')[0]; + if ($record) { + return $this->readXMLRecord($record); + } + + throw new Exception('Could not read record'); + } +} diff --git a/lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.class.php b/lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.class.php deleted file mode 100644 index b4b3a20..0000000 --- a/lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.class.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This is a LibraryResultParser implementation for the K10PlusZentral catalog. - * - * @see LibraryResultParser - */ -class K10PlusLibraryResultParser implements LibraryResultParser -{ - /** - * @see LibraryResultParser::readResultSet - */ - public function readResultSet($data = '') : array - { - //Convert the data to JSON: - $json_data = json_decode($data, true); - - $header = $json_data['responseHeader']; - - if ($header['status'] != 0) { - //Probably an error! - return []; - } - - if (!$json_data['response']) { - //JSON structure error! - return []; - } - - if (!$json_data['response']['docs'] || ($json_data['response']['numFound'] == 0)) { - //No data found. - return []; - } - - $result_set = []; - foreach ($json_data['response']['docs'] as $doc_data) { - $result = $this->readRecord($doc_data); - if ($result instanceof LibraryDocument) { - $result_set[] = $result; - } - } - return $result_set; - } - - - /** - * @see LibraryResultParser::readRecord - */ - public function readRecord($data = '') : LibraryDocument - { - $parser = new MarcxmlLibraryResultParser(); - //The result is one marcxml record in a collection: - $result = $parser->readResultSet($data['fullrecord_marcxml'])[0]; - //Now we have to set the type using the data from the JSON fields: - $doc_type = $data['format'][0]; - if (($doc_type == 'Article') || ($doc_type == 'electronic Article')) { - $result->type = 'article'; - } elseif (($doc_type == 'Book') || ($doc_type == 'eBook')) { - $result->type = 'book'; - } elseif ($doc_type == 'journal') { - $result->type = 'article-journal'; - } - if (!isset($result->csl_data['URL'])) { - $result->csl_data['URL'] = $data['url']; - } - $result->filterCslFieldsByType(); - return $result; - } -} diff --git a/lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.php b/lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.php new file mode 100644 index 0000000..b4b3a20 --- /dev/null +++ b/lib/classes/librarysearch/resultparsers/K10PlusLibraryResultParser.php @@ -0,0 +1,84 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This is a LibraryResultParser implementation for the K10PlusZentral catalog. + * + * @see LibraryResultParser + */ +class K10PlusLibraryResultParser implements LibraryResultParser +{ + /** + * @see LibraryResultParser::readResultSet + */ + public function readResultSet($data = '') : array + { + //Convert the data to JSON: + $json_data = json_decode($data, true); + + $header = $json_data['responseHeader']; + + if ($header['status'] != 0) { + //Probably an error! + return []; + } + + if (!$json_data['response']) { + //JSON structure error! + return []; + } + + if (!$json_data['response']['docs'] || ($json_data['response']['numFound'] == 0)) { + //No data found. + return []; + } + + $result_set = []; + foreach ($json_data['response']['docs'] as $doc_data) { + $result = $this->readRecord($doc_data); + if ($result instanceof LibraryDocument) { + $result_set[] = $result; + } + } + return $result_set; + } + + + /** + * @see LibraryResultParser::readRecord + */ + public function readRecord($data = '') : LibraryDocument + { + $parser = new MarcxmlLibraryResultParser(); + //The result is one marcxml record in a collection: + $result = $parser->readResultSet($data['fullrecord_marcxml'])[0]; + //Now we have to set the type using the data from the JSON fields: + $doc_type = $data['format'][0]; + if (($doc_type == 'Article') || ($doc_type == 'electronic Article')) { + $result->type = 'article'; + } elseif (($doc_type == 'Book') || ($doc_type == 'eBook')) { + $result->type = 'book'; + } elseif ($doc_type == 'journal') { + $result->type = 'article-journal'; + } + if (!isset($result->csl_data['URL'])) { + $result->csl_data['URL'] = $data['url']; + } + $result->filterCslFieldsByType(); + return $result; + } +} diff --git a/lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.class.php b/lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.class.php deleted file mode 100644 index 8017f3d..0000000 --- a/lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.class.php +++ /dev/null @@ -1,234 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This is a LibraryResultParser implementation for the BASE catalog. - * - * @see LibraryResultParser - */ -class MarcxmlLibraryResultParser implements LibraryResultParser -{ - private $mapping = [ - '001' => ['field' => 'id', 'callback' => 'simpleMap', 'cb_args' => ''], - '008' => [ - ['field' => 'language', 'callback' => 'simpleFixFieldMap', 'cb_args' => ['start' => 35, 'length' => 3]], - ['field' => 'issued', 'callback' => 'simpleFixFieldMap', 'cb_args' => ['start' => 7, 'length' => 4], 'format' => 'date'] - ], - '020' => ['field' => 'ISBN', 'callback' => 'simpleMap', 'cb_args' => '$a'], - '245' => ['field' => 'title', 'callback' => 'simpleMap', 'cb_args' => '$a $b $h'], - '264' => ['field' => 'publisher', 'callback' => 'simpleMap', 'cb_args' => '$a $b'], - '256' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], - '300' => ['field' => 'medium', 'callback' => 'simpleMap', 'cb_args' => '$a $b $c $e'], - '440' => ['field' => 'container-title', 'callback' => 'simpleMap', 'cb_args' => '$a $v'], - '500' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], - '502' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], - '518' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], - '520' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], - '533' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$n' . "\n"], - '600' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], - '610' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], - '611' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], - '630' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], - '650' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => '$a'], - '651' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], - '652' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], - '653' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], - '773' => [ - ['field' => 'publisher', 'callback' => 'simpleMap', 'cb_args' => '$t, $g, $d'], - ['field' => 'ISSN', 'callback' => 'simpleMap', 'cb_args' => '$x'], - ], - '100' => ['field' => 'author', 'callback' => 'simpleMap', 'cb_args' => '$a', 'format' => 'name'], - '700' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a', 'contributor', '$a;'], 'format' => 'name'], - '110' => ['field' => 'author', 'callback' => 'simpleMap', 'cb_args' => '$a, $b', 'format' => 'name'], - '111' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a, $b', 'contributor', '$a, $b;'], 'format' => 'name'], - '710' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a, $b', 'contributor', '$a, $b;'], 'format' => 'name'], - '711' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a, $b', 'contributor', '$a, $b;'], 'format' => 'name'], - '856' => ['field' => 'URL', 'callback' => 'notEmptyMap', 'cb_args' => ['$u', 'URL2', '$u']], - ]; - - function simpleListMap($doc, $data, $field, $args = [], $format = '') - { - if (is_array($data)) { - $result = join('; ', $data); - } else { - $result = $data; - } - $result = (($doc->csl_data[$field])) ? $doc->csl_data[$field] . '; ' . $result : $result; - $doc->csl_data[$field] = trim($result); - } - - function simpleFixFieldMap($doc, $data, $field, $args = [], $format = '') - { - if (is_array($args) && $data != "") { - if ($result = trim(mb_substr($data, $args['start'], $args['length']))) { - if ($format === 'date') { - $result = ['date-parts' => [[$result, 1, 1]]]; - $doc->csl_data[$field] = $result; - } else { - $doc->csl_data[$field] = trim($doc->csl_data[$field] . " " . $result); - } - } - } - } - - function notEmptyMap($doc, $data, $field, $args, $format = '') - { - if (empty($doc->csl_data[$field])) { - $this->simpleMap($doc, $data, $field, $args[0], $format); - } else { - $this->simpleMap($doc, $data, $args[1], $args[2], $format); - } - return; - } - - function simpleMap($doc, $data, $field, $args, $format = '') - { - $trim_chars = " \t\n\r\0/,:."; - if ($args != "" && is_array($data)) { - foreach ($data as $key => $value) { - $search[] = '$' . $key; - $replace[] = $value; - } - $result = str_replace($search, $replace, $args); - $result = preg_replace('/\$[0-9a-z]\s*/', "", $result); - - } else { - $result = $data; - } - $result = trim($result, $trim_chars); - if ($format == 'name') { - $author_data = explode(', ', $result); - $result = - [ - 'family' => $author_data[0], - 'given' => $author_data[1], - 'suffix' => '' - ]; - - $doc->csl_data[$field][] = $result; - } else { - $doc->csl_data[$field] = trim($doc->csl_data[$field] . " " . $result); - } - } - - /** - * @see LibraryResultParser::readResultSet - */ - public function readResultSet($data = ''): array - { - //echo '
'. htmlready(print_r($data,1)) . '
'; - $dom = new \DOMDocument(); - $dom->loadXML($data); - $collection = $dom->getElementsByTagName('collection')[0]; - if (!$collection) { - //Wrong document type. - return []; - } - - $records = $collection->getElementsByTagName('record'); - - $result_set = []; - foreach ($records as $record) { - $result_set[] = $this->readResultNode($record); - } - return $result_set; - } - - /** - * Reads the XML nodes of one record and creates a LibraryDocument - * out of it. - * - * @param DOMElement $node The XML node of one record. - * - * @returns LibraryDocument The LibraryDocument instance that could be read - * from the data. - */ - public function readResultNode(\DOMElement $node): LibraryDocument - { - $result = new LibraryDocument(); - - - - $xmlrecord = simplexml_import_dom($node); - $plugin_mapping = $this->mapping; - foreach ($xmlrecord->controlfield as $field) { - $code = (string)$field['tag']; - $data = (string)$field; - if (isset($plugin_mapping[$code])) { - $mapping = (is_array($plugin_mapping[$code][0])) ? $plugin_mapping[$code] : [$plugin_mapping[$code]]; - for ($j = 0; $j < count($mapping); ++$j) { - $map_method = $mapping[$j]['callback']; - $this->$map_method($result, $data, $mapping[$j]['field'], $mapping[$j]['cb_args'], $mapping[$j]['format']); - } - } - } - foreach ($xmlrecord->datafield as $field) { - $code = (string)$field['tag']; - $data = []; - foreach ($field->subfield as $subfield) { - $subcode = (string)$subfield['code']; - if ($subcode && !isset($data[$subcode])) { - $data[$subcode] = (string)$subfield; - } - } - if (isset($plugin_mapping[$code])) { - $mapping = (is_array($plugin_mapping[$code][0])) ? $plugin_mapping[$code] : [$plugin_mapping[$code]]; - for ($j = 0; $j < count($mapping); ++$j) { - $map_method = $mapping[$j]['callback']; - $this->$map_method($result, $data, $mapping[$j]['field'], $mapping[$j]['cb_args'], $mapping[$j]['format']); - } - } - } - return $result; - } - - /** - * @see LibraryResultParser::readRecord - */ - public function readRecord($data = ''): LibraryDocument - { - $dom = new DOMDocument(); - $dom->loadXML($data); - $record = $dom->getElementsByTagName('record')[0]; - if ($record instanceof \DOMElement) { - return $this->readResultNode($record); - } - - throw new Exception('Could not read record'); - } - - /** - * This is a helper method to index the child nodes of a node which - * represent the Marc21 subfields. The index is the subfield code - * of the DOM child node. - * - * @param DOMElement $datafield The node whose child nodes shall be indexed. - * - * @returns DOMElement[] An array of DOMElement nodes reperesenting - * the indexed child nodes of the supplied node. - */ - protected function indexSubfields(\DOMElement $datafield): array - { - $indexed_subfields = []; - $subfields = $datafield->getElementsByTagName('subfield'); - foreach ($subfields as $subfield) { - $code = $subfield->getAttribute('code'); - $indexed_subfields[$code] = $subfield; - } - return $indexed_subfields; - } -} diff --git a/lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.php b/lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.php new file mode 100644 index 0000000..8017f3d --- /dev/null +++ b/lib/classes/librarysearch/resultparsers/MarcxmlLibraryResultParser.php @@ -0,0 +1,234 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This is a LibraryResultParser implementation for the BASE catalog. + * + * @see LibraryResultParser + */ +class MarcxmlLibraryResultParser implements LibraryResultParser +{ + private $mapping = [ + '001' => ['field' => 'id', 'callback' => 'simpleMap', 'cb_args' => ''], + '008' => [ + ['field' => 'language', 'callback' => 'simpleFixFieldMap', 'cb_args' => ['start' => 35, 'length' => 3]], + ['field' => 'issued', 'callback' => 'simpleFixFieldMap', 'cb_args' => ['start' => 7, 'length' => 4], 'format' => 'date'] + ], + '020' => ['field' => 'ISBN', 'callback' => 'simpleMap', 'cb_args' => '$a'], + '245' => ['field' => 'title', 'callback' => 'simpleMap', 'cb_args' => '$a $b $h'], + '264' => ['field' => 'publisher', 'callback' => 'simpleMap', 'cb_args' => '$a $b'], + '256' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], + '300' => ['field' => 'medium', 'callback' => 'simpleMap', 'cb_args' => '$a $b $c $e'], + '440' => ['field' => 'container-title', 'callback' => 'simpleMap', 'cb_args' => '$a $v'], + '500' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], + '502' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], + '518' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], + '520' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$a' . "\n"], + '533' => ['field' => 'note', 'callback' => 'simpleMap', 'cb_args' => '$n' . "\n"], + '600' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], + '610' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], + '611' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], + '630' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], + '650' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => '$a'], + '651' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], + '652' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], + '653' => ['field' => 'note', 'callback' => 'simpleListMap', 'cb_args' => false], + '773' => [ + ['field' => 'publisher', 'callback' => 'simpleMap', 'cb_args' => '$t, $g, $d'], + ['field' => 'ISSN', 'callback' => 'simpleMap', 'cb_args' => '$x'], + ], + '100' => ['field' => 'author', 'callback' => 'simpleMap', 'cb_args' => '$a', 'format' => 'name'], + '700' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a', 'contributor', '$a;'], 'format' => 'name'], + '110' => ['field' => 'author', 'callback' => 'simpleMap', 'cb_args' => '$a, $b', 'format' => 'name'], + '111' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a, $b', 'contributor', '$a, $b;'], 'format' => 'name'], + '710' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a, $b', 'contributor', '$a, $b;'], 'format' => 'name'], + '711' => ['field' => 'author', 'callback' => 'notEmptyMap', 'cb_args' => ['$a, $b', 'contributor', '$a, $b;'], 'format' => 'name'], + '856' => ['field' => 'URL', 'callback' => 'notEmptyMap', 'cb_args' => ['$u', 'URL2', '$u']], + ]; + + function simpleListMap($doc, $data, $field, $args = [], $format = '') + { + if (is_array($data)) { + $result = join('; ', $data); + } else { + $result = $data; + } + $result = (($doc->csl_data[$field])) ? $doc->csl_data[$field] . '; ' . $result : $result; + $doc->csl_data[$field] = trim($result); + } + + function simpleFixFieldMap($doc, $data, $field, $args = [], $format = '') + { + if (is_array($args) && $data != "") { + if ($result = trim(mb_substr($data, $args['start'], $args['length']))) { + if ($format === 'date') { + $result = ['date-parts' => [[$result, 1, 1]]]; + $doc->csl_data[$field] = $result; + } else { + $doc->csl_data[$field] = trim($doc->csl_data[$field] . " " . $result); + } + } + } + } + + function notEmptyMap($doc, $data, $field, $args, $format = '') + { + if (empty($doc->csl_data[$field])) { + $this->simpleMap($doc, $data, $field, $args[0], $format); + } else { + $this->simpleMap($doc, $data, $args[1], $args[2], $format); + } + return; + } + + function simpleMap($doc, $data, $field, $args, $format = '') + { + $trim_chars = " \t\n\r\0/,:."; + if ($args != "" && is_array($data)) { + foreach ($data as $key => $value) { + $search[] = '$' . $key; + $replace[] = $value; + } + $result = str_replace($search, $replace, $args); + $result = preg_replace('/\$[0-9a-z]\s*/', "", $result); + + } else { + $result = $data; + } + $result = trim($result, $trim_chars); + if ($format == 'name') { + $author_data = explode(', ', $result); + $result = + [ + 'family' => $author_data[0], + 'given' => $author_data[1], + 'suffix' => '' + ]; + + $doc->csl_data[$field][] = $result; + } else { + $doc->csl_data[$field] = trim($doc->csl_data[$field] . " " . $result); + } + } + + /** + * @see LibraryResultParser::readResultSet + */ + public function readResultSet($data = ''): array + { + //echo '
'. htmlready(print_r($data,1)) . '
'; + $dom = new \DOMDocument(); + $dom->loadXML($data); + $collection = $dom->getElementsByTagName('collection')[0]; + if (!$collection) { + //Wrong document type. + return []; + } + + $records = $collection->getElementsByTagName('record'); + + $result_set = []; + foreach ($records as $record) { + $result_set[] = $this->readResultNode($record); + } + return $result_set; + } + + /** + * Reads the XML nodes of one record and creates a LibraryDocument + * out of it. + * + * @param DOMElement $node The XML node of one record. + * + * @returns LibraryDocument The LibraryDocument instance that could be read + * from the data. + */ + public function readResultNode(\DOMElement $node): LibraryDocument + { + $result = new LibraryDocument(); + + + + $xmlrecord = simplexml_import_dom($node); + $plugin_mapping = $this->mapping; + foreach ($xmlrecord->controlfield as $field) { + $code = (string)$field['tag']; + $data = (string)$field; + if (isset($plugin_mapping[$code])) { + $mapping = (is_array($plugin_mapping[$code][0])) ? $plugin_mapping[$code] : [$plugin_mapping[$code]]; + for ($j = 0; $j < count($mapping); ++$j) { + $map_method = $mapping[$j]['callback']; + $this->$map_method($result, $data, $mapping[$j]['field'], $mapping[$j]['cb_args'], $mapping[$j]['format']); + } + } + } + foreach ($xmlrecord->datafield as $field) { + $code = (string)$field['tag']; + $data = []; + foreach ($field->subfield as $subfield) { + $subcode = (string)$subfield['code']; + if ($subcode && !isset($data[$subcode])) { + $data[$subcode] = (string)$subfield; + } + } + if (isset($plugin_mapping[$code])) { + $mapping = (is_array($plugin_mapping[$code][0])) ? $plugin_mapping[$code] : [$plugin_mapping[$code]]; + for ($j = 0; $j < count($mapping); ++$j) { + $map_method = $mapping[$j]['callback']; + $this->$map_method($result, $data, $mapping[$j]['field'], $mapping[$j]['cb_args'], $mapping[$j]['format']); + } + } + } + return $result; + } + + /** + * @see LibraryResultParser::readRecord + */ + public function readRecord($data = ''): LibraryDocument + { + $dom = new DOMDocument(); + $dom->loadXML($data); + $record = $dom->getElementsByTagName('record')[0]; + if ($record instanceof \DOMElement) { + return $this->readResultNode($record); + } + + throw new Exception('Could not read record'); + } + + /** + * This is a helper method to index the child nodes of a node which + * represent the Marc21 subfields. The index is the subfield code + * of the DOM child node. + * + * @param DOMElement $datafield The node whose child nodes shall be indexed. + * + * @returns DOMElement[] An array of DOMElement nodes reperesenting + * the indexed child nodes of the supplied node. + */ + protected function indexSubfields(\DOMElement $datafield): array + { + $indexed_subfields = []; + $subfields = $datafield->getElementsByTagName('subfield'); + foreach ($subfields as $subfield) { + $code = $subfield->getAttribute('code'); + $indexed_subfields[$code] = $subfield; + } + return $indexed_subfields; + } +} diff --git a/lib/classes/librarysearch/resultparsers/SRULibraryResultParser.class.php b/lib/classes/librarysearch/resultparsers/SRULibraryResultParser.class.php deleted file mode 100644 index fed92f3..0000000 --- a/lib/classes/librarysearch/resultparsers/SRULibraryResultParser.class.php +++ /dev/null @@ -1,96 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This is a LibraryResultParser implementation for catalogs that - * use SRU. - * - * @see LibraryResultParser - */ -class SRULibraryResultParser implements LibraryResultParser -{ - /** - * @see LibraryResultParser::readResultSet - */ - public function readResultSet($data = '') : array - { - - $dom = new \DOMDocument(); - $dom->loadXML($data); - - $record_schema = $dom->getElementsByTagName('recordSchema')[0]; - if (!$record_schema) { - //The recordSchema element is missing. - //We cannot continue. - return []; - } - if (strpos($record_schema->textContent, 'marcxml') !== false) { - $parser = new MarcxmlLibraryResultParser(); - $result_set = []; - $collection_nodes = $dom->getElementsByTagName('collection'); - if ($collection_nodes->length < 1) { - $collection_nodes = $dom->getElementsByTagName('records'); - } - if ($collection_nodes->length > 0) { - foreach ($collection_nodes as $collection) { - if ($collection instanceof \DOMText) { - //Nothing we can do with text nodes. - continue; - } - foreach ($collection->getElementsByTagName('record') as $record) { - $document = $parser->readResultNode($record); - if ($document->getTitle()) { - $result_set[] = $document; - } - } - } - } - return $result_set; - } - throw new RuntimeException('only recordSchema marcxml implemented'); - } - - - /** - * @see LibraryResultParser::readRecord - */ - public function readRecord($data = ''): LibraryDocument - { - $dom = new \DOMDocument(); - @$dom->loadXML($data); - $record = $dom->getElementsByTagName('zs:record')[0]; - if (!$record) { - //Wrong document type. - throw new Exception('Wrong document type!'); - } - - $record_schema = $record->getElementsByTagName('zs:recordSchema')[0]; - if ($record_schema->textContent == 'marcxml') { - $record_node = $record->getElementsByTagName('record')[0]; - if (!$record_node) { - throw new Exception('Invalid data!'); - } - $parser = new MarcxmlLibraryResultParser(); - $document = $parser->readResultNode($record); - if ($document->getTitle()) { - return $document; - } - } - - throw new Exception('Could not read record'); - } -} diff --git a/lib/classes/librarysearch/resultparsers/SRULibraryResultParser.php b/lib/classes/librarysearch/resultparsers/SRULibraryResultParser.php new file mode 100644 index 0000000..fed92f3 --- /dev/null +++ b/lib/classes/librarysearch/resultparsers/SRULibraryResultParser.php @@ -0,0 +1,96 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This is a LibraryResultParser implementation for catalogs that + * use SRU. + * + * @see LibraryResultParser + */ +class SRULibraryResultParser implements LibraryResultParser +{ + /** + * @see LibraryResultParser::readResultSet + */ + public function readResultSet($data = '') : array + { + + $dom = new \DOMDocument(); + $dom->loadXML($data); + + $record_schema = $dom->getElementsByTagName('recordSchema')[0]; + if (!$record_schema) { + //The recordSchema element is missing. + //We cannot continue. + return []; + } + if (strpos($record_schema->textContent, 'marcxml') !== false) { + $parser = new MarcxmlLibraryResultParser(); + $result_set = []; + $collection_nodes = $dom->getElementsByTagName('collection'); + if ($collection_nodes->length < 1) { + $collection_nodes = $dom->getElementsByTagName('records'); + } + if ($collection_nodes->length > 0) { + foreach ($collection_nodes as $collection) { + if ($collection instanceof \DOMText) { + //Nothing we can do with text nodes. + continue; + } + foreach ($collection->getElementsByTagName('record') as $record) { + $document = $parser->readResultNode($record); + if ($document->getTitle()) { + $result_set[] = $document; + } + } + } + } + return $result_set; + } + throw new RuntimeException('only recordSchema marcxml implemented'); + } + + + /** + * @see LibraryResultParser::readRecord + */ + public function readRecord($data = ''): LibraryDocument + { + $dom = new \DOMDocument(); + @$dom->loadXML($data); + $record = $dom->getElementsByTagName('zs:record')[0]; + if (!$record) { + //Wrong document type. + throw new Exception('Wrong document type!'); + } + + $record_schema = $record->getElementsByTagName('zs:recordSchema')[0]; + if ($record_schema->textContent == 'marcxml') { + $record_node = $record->getElementsByTagName('record')[0]; + if (!$record_node) { + throw new Exception('Invalid data!'); + } + $parser = new MarcxmlLibraryResultParser(); + $document = $parser->readResultNode($record); + if ($document->getTitle()) { + return $document; + } + } + + throw new Exception('Could not read record'); + } +} diff --git a/lib/classes/librarysearch/searchmodules/BASELibrarySearch.class.php b/lib/classes/librarysearch/searchmodules/BASELibrarySearch.class.php deleted file mode 100644 index e0408e7..0000000 --- a/lib/classes/librarysearch/searchmodules/BASELibrarySearch.class.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This is a LibrarySearch implementation for the BASE catalog. - * - * @see LibrarySearch - */ -class BASELibrarySearch extends LibrarySearch -{ - /** - * This array is used to map the general search field names to - * the real field names. - */ - protected static $field_replacements = [ - LibrarySearch::TITLE => 'dctitle', - LibrarySearch::AUTHOR => 'dccreator', - LibrarySearch::YEAR => 'dcyear', - LibrarySearch::NUMBER => 'dcrelation', - LibrarySearch::ISSN => 'dcrelation', //No special ISSN field available. - LibrarySearch::ISBN => 'dcrelation', //No special ISBN field available. - LibrarySearch::PUBLICATION => 'dcpublisher', - LibrarySearch::SIGNATURE => 'dcdocid' - ]; - - - /** - * @see LibrarySearch::translateQueryFields - */ - protected function translateQueryFields(array $query_fields = []) : array - { - if (!$query_fields) { - return []; - } - - $translated_fields = []; - foreach ($query_fields as $key => $value) { - if (in_array($key, array_keys(self::$field_replacements))) { - $new_key = self::$field_replacements[$key]; - $translated_fields[$new_key] = $value; - } else { - $translated_fields[$key] = $value; - } - } - return $translated_fields; - } - - - /** - * @see LibrarySearch::query - */ - public function query( - array $search_parameters = [], - string $order_by = self::ORDER_BY_RELEVANCE, - int $limit = 125 - ) : array - { - if (!$search_parameters) { - return []; - } - - //The standardised parameter names must be converted to module-specific - //search parameter names before being added to the query string. - $search_parameters = $this->translateQueryFields($search_parameters); - $query_string = ''; - foreach ($search_parameters as $key => $value) { - if (!empty($query_string)) { - $query_string .= ' AND '; - } - //TODO: escape colon in data! - $query_string .= sprintf('%1$s:(%2$s)', $key, $value); - } - - $query_parameters = []; - $query_parameters['func'] = 'PerformSearch'; - $query_parameters['coll'] = $this->settings['collection'] - ? $this->settings['collection'] - : 'de'; - $query_parameters['query'] = $query_string; - //Order by relevance is the default in BASE. - //No URL parameter needed in that case. - if ($order_by == self::ORDER_BY_YEAR) { - $query_parameters['sortby'] = 'dcyear desc'; - } - if ($limit > 0) { - //BASE has a limit of 125 items per response. - if ($limit > 125) { - $limit = 125; - } - $query_parameters['hits'] = $limit; - } - $data = $this->requestData($this->request_base_url, $query_parameters); - if ($data === null) { - //There are no data we can retrieve. - return []; - } - $parser = new BASELibraryResultParser(); - return $parser->readResultSet($data); - } -} diff --git a/lib/classes/librarysearch/searchmodules/BASELibrarySearch.php b/lib/classes/librarysearch/searchmodules/BASELibrarySearch.php new file mode 100644 index 0000000..e0408e7 --- /dev/null +++ b/lib/classes/librarysearch/searchmodules/BASELibrarySearch.php @@ -0,0 +1,114 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This is a LibrarySearch implementation for the BASE catalog. + * + * @see LibrarySearch + */ +class BASELibrarySearch extends LibrarySearch +{ + /** + * This array is used to map the general search field names to + * the real field names. + */ + protected static $field_replacements = [ + LibrarySearch::TITLE => 'dctitle', + LibrarySearch::AUTHOR => 'dccreator', + LibrarySearch::YEAR => 'dcyear', + LibrarySearch::NUMBER => 'dcrelation', + LibrarySearch::ISSN => 'dcrelation', //No special ISSN field available. + LibrarySearch::ISBN => 'dcrelation', //No special ISBN field available. + LibrarySearch::PUBLICATION => 'dcpublisher', + LibrarySearch::SIGNATURE => 'dcdocid' + ]; + + + /** + * @see LibrarySearch::translateQueryFields + */ + protected function translateQueryFields(array $query_fields = []) : array + { + if (!$query_fields) { + return []; + } + + $translated_fields = []; + foreach ($query_fields as $key => $value) { + if (in_array($key, array_keys(self::$field_replacements))) { + $new_key = self::$field_replacements[$key]; + $translated_fields[$new_key] = $value; + } else { + $translated_fields[$key] = $value; + } + } + return $translated_fields; + } + + + /** + * @see LibrarySearch::query + */ + public function query( + array $search_parameters = [], + string $order_by = self::ORDER_BY_RELEVANCE, + int $limit = 125 + ) : array + { + if (!$search_parameters) { + return []; + } + + //The standardised parameter names must be converted to module-specific + //search parameter names before being added to the query string. + $search_parameters = $this->translateQueryFields($search_parameters); + $query_string = ''; + foreach ($search_parameters as $key => $value) { + if (!empty($query_string)) { + $query_string .= ' AND '; + } + //TODO: escape colon in data! + $query_string .= sprintf('%1$s:(%2$s)', $key, $value); + } + + $query_parameters = []; + $query_parameters['func'] = 'PerformSearch'; + $query_parameters['coll'] = $this->settings['collection'] + ? $this->settings['collection'] + : 'de'; + $query_parameters['query'] = $query_string; + //Order by relevance is the default in BASE. + //No URL parameter needed in that case. + if ($order_by == self::ORDER_BY_YEAR) { + $query_parameters['sortby'] = 'dcyear desc'; + } + if ($limit > 0) { + //BASE has a limit of 125 items per response. + if ($limit > 125) { + $limit = 125; + } + $query_parameters['hits'] = $limit; + } + $data = $this->requestData($this->request_base_url, $query_parameters); + if ($data === null) { + //There are no data we can retrieve. + return []; + } + $parser = new BASELibraryResultParser(); + return $parser->readResultSet($data); + } +} diff --git a/lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.class.php b/lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.class.php deleted file mode 100644 index 9660cb4..0000000 --- a/lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.class.php +++ /dev/null @@ -1,149 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This is a LibrarySearch implementation for the K10PlusZentral catalog. - * - * @see LibrarySearch - */ -class K10PlusZentralLibrarySearch extends LibrarySearch -{ - /** - * This array is used to map the general search field names to - * the real field names. - */ - protected static $field_replacements = [ - LibrarySearch::TITLE => 'title', - LibrarySearch::AUTHOR => 'author', - LibrarySearch::YEAR => 'publishDate', - LibrarySearch::NUMBER => 'number', - LibrarySearch::ISSN => 'issn', - LibrarySearch::ISBN => 'isbn', - LibrarySearch::PUBLICATION => 'journal', - LibrarySearch::SIGNATURE => 'signature' - ]; - - - /** - * This is a helper method to get LibrarySearch instances from the - * raw result data of the query. - * - * @param string $data Raw query response data. - * - * @returns LibraryDocument[] An array of LibraryDocument instaces that - * could be read. - */ - protected function extractResponseData($data) - { - $parser = new K10PlusLibraryResultParser(); - $result_set = $parser->readResultSet($data); - return $result_set; - } - - - /** - * @see LibrarySearch::translateQueryFields - */ - protected function translateQueryFields(array $query_fields = []) : array - { - if (!$query_fields) { - return []; - } - - $translated_fields = []; - foreach ($query_fields as $key => $value) { - if (in_array($key, array_keys(self::$field_replacements))) { - $new_key = self::$field_replacements[$key]; - $translated_fields[$new_key] = $value; - } else { - $translated_fields[$key] = $value; - } - } - return $translated_fields; - } - - - /** - * @see LibrarySearch::query - */ - public function query( - array $search_parameters = [], - string $order_by = self::ORDER_BY_RELEVANCE, - int $limit = 200 - ) : array - { - if (!$search_parameters) { - return []; - } - - //The standardised parameter names must be converted to module-specific - //search parameter names before being added to the query string. - $search_parameters = $this->translateQueryFields($search_parameters); - $query_string = ''; - - foreach ($search_parameters as $key => $value) { - if (!empty($query_string)) { - $query_string .= ' AND '; - } - if ($key == self::$field_replacements[LibrarySearch::NUMBER]) { - $query_string .= sprintf( - '(%1$s:"%2$s" OR %3$s:"%2$s")', - self::$field_replacements[LibrarySearch::ISSN], - $this->escapeQueryChars($value), - self::$field_replacements[LibrarySearch::ISBN] - ); - } else { - //TODO: escape colon in data! - $value = '(' . $this->escapeQueryChars($value) . ')'; - $query_string .= sprintf('%1$s:%2$s', $key, $value); - } - } - - $query_parameters = []; - //Special handling for the query parameter: - $query_parameters['q'] = $query_string; - if ($order_by = self::ORDER_BY_YEAR) { - $query_parameters['sort'] = 'publishDate desc'; - } else { - $query_parameters['sort'] = 'score desc'; - } - if ($limit > 0) { - $query_parameters['rows'] = $limit; - } - //$query_parameters['debug'] = 'query'; - $query_parameters['df'] = self::$field_replacements[LibrarySearch::TITLE]; - $data = $this->requestData($this->request_base_url, $query_parameters); - if ($data === null) { - //There are no data we can retrieve. - return []; - } - $result_objects = $this->extractResponseData($data); - return $result_objects; - } - - public function escapeQueryChars($str) - { - $reserved = preg_quote('+-&|!(){}[]^"~*?:\\'); - return preg_replace_callback( - '/([' . $reserved . '])/', - function ($matches) { - return '\\' . $matches[0]; - }, - $str - ); - } -} diff --git a/lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.php b/lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.php new file mode 100644 index 0000000..9660cb4 --- /dev/null +++ b/lib/classes/librarysearch/searchmodules/K10PlusZentralLibrarySearch.php @@ -0,0 +1,149 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This is a LibrarySearch implementation for the K10PlusZentral catalog. + * + * @see LibrarySearch + */ +class K10PlusZentralLibrarySearch extends LibrarySearch +{ + /** + * This array is used to map the general search field names to + * the real field names. + */ + protected static $field_replacements = [ + LibrarySearch::TITLE => 'title', + LibrarySearch::AUTHOR => 'author', + LibrarySearch::YEAR => 'publishDate', + LibrarySearch::NUMBER => 'number', + LibrarySearch::ISSN => 'issn', + LibrarySearch::ISBN => 'isbn', + LibrarySearch::PUBLICATION => 'journal', + LibrarySearch::SIGNATURE => 'signature' + ]; + + + /** + * This is a helper method to get LibrarySearch instances from the + * raw result data of the query. + * + * @param string $data Raw query response data. + * + * @returns LibraryDocument[] An array of LibraryDocument instaces that + * could be read. + */ + protected function extractResponseData($data) + { + $parser = new K10PlusLibraryResultParser(); + $result_set = $parser->readResultSet($data); + return $result_set; + } + + + /** + * @see LibrarySearch::translateQueryFields + */ + protected function translateQueryFields(array $query_fields = []) : array + { + if (!$query_fields) { + return []; + } + + $translated_fields = []; + foreach ($query_fields as $key => $value) { + if (in_array($key, array_keys(self::$field_replacements))) { + $new_key = self::$field_replacements[$key]; + $translated_fields[$new_key] = $value; + } else { + $translated_fields[$key] = $value; + } + } + return $translated_fields; + } + + + /** + * @see LibrarySearch::query + */ + public function query( + array $search_parameters = [], + string $order_by = self::ORDER_BY_RELEVANCE, + int $limit = 200 + ) : array + { + if (!$search_parameters) { + return []; + } + + //The standardised parameter names must be converted to module-specific + //search parameter names before being added to the query string. + $search_parameters = $this->translateQueryFields($search_parameters); + $query_string = ''; + + foreach ($search_parameters as $key => $value) { + if (!empty($query_string)) { + $query_string .= ' AND '; + } + if ($key == self::$field_replacements[LibrarySearch::NUMBER]) { + $query_string .= sprintf( + '(%1$s:"%2$s" OR %3$s:"%2$s")', + self::$field_replacements[LibrarySearch::ISSN], + $this->escapeQueryChars($value), + self::$field_replacements[LibrarySearch::ISBN] + ); + } else { + //TODO: escape colon in data! + $value = '(' . $this->escapeQueryChars($value) . ')'; + $query_string .= sprintf('%1$s:%2$s', $key, $value); + } + } + + $query_parameters = []; + //Special handling for the query parameter: + $query_parameters['q'] = $query_string; + if ($order_by = self::ORDER_BY_YEAR) { + $query_parameters['sort'] = 'publishDate desc'; + } else { + $query_parameters['sort'] = 'score desc'; + } + if ($limit > 0) { + $query_parameters['rows'] = $limit; + } + //$query_parameters['debug'] = 'query'; + $query_parameters['df'] = self::$field_replacements[LibrarySearch::TITLE]; + $data = $this->requestData($this->request_base_url, $query_parameters); + if ($data === null) { + //There are no data we can retrieve. + return []; + } + $result_objects = $this->extractResponseData($data); + return $result_objects; + } + + public function escapeQueryChars($str) + { + $reserved = preg_quote('+-&|!(){}[]^"~*?:\\'); + return preg_replace_callback( + '/([' . $reserved . '])/', + function ($matches) { + return '\\' . $matches[0]; + }, + $str + ); + } +} diff --git a/lib/classes/librarysearch/searchmodules/SRULibrarySearch.class.php b/lib/classes/librarysearch/searchmodules/SRULibrarySearch.class.php deleted file mode 100644 index b94869a..0000000 --- a/lib/classes/librarysearch/searchmodules/SRULibrarySearch.class.php +++ /dev/null @@ -1,153 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -/** - * This is a LibrarySearch implementation for catalogs that use SRU. - * - * @see LibrarySearch - */ -class SRULibrarySearch extends LibrarySearch -{ - /** - * This array is used to map the general search field names to - * the real field names. - */ - protected static $field_replacements = [ - 'pica' => [ - LibrarySearch::TITLE => 'pica.tit', - LibrarySearch::AUTHOR => 'pica.per', - LibrarySearch::YEAR => 'pica.jhr', - LibrarySearch::NUMBER => 'pica.num', - LibrarySearch::ISBN => 'pica.isb', - LibrarySearch::ISSN => 'pica.iss', - LibrarySearch::PUBLICATION => 'pica.gti', - LibrarySearch::SIGNATURE => 'pica.sga' - ], - 'cql' => [ - LibrarySearch::TITLE => 'dc.title', - LibrarySearch::AUTHOR => 'dc.creator', - LibrarySearch::YEAR => 'dc.date', - LibrarySearch::NUMBER => 'dc.identifier', - LibrarySearch::ISBN => 'dc.identifier', - LibrarySearch::ISSN => 'dc.identifier' - ] - ]; - - - /** - * @see LibrarySearch::translateQueryFields - */ - protected function translateQueryFields(array $query_fields = []) : array - { - if (!$query_fields) { - return []; - } - - $query_format = 'pica'; - if ($this->settings['query_format'] == 'cql') { - $query_format = 'cql'; - } - - $translated_fields = []; - foreach ($query_fields as $key => $value) { - if (in_array($key, array_keys(self::$field_replacements[$query_format]))) { - $new_key = self::$field_replacements[$query_format][$key]; - $translated_fields[$new_key] = $value; - } else { - $translated_fields[$key] = $value; - } - } - return $translated_fields; - } - - - /** - * @see LibrarySearch::query - */ - public function query( - array $search_parameters = [], - string $order_by = self::ORDER_BY_RELEVANCE, - int $limit = 200 - ) : array - { - if (!$search_parameters) { - return []; - } - - //The standardised parameter names must be converted to module-specific - //search parameter names before being added to the query string. - $search_parameters = $this->translateQueryFields($search_parameters); - $query_string = ''; - $query_format = 'pica'; - if ($this->settings['query_format'] == 'cql') { - $query_format = 'cql'; - } - foreach ($search_parameters as $key => $value) { - if (!empty($query_string)) { - $query_string .= ' and '; - } - if ($key == self::$field_replacements[$query_format][LibrarySearch::NUMBER]) { - $query_string .= sprintf( - '(%1$s="%2$s" or %3$s="%2$s" or %4$s="%2$s")', - self::$field_replacements[$query_format][LibrarySearch::ISBN], - addslashes($value), - self::$field_replacements[$query_format][LibrarySearch::ISSN], - self::$field_replacements[$query_format][LibrarySearch::NUMBER] - ); - } else { - //TODO: escape colon in data! - $query_string .= sprintf('%1$s="%2$s"', $key, addslashes($value)); - } - } - - $query_parameters = []; - $query_parameters['version'] = '1.1'; //TODO: is version 2.0 supported? - $query_parameters['operation'] = 'searchRetrieve'; - $query_parameters['recordSchema'] = 'marcxml'; - if ($this->settings['sru_version'] == '1.2') { - $query_parameters['version'] = '1.2'; - //Use SRU/SRW 1.2 - if ($order_by == self::ORDER_BY_RELEVANCE) { - if ($query_format != 'cql') { - $query_string .= ' sortby relevance/descending'; - } - } elseif ($order_by == self::ORDER_BY_YEAR) { - if ($query_format == 'cql') { - $query_string .= ' sortBy dc.date/sort.descending'; - } else { - $query_string .= ' sortby year/descending'; - } - } - } else { - //Use SRU/SRW 1.1 - if ($order_by == self::ORDER_BY_RELEVANCE) { - $query_parameters['sortKeys'] = 'relevance,,0'; - } elseif ($order_by == self::ORDER_BY_YEAR) { - $query_parameters['sortKeys'] = 'year,,0'; - } - } - $query_parameters['maximumRecords'] = $limit; - $query_parameters['query'] = $query_string; - $data = $this->requestData($this->request_base_url, $query_parameters); - if (!$data) { - //There are no data we can retrieve. - return []; - } - $parser = new SRULibraryResultParser(); - return $parser->readResultSet($data); - } -} diff --git a/lib/classes/librarysearch/searchmodules/SRULibrarySearch.php b/lib/classes/librarysearch/searchmodules/SRULibrarySearch.php new file mode 100644 index 0000000..b94869a --- /dev/null +++ b/lib/classes/librarysearch/searchmodules/SRULibrarySearch.php @@ -0,0 +1,153 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +/** + * This is a LibrarySearch implementation for catalogs that use SRU. + * + * @see LibrarySearch + */ +class SRULibrarySearch extends LibrarySearch +{ + /** + * This array is used to map the general search field names to + * the real field names. + */ + protected static $field_replacements = [ + 'pica' => [ + LibrarySearch::TITLE => 'pica.tit', + LibrarySearch::AUTHOR => 'pica.per', + LibrarySearch::YEAR => 'pica.jhr', + LibrarySearch::NUMBER => 'pica.num', + LibrarySearch::ISBN => 'pica.isb', + LibrarySearch::ISSN => 'pica.iss', + LibrarySearch::PUBLICATION => 'pica.gti', + LibrarySearch::SIGNATURE => 'pica.sga' + ], + 'cql' => [ + LibrarySearch::TITLE => 'dc.title', + LibrarySearch::AUTHOR => 'dc.creator', + LibrarySearch::YEAR => 'dc.date', + LibrarySearch::NUMBER => 'dc.identifier', + LibrarySearch::ISBN => 'dc.identifier', + LibrarySearch::ISSN => 'dc.identifier' + ] + ]; + + + /** + * @see LibrarySearch::translateQueryFields + */ + protected function translateQueryFields(array $query_fields = []) : array + { + if (!$query_fields) { + return []; + } + + $query_format = 'pica'; + if ($this->settings['query_format'] == 'cql') { + $query_format = 'cql'; + } + + $translated_fields = []; + foreach ($query_fields as $key => $value) { + if (in_array($key, array_keys(self::$field_replacements[$query_format]))) { + $new_key = self::$field_replacements[$query_format][$key]; + $translated_fields[$new_key] = $value; + } else { + $translated_fields[$key] = $value; + } + } + return $translated_fields; + } + + + /** + * @see LibrarySearch::query + */ + public function query( + array $search_parameters = [], + string $order_by = self::ORDER_BY_RELEVANCE, + int $limit = 200 + ) : array + { + if (!$search_parameters) { + return []; + } + + //The standardised parameter names must be converted to module-specific + //search parameter names before being added to the query string. + $search_parameters = $this->translateQueryFields($search_parameters); + $query_string = ''; + $query_format = 'pica'; + if ($this->settings['query_format'] == 'cql') { + $query_format = 'cql'; + } + foreach ($search_parameters as $key => $value) { + if (!empty($query_string)) { + $query_string .= ' and '; + } + if ($key == self::$field_replacements[$query_format][LibrarySearch::NUMBER]) { + $query_string .= sprintf( + '(%1$s="%2$s" or %3$s="%2$s" or %4$s="%2$s")', + self::$field_replacements[$query_format][LibrarySearch::ISBN], + addslashes($value), + self::$field_replacements[$query_format][LibrarySearch::ISSN], + self::$field_replacements[$query_format][LibrarySearch::NUMBER] + ); + } else { + //TODO: escape colon in data! + $query_string .= sprintf('%1$s="%2$s"', $key, addslashes($value)); + } + } + + $query_parameters = []; + $query_parameters['version'] = '1.1'; //TODO: is version 2.0 supported? + $query_parameters['operation'] = 'searchRetrieve'; + $query_parameters['recordSchema'] = 'marcxml'; + if ($this->settings['sru_version'] == '1.2') { + $query_parameters['version'] = '1.2'; + //Use SRU/SRW 1.2 + if ($order_by == self::ORDER_BY_RELEVANCE) { + if ($query_format != 'cql') { + $query_string .= ' sortby relevance/descending'; + } + } elseif ($order_by == self::ORDER_BY_YEAR) { + if ($query_format == 'cql') { + $query_string .= ' sortBy dc.date/sort.descending'; + } else { + $query_string .= ' sortby year/descending'; + } + } + } else { + //Use SRU/SRW 1.1 + if ($order_by == self::ORDER_BY_RELEVANCE) { + $query_parameters['sortKeys'] = 'relevance,,0'; + } elseif ($order_by == self::ORDER_BY_YEAR) { + $query_parameters['sortKeys'] = 'year,,0'; + } + } + $query_parameters['maximumRecords'] = $limit; + $query_parameters['query'] = $query_string; + $data = $this->requestData($this->request_base_url, $query_parameters); + if (!$data) { + //There are no data we can retrieve. + return []; + } + $parser = new SRULibraryResultParser(); + return $parser->readResultSet($data); + } +} diff --git a/lib/classes/searchtypes/MyCoursesSearch.class.php b/lib/classes/searchtypes/MyCoursesSearch.class.php deleted file mode 100644 index 64bee64..0000000 --- a/lib/classes/searchtypes/MyCoursesSearch.class.php +++ /dev/null @@ -1,220 +0,0 @@ - - * @copyright 2015 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -class MyCoursesSearch extends StandardSearch -{ - private $perm_level; - private $parameters; - protected $additional_sql_conditions; - - /** - * - * @param string $search - * - * @param string $perm_level - * - * @param string $additional_sql_conditions An additional SQL snippet - * consisting of conditions. This snippet is appended to the - * default conditions. - * - * @return void - */ - public function __construct($search, $perm_level = 'dozent', $parameters = [], $additional_sql_conditions = '') - { - parent::__construct($search); - - $this->perm_level = $perm_level; - $this->parameters = $parameters; - $this->additional_sql_conditions = trim($additional_sql_conditions); - } - - - /** - * returns the title/description of the searchfield - * - * @return string title/description - */ - public function getTitle() - { - return _('Veranstaltung suchen'); - } - - /** - * returns the results of a search - * Use the contextual_data variable to send more variables than just the input - * to the SQL. QuickSearch for example sends all other variables of the same - *
-tag here. - * @param string $input the search-word(s) - * @param array $contextual_data an associative array with more variables - * @param int $limit maximum number of results (default: all) - * @param int $offset return results starting from this row (default: 0) - * @return array: array(array(), ...) - */ - public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) - { - $sql = $this->getSQL(); - if (!$sql) { - return []; - } - if ($offset || $limit != PHP_INT_MAX) { - $sql .= sprintf(' LIMIT %d, %d', $offset, $limit); - } - - $statement = DBManager::get()->prepare($sql, [PDO::FETCH_NUM]); - $statement->execute(array_merge( - $this->parameters, - $contextual_data, - [':input' => "%{$input}%"] - )); - $results = $statement->fetchAll(); - return $results; - } - - /** - * returns a sql-string appropriate for the searchtype of the current class - * - * @return string - */ - private function getSQL() - { - $semnumber = Config::get()->IMPORTANT_SEMNUMBER; - $semester_text = "CONCAT( - '(', - IF(semester_data.semester_id IS NULL, '" . _('unbegrenzt') . "', - GROUP_CONCAT(semester_data.`name` SEPARATOR ', ')), - ')' - )"; - - $conditions = implode(' AND ', $this->getConditions()); - - switch ($this->perm_level) { - // Roots see everything, everywhere. - case 'root': - $query = "SELECT DISTINCT - s.`Seminar_id`, - CONCAT_WS(' ', s.`VeranstaltungsNummer`, s.`Name`, {$semester_text}) - FROM `seminare` s - LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) - LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) - WHERE {$conditions} - GROUP BY s.Seminar_id "; - if ($semnumber) { - $query .= " ORDER BY MAX(semester_data.`beginn`) DESC, s.`VeranstaltungsNummer`, s.`Name`"; - } else { - $query .= " ORDER BY MAX(semester_data.beginn) DESC, s.`Name`"; - } - return $query; - // Admins see everything at their assigned institutes. - case 'admin': - $sem_inst = Config::get()->ALLOW_ADMIN_RELATED_INST ? 'si' : 's'; - $query = "SELECT DISTINCT - s.`Seminar_id`, - CONCAT_WS(' ', s.`VeranstaltungsNummer`, s.`Name`, {$semester_text}) - FROM `seminare` s - JOIN `seminar_inst` si USING (Seminar_id) - LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) - LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) - WHERE {$conditions} - AND {$sem_inst}.`institut_id` IN (:institutes) - GROUP BY s.Seminar_id "; - if ($semnumber) { - $query .= " ORDER BY MAX(semester_data.`beginn`) DESC, s.`VeranstaltungsNummer`, s.`Name`"; - } else { - $query .= " ORDER BY MAX(semester_data.`beginn`) DESC, s.`Name`"; - } - return $query; - // non-admins search all their administrable courses. - default: - $query = "SELECT DISTINCT - s.`Seminar_id`, - CONCAT_WS(' ', s.`VeranstaltungsNummer`, s.`Name`, {$semester_text}), - s.`VeranstaltungsNummer` AS num, - s.`Name`, - MAX(semester_data.beginn) AS beginn - FROM `seminare` s - JOIN `seminar_user` su ON (s.`Seminar_id` = su.`Seminar_id`) - LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) - LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) - WHERE {$conditions} - AND su.`user_id` = :userid - AND su.`status` IN ('dozent','tutor') - GROUP BY s.Seminar_id "; - - if (Config::get()->DEPUTIES_ENABLE) { - $query .= " UNION "; - $query .= "SELECT DISTINCT - s.`Seminar_id`, - CONCAT_WS(' ', s.`VeranstaltungsNummer`, ' ', s.`Name`, {$semester_text}), - s.`VeranstaltungsNummer` AS num, - s.`Name`, - MAX(semester_data.beginn) AS beginn - FROM `seminare` s - JOIN `deputies` d ON (s.`Seminar_id` = d.`range_id`) - LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) - LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) - WHERE {$conditions} - AND d.`user_id` = :userid - GROUP BY s.Seminar_id"; - } - - if ($semnumber) { - $query .= " ORDER BY beginn DESC, num, `Name`"; - } else { - $query .= " ORDER BY beginn DESC, `Name`"; - } - - return $query; - } - } - - /** - * Returns the default conditions use by all searches as a list. - * - * @return array - */ - private function getConditions(): array - { - $conditions = [ - '(s.`VeranstaltungsNummer` LIKE :input OR s.`Name` LIKE :input)', - 's.`Seminar_id` NOT IN (:exclude)', - ]; - - if (isset($this->parameters['semtypes'])) { - $conditions[] = 's.`status` NOT IN (:semtypes)'; - } - - if (isset($this->parameters['semesters'])) { - $conditions[] = 'semester_data.`semester_id` IN (:semesters)'; - } - - if ($this->additional_sql_conditions) { - $conditions[] = $this->additional_sql_conditions; - } - - return $conditions; - } - - /** - * A very simple overwrite of the same method from SearchType class. - * returns the absolute path to this class for autoincluding this class. - * - * @return: path to this class - */ - public function includePath() - { - return studip_relative_path(__FILE__); - } -} diff --git a/lib/classes/searchtypes/MyCoursesSearch.php b/lib/classes/searchtypes/MyCoursesSearch.php new file mode 100644 index 0000000..64bee64 --- /dev/null +++ b/lib/classes/searchtypes/MyCoursesSearch.php @@ -0,0 +1,220 @@ + + * @copyright 2015 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class MyCoursesSearch extends StandardSearch +{ + private $perm_level; + private $parameters; + protected $additional_sql_conditions; + + /** + * + * @param string $search + * + * @param string $perm_level + * + * @param string $additional_sql_conditions An additional SQL snippet + * consisting of conditions. This snippet is appended to the + * default conditions. + * + * @return void + */ + public function __construct($search, $perm_level = 'dozent', $parameters = [], $additional_sql_conditions = '') + { + parent::__construct($search); + + $this->perm_level = $perm_level; + $this->parameters = $parameters; + $this->additional_sql_conditions = trim($additional_sql_conditions); + } + + + /** + * returns the title/description of the searchfield + * + * @return string title/description + */ + public function getTitle() + { + return _('Veranstaltung suchen'); + } + + /** + * returns the results of a search + * Use the contextual_data variable to send more variables than just the input + * to the SQL. QuickSearch for example sends all other variables of the same + * -tag here. + * @param string $input the search-word(s) + * @param array $contextual_data an associative array with more variables + * @param int $limit maximum number of results (default: all) + * @param int $offset return results starting from this row (default: 0) + * @return array: array(array(), ...) + */ + public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) + { + $sql = $this->getSQL(); + if (!$sql) { + return []; + } + if ($offset || $limit != PHP_INT_MAX) { + $sql .= sprintf(' LIMIT %d, %d', $offset, $limit); + } + + $statement = DBManager::get()->prepare($sql, [PDO::FETCH_NUM]); + $statement->execute(array_merge( + $this->parameters, + $contextual_data, + [':input' => "%{$input}%"] + )); + $results = $statement->fetchAll(); + return $results; + } + + /** + * returns a sql-string appropriate for the searchtype of the current class + * + * @return string + */ + private function getSQL() + { + $semnumber = Config::get()->IMPORTANT_SEMNUMBER; + $semester_text = "CONCAT( + '(', + IF(semester_data.semester_id IS NULL, '" . _('unbegrenzt') . "', + GROUP_CONCAT(semester_data.`name` SEPARATOR ', ')), + ')' + )"; + + $conditions = implode(' AND ', $this->getConditions()); + + switch ($this->perm_level) { + // Roots see everything, everywhere. + case 'root': + $query = "SELECT DISTINCT + s.`Seminar_id`, + CONCAT_WS(' ', s.`VeranstaltungsNummer`, s.`Name`, {$semester_text}) + FROM `seminare` s + LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) + LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) + WHERE {$conditions} + GROUP BY s.Seminar_id "; + if ($semnumber) { + $query .= " ORDER BY MAX(semester_data.`beginn`) DESC, s.`VeranstaltungsNummer`, s.`Name`"; + } else { + $query .= " ORDER BY MAX(semester_data.beginn) DESC, s.`Name`"; + } + return $query; + // Admins see everything at their assigned institutes. + case 'admin': + $sem_inst = Config::get()->ALLOW_ADMIN_RELATED_INST ? 'si' : 's'; + $query = "SELECT DISTINCT + s.`Seminar_id`, + CONCAT_WS(' ', s.`VeranstaltungsNummer`, s.`Name`, {$semester_text}) + FROM `seminare` s + JOIN `seminar_inst` si USING (Seminar_id) + LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) + LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) + WHERE {$conditions} + AND {$sem_inst}.`institut_id` IN (:institutes) + GROUP BY s.Seminar_id "; + if ($semnumber) { + $query .= " ORDER BY MAX(semester_data.`beginn`) DESC, s.`VeranstaltungsNummer`, s.`Name`"; + } else { + $query .= " ORDER BY MAX(semester_data.`beginn`) DESC, s.`Name`"; + } + return $query; + // non-admins search all their administrable courses. + default: + $query = "SELECT DISTINCT + s.`Seminar_id`, + CONCAT_WS(' ', s.`VeranstaltungsNummer`, s.`Name`, {$semester_text}), + s.`VeranstaltungsNummer` AS num, + s.`Name`, + MAX(semester_data.beginn) AS beginn + FROM `seminare` s + JOIN `seminar_user` su ON (s.`Seminar_id` = su.`Seminar_id`) + LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) + LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) + WHERE {$conditions} + AND su.`user_id` = :userid + AND su.`status` IN ('dozent','tutor') + GROUP BY s.Seminar_id "; + + if (Config::get()->DEPUTIES_ENABLE) { + $query .= " UNION "; + $query .= "SELECT DISTINCT + s.`Seminar_id`, + CONCAT_WS(' ', s.`VeranstaltungsNummer`, ' ', s.`Name`, {$semester_text}), + s.`VeranstaltungsNummer` AS num, + s.`Name`, + MAX(semester_data.beginn) AS beginn + FROM `seminare` s + JOIN `deputies` d ON (s.`Seminar_id` = d.`range_id`) + LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id) + LEFT JOIN `semester_data` ON (semester_data.semester_id = semester_courses.semester_id) + WHERE {$conditions} + AND d.`user_id` = :userid + GROUP BY s.Seminar_id"; + } + + if ($semnumber) { + $query .= " ORDER BY beginn DESC, num, `Name`"; + } else { + $query .= " ORDER BY beginn DESC, `Name`"; + } + + return $query; + } + } + + /** + * Returns the default conditions use by all searches as a list. + * + * @return array + */ + private function getConditions(): array + { + $conditions = [ + '(s.`VeranstaltungsNummer` LIKE :input OR s.`Name` LIKE :input)', + 's.`Seminar_id` NOT IN (:exclude)', + ]; + + if (isset($this->parameters['semtypes'])) { + $conditions[] = 's.`status` NOT IN (:semtypes)'; + } + + if (isset($this->parameters['semesters'])) { + $conditions[] = 'semester_data.`semester_id` IN (:semesters)'; + } + + if ($this->additional_sql_conditions) { + $conditions[] = $this->additional_sql_conditions; + } + + return $conditions; + } + + /** + * A very simple overwrite of the same method from SearchType class. + * returns the absolute path to this class for autoincluding this class. + * + * @return: path to this class + */ + public function includePath() + { + return studip_relative_path(__FILE__); + } +} diff --git a/lib/classes/searchtypes/PermissionSearch.class.php b/lib/classes/searchtypes/PermissionSearch.class.php deleted file mode 100644 index cc4ce3e..0000000 --- a/lib/classes/searchtypes/PermissionSearch.class.php +++ /dev/null @@ -1,219 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -/** - * Class of type SearchType used for searches with QuickSearch - * (lib/classes/QuickSearch.class.php). You can search for people with a given - * Stud.IP permission level, either globally or at an institute. - * - * @author Thomas Hackl - * - */ - -class PermissionSearch extends SQLSearch { - - private $search; - private $presets; - - /** - * - * @param string $query: SQL with at least ":input" as parameter - * @param array $presets: variables from the same form that should be used - * in this search. array("input_name" => "placeholder_in_sql_query") - * @return void - */ - public function __construct($search, $title = "", $avatarLike = "user_id", $presets = []) { - $this->search = $search; - $this->presets = $presets; - $this->title = $title; - $this->avatarLike = in_array($avatarLike, words('user_id, username')) ? $avatarLike : 'user_id'; - } - - - /** - * returns the results of a search - * Use the contextual_data variable to send more variables than just the input - * to the SQL. QuickSearch for example sends all other variables of the same - * -tag here. - * @param input string: the search-word(s) - * @param contextual_data array: an associative array with more variables - * @param limit int: maximum number of results (default: all) - * @param offset int: return results starting from this row (default: 0) - * @return array: array(array(), ...) - */ - public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) - { - $db = DBManager::get(); - $sql = $this->getSQL(); - if ($offset || $limit != PHP_INT_MAX) { - $sql .= sprintf(' LIMIT %d, %d', $offset, $limit); - } - if (is_callable($this->presets, true)) { - $presets = call_user_func($this->presets, $this, $contextual_data); - } else { - $presets = $this->presets + $contextual_data; - } - - $data = $this->getDefaultData(); - $data[':input'] = "%{$input}%"; - - foreach ($presets as $name => $value) { - if (is_array($value) && !$value) { - $value = ''; - } - - if ($name !== 'input' && mb_strpos($sql, ":{$name}") !== false) { - $data[":{$name}"] = $value; - } - } - - $statement = $db->prepare($sql); - $statement->execute($data); - $results = $statement->fetchAll(); - return $results; - } - - private function getSQL() - { - $first_column = "auth_user_md5.{$this->avatarLike}"; - - // Respect user visibility setting - if ($GLOBALS['user']->perms === 'root') { - // Root may find everyone - $visibility_condition = '1'; - } else { - $visibility_condition = "auth_user_md5.visible NOT IN ('never')"; - if (Config::get()->DOZENT_ALWAYS_VISIBLE) { - $visibility_condition .= " OR auth_user_md5.perms = 'dozent'"; - } - $visibility_condition = "({$visibility_condition})"; - } - - switch ($this->search) { - case 'user': - return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') - FROM auth_user_md5 - LEFT JOIN user_info USING (user_id) - WHERE ( - CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') - OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input - OR auth_user_md5.username LIKE :input - ) - AND auth_user_md5.perms IN (:permission) - AND auth_user_md5.user_id NOT IN (:exclude_user) - AND {$visibility_condition} - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; - case 'user_not_already_in_sem': - return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') - FROM auth_user_md5 - LEFT JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id AND status IN (:sem_perm) - LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id - WHERE su.user_id IS NULL - AND ( - CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') - OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input - OR auth_user_md5.username LIKE :input - ) - AND auth_user_md5.perms IN (:permission) - AND {$visibility_condition} - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; - case 'user_in_sem': - return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') - FROM auth_user_md5 - JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id AND status IN (:sem_perm) - LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id - WHERE ( - CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') - OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input - OR auth_user_md5.username LIKE :input - ) - AND auth_user_md5.user_id NOT IN (:exclude_user) - AND {$visibility_condition} - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; - case 'user_inst': - return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') - FROM auth_user_md5 - LEFT JOIN user_inst ON user_inst.user_id = auth_user_md5.user_id - LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id - WHERE ( - CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') - OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input - OR auth_user_md5.username LIKE :input - ) - AND user_inst.Institut_id IN (:institute) - AND user_inst.inst_perms IN (:permission) - AND auth_user_md5.user_id NOT IN (:exclude_user) - AND {$visibility_condition} - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; - case 'user_inst_not_already_in_sem': - return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') - FROM auth_user_md5 - LEFT JOIN user_inst ON user_inst.user_id = auth_user_md5.user_id - LEFT JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id AND status IN (:sem_perm) - LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id - WHERE su.user_id IS NULL - AND ( - CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') - OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input - OR auth_user_md5.username LIKE :input - ) - AND user_inst.Institut_id IN (:institute) - AND user_inst.inst_perms IN (:permission) - AND {$visibility_condition} - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; - case 'user_not_already_in_sem_or_deputy': - return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') - FROM auth_user_md5 - LEFT JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id - LEFT JOIN deputies d ON d.user_id = auth_user_md5.user_id AND range_id=:seminar_id - LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id - WHERE su.user_id IS NULL - AND d.user_id IS NULL - AND ( - CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') - OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input - OR auth_user_md5.username LIKE :input - ) - AND auth_user_md5.perms IN (:permission) - AND {$visibility_condition} - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; - } - - // No search type matched? - throw new InvalidArgumentException('search parameter not valid'); - } - - private function getDefaultData() - { - $data = []; - if (in_array($this->search, ['user', 'user_in_sem', 'user_inst'])) { - $data[':exclude_user'] = ''; - } - if (in_array($this->search, ['user_not_already_in_sem', 'user_inst_not_already_in_sem'])) { - $data[':sem_perm'] = ['autor', 'tutor', 'dozent']; - } - if (in_array($this->search, ['user', 'user_inst'])) { - $data[':permission'] = ['autor', 'tutor', 'dozent', 'admin']; - } - return $data; - } - - /** - * A very simple overwrite of the same method from SearchType class. - * returns the absolute path to this class for autoincluding this class. - * @return: path to this class - */ - public function includePath() - { - return studip_relative_path(__FILE__); - } -} diff --git a/lib/classes/searchtypes/PermissionSearch.php b/lib/classes/searchtypes/PermissionSearch.php new file mode 100644 index 0000000..cc4ce3e --- /dev/null +++ b/lib/classes/searchtypes/PermissionSearch.php @@ -0,0 +1,219 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +/** + * Class of type SearchType used for searches with QuickSearch + * (lib/classes/QuickSearch.class.php). You can search for people with a given + * Stud.IP permission level, either globally or at an institute. + * + * @author Thomas Hackl + * + */ + +class PermissionSearch extends SQLSearch { + + private $search; + private $presets; + + /** + * + * @param string $query: SQL with at least ":input" as parameter + * @param array $presets: variables from the same form that should be used + * in this search. array("input_name" => "placeholder_in_sql_query") + * @return void + */ + public function __construct($search, $title = "", $avatarLike = "user_id", $presets = []) { + $this->search = $search; + $this->presets = $presets; + $this->title = $title; + $this->avatarLike = in_array($avatarLike, words('user_id, username')) ? $avatarLike : 'user_id'; + } + + + /** + * returns the results of a search + * Use the contextual_data variable to send more variables than just the input + * to the SQL. QuickSearch for example sends all other variables of the same + * -tag here. + * @param input string: the search-word(s) + * @param contextual_data array: an associative array with more variables + * @param limit int: maximum number of results (default: all) + * @param offset int: return results starting from this row (default: 0) + * @return array: array(array(), ...) + */ + public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) + { + $db = DBManager::get(); + $sql = $this->getSQL(); + if ($offset || $limit != PHP_INT_MAX) { + $sql .= sprintf(' LIMIT %d, %d', $offset, $limit); + } + if (is_callable($this->presets, true)) { + $presets = call_user_func($this->presets, $this, $contextual_data); + } else { + $presets = $this->presets + $contextual_data; + } + + $data = $this->getDefaultData(); + $data[':input'] = "%{$input}%"; + + foreach ($presets as $name => $value) { + if (is_array($value) && !$value) { + $value = ''; + } + + if ($name !== 'input' && mb_strpos($sql, ":{$name}") !== false) { + $data[":{$name}"] = $value; + } + } + + $statement = $db->prepare($sql); + $statement->execute($data); + $results = $statement->fetchAll(); + return $results; + } + + private function getSQL() + { + $first_column = "auth_user_md5.{$this->avatarLike}"; + + // Respect user visibility setting + if ($GLOBALS['user']->perms === 'root') { + // Root may find everyone + $visibility_condition = '1'; + } else { + $visibility_condition = "auth_user_md5.visible NOT IN ('never')"; + if (Config::get()->DOZENT_ALWAYS_VISIBLE) { + $visibility_condition .= " OR auth_user_md5.perms = 'dozent'"; + } + $visibility_condition = "({$visibility_condition})"; + } + + switch ($this->search) { + case 'user': + return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') + FROM auth_user_md5 + LEFT JOIN user_info USING (user_id) + WHERE ( + CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') + OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input + OR auth_user_md5.username LIKE :input + ) + AND auth_user_md5.perms IN (:permission) + AND auth_user_md5.user_id NOT IN (:exclude_user) + AND {$visibility_condition} + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; + case 'user_not_already_in_sem': + return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') + FROM auth_user_md5 + LEFT JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id AND status IN (:sem_perm) + LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id + WHERE su.user_id IS NULL + AND ( + CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') + OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input + OR auth_user_md5.username LIKE :input + ) + AND auth_user_md5.perms IN (:permission) + AND {$visibility_condition} + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; + case 'user_in_sem': + return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') + FROM auth_user_md5 + JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id AND status IN (:sem_perm) + LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id + WHERE ( + CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') + OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input + OR auth_user_md5.username LIKE :input + ) + AND auth_user_md5.user_id NOT IN (:exclude_user) + AND {$visibility_condition} + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; + case 'user_inst': + return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') + FROM auth_user_md5 + LEFT JOIN user_inst ON user_inst.user_id = auth_user_md5.user_id + LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id + WHERE ( + CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') + OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input + OR auth_user_md5.username LIKE :input + ) + AND user_inst.Institut_id IN (:institute) + AND user_inst.inst_perms IN (:permission) + AND auth_user_md5.user_id NOT IN (:exclude_user) + AND {$visibility_condition} + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; + case 'user_inst_not_already_in_sem': + return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') + FROM auth_user_md5 + LEFT JOIN user_inst ON user_inst.user_id = auth_user_md5.user_id + LEFT JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id AND status IN (:sem_perm) + LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id + WHERE su.user_id IS NULL + AND ( + CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') + OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input + OR auth_user_md5.username LIKE :input + ) + AND user_inst.Institut_id IN (:institute) + AND user_inst.inst_perms IN (:permission) + AND {$visibility_condition} + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; + case 'user_not_already_in_sem_or_deputy': + return "SELECT DISTINCT $first_column, CONCAT(Nachname, ', ', Vorname, ' (', username, ')') + FROM auth_user_md5 + LEFT JOIN seminar_user su ON su.user_id = auth_user_md5.user_id AND seminar_id=:seminar_id + LEFT JOIN deputies d ON d.user_id = auth_user_md5.user_id AND range_id=:seminar_id + LEFT JOIN user_info ON auth_user_md5.user_id = user_info.user_id + WHERE su.user_id IS NULL + AND d.user_id IS NULL + AND ( + CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') + OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input + OR auth_user_md5.username LIKE :input + ) + AND auth_user_md5.perms IN (:permission) + AND {$visibility_condition} + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname, auth_user_md5.username"; + } + + // No search type matched? + throw new InvalidArgumentException('search parameter not valid'); + } + + private function getDefaultData() + { + $data = []; + if (in_array($this->search, ['user', 'user_in_sem', 'user_inst'])) { + $data[':exclude_user'] = ''; + } + if (in_array($this->search, ['user_not_already_in_sem', 'user_inst_not_already_in_sem'])) { + $data[':sem_perm'] = ['autor', 'tutor', 'dozent']; + } + if (in_array($this->search, ['user', 'user_inst'])) { + $data[':permission'] = ['autor', 'tutor', 'dozent', 'admin']; + } + return $data; + } + + /** + * A very simple overwrite of the same method from SearchType class. + * returns the absolute path to this class for autoincluding this class. + * @return: path to this class + */ + public function includePath() + { + return studip_relative_path(__FILE__); + } +} diff --git a/lib/classes/searchtypes/RangeSearch.class.php b/lib/classes/searchtypes/RangeSearch.class.php deleted file mode 100644 index caefae0..0000000 --- a/lib/classes/searchtypes/RangeSearch.class.php +++ /dev/null @@ -1,87 +0,0 @@ - - * @license GPL2 or any later version - * @category Stud.IP - */ -class RangeSearch extends SearchType -{ - /** - * returns the title/description of the searchfield - * - * @return string title/description - */ - public function getTitle() - { - return _('Person, Veranstaltung oder Einrichtung suchen'); - } - - /** - * returns a sql-string appropriate for the searchtype of the current class - * - * @return string - */ - private function getSQL() - { - $this->extendedLayout = true; - - $queries = []; - $queries[] = "SELECT user_id AS id, - TRIM(CONCAT(Nachname, ', ', Vorname, ' (', username, ')')) AS name, - 'user' AS type - FROM auth_user_md5 - LEFT JOIN user_info USING (user_id) - WHERE ( - CONCAT(Nachname, ', ', Vorname, ' ', Nachname) LIKE REPLACE(:input, ' ', '% ') - OR username LIKE :input - ) - AND " . get_vis_query(); - $queries[] = "SELECT Seminar_id AS id, - TRIM(CONCAT(VeranstaltungsNummer, ' ', Name)) AS name, - 'course' AS type - FROM seminare - WHERE CONCAT(VeranstaltungsNummer, ' ', Name, ' ', Untertitel) LIKE REPLACE(:input, ' ', '% ')"; - $queries[] = "SELECT Institut_id AS id, - Name AS name, - 'institute' AS type - FROM Institute - WHERE Name LIKE REPLACE(:input, ' ', '% ')"; - $queries = implode(" UNION ALL ", $queries); - - return "SELECT * - FROM ({$queries}) AS tmp - ORDER BY name ASC"; - } - - /** - * {@inheritdoc} - */ - public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) - { - $query = $this->getSQL(); - - if ($offset || $limit != PHP_INT_MAX) { - $query .= sprintf(' LIMIT %u, %u', $offset, $limit); - } - - return DBManager::get()->fetchAll($query, [':input' => "%{$input}%"], function ($row) { - $range = RangeFactory::createRange($row['type'], null); - return [ - $row['id'], - $range->describeRange() . ': ' . $row['name'], - ]; - }); - - } - - /** - * A very simple overwrite of the same method from SearchType class. - * returns the absolute path to this class for autoincluding this class. - * - * @return: path to this class - */ - public function includePath() - { - return studip_relative_path(__FILE__); - } -} diff --git a/lib/classes/searchtypes/RangeSearch.php b/lib/classes/searchtypes/RangeSearch.php new file mode 100644 index 0000000..caefae0 --- /dev/null +++ b/lib/classes/searchtypes/RangeSearch.php @@ -0,0 +1,87 @@ + + * @license GPL2 or any later version + * @category Stud.IP + */ +class RangeSearch extends SearchType +{ + /** + * returns the title/description of the searchfield + * + * @return string title/description + */ + public function getTitle() + { + return _('Person, Veranstaltung oder Einrichtung suchen'); + } + + /** + * returns a sql-string appropriate for the searchtype of the current class + * + * @return string + */ + private function getSQL() + { + $this->extendedLayout = true; + + $queries = []; + $queries[] = "SELECT user_id AS id, + TRIM(CONCAT(Nachname, ', ', Vorname, ' (', username, ')')) AS name, + 'user' AS type + FROM auth_user_md5 + LEFT JOIN user_info USING (user_id) + WHERE ( + CONCAT(Nachname, ', ', Vorname, ' ', Nachname) LIKE REPLACE(:input, ' ', '% ') + OR username LIKE :input + ) + AND " . get_vis_query(); + $queries[] = "SELECT Seminar_id AS id, + TRIM(CONCAT(VeranstaltungsNummer, ' ', Name)) AS name, + 'course' AS type + FROM seminare + WHERE CONCAT(VeranstaltungsNummer, ' ', Name, ' ', Untertitel) LIKE REPLACE(:input, ' ', '% ')"; + $queries[] = "SELECT Institut_id AS id, + Name AS name, + 'institute' AS type + FROM Institute + WHERE Name LIKE REPLACE(:input, ' ', '% ')"; + $queries = implode(" UNION ALL ", $queries); + + return "SELECT * + FROM ({$queries}) AS tmp + ORDER BY name ASC"; + } + + /** + * {@inheritdoc} + */ + public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) + { + $query = $this->getSQL(); + + if ($offset || $limit != PHP_INT_MAX) { + $query .= sprintf(' LIMIT %u, %u', $offset, $limit); + } + + return DBManager::get()->fetchAll($query, [':input' => "%{$input}%"], function ($row) { + $range = RangeFactory::createRange($row['type'], null); + return [ + $row['id'], + $range->describeRange() . ': ' . $row['name'], + ]; + }); + + } + + /** + * A very simple overwrite of the same method from SearchType class. + * returns the absolute path to this class for autoincluding this class. + * + * @return: path to this class + */ + public function includePath() + { + return studip_relative_path(__FILE__); + } +} diff --git a/lib/classes/searchtypes/ResourceSearch.class.php b/lib/classes/searchtypes/ResourceSearch.class.php deleted file mode 100644 index 24038a8..0000000 --- a/lib/classes/searchtypes/ResourceSearch.class.php +++ /dev/null @@ -1,362 +0,0 @@ - - * @author Timo Hartge - * @copyright 2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - - -class ResourceSearch extends SearchType -{ - //Attributes: - - - /** - * string[] - * Limits the search results to resources where a person has at least - * the permission levels specified in this attribute. - * Defaults to ['user', 'autor', 'tutor', 'admin']. - */ - protected $accepted_permission_levels = ['user', 'autor', 'tutor', 'admin']; - - /** - * bool - * Whether to use global permissions when searching for resources - * where a person has permissions on (true) or not. Defaults to true. - */ - protected $use_global_permissions = true; - - - /** - * string[] - * Additional properties that shall be appended to the name of the resource. - * The array must be one-dimensional. - */ - protected $additional_display_properties = []; - - - /** - * string - * A format specification for additional properties in the "sprintf syntax". - * This format specification must have only one placeholder named '%s' - * in which the additional properties will be inserted, separated by - * the string ", ". - * This attribute defaults to '[%s]'. - */ - protected $additional_property_format = '[%s]'; - - - /** - * array - * Array of class_names of Resource classes that will be included in the search. - * An empty array will include all resource classes. - */ - protected $searchable_resource_classes = []; - - - /** - * The setter method for the $accepted_permission_levels attribute. - * - * @param string[] $accepted_permission_levels The new value for the attribute. - */ - public function setAcceptedPermissionLevels($levels = []) - { - //Check the array: - if (!$levels) { - throw new InvalidArgumentException( - _('Es wurde keine gültige Berechtigungsstufe übergeben!') - ); - } - - if (!is_array($levels)) { - $levels = [$levels]; - } - - foreach ($levels as $level) { - if (!in_array($level, ['user', 'autor', 'tutor', 'admin'])) { - throw new InvalidArgumentException( - sprintf( - _('Die Berechtigungsstufe %s ist ungültig!'), - $level - ) - ); - } - } - - //The checks are finished: set the new levels: - $this->accepted_permission_levels = array_unique($levels); - } - - - /** - * The getter method for the $accepted_permission_levels attribute. - * - * @returns string[] - */ - public function getAcceptedPermissionLevels() - { - return $this->accepted_permission_levels; - } - - - /** - * The setter method for the $use_global_permission attribute. - * - * @param bool $use_global_permissions The new value for the attribute. - */ - public function setUseOfGlobalPermissions($use_global_permissions = true) - { - $this->use_global_permissions = (bool)$use_global_permissions; - } - - - /** - * The getter method for the $use_global_permission attribute. - * - * @returns bool - */ - public function getUseOfGlobalPermissions() - { - return $this->use_global_permissions; - } - - - /** - * The setter method for the $additional_display_properties attribute. - * - * @param array $properties The new value for the attribute. - */ - public function setAdditionalDisplayProperties($properties = []) - { - $this->additional_display_properties = $properties; - } - - - /** - * The getter method for the $additional_display_properties attribute. - * - * @returns array @see $additional_display_properties for the array format. - */ - public function getAdditionalDisplayProperties() - { - return $this->additional_display_properties; - } - - - /** - * The setter method for the $additional_property_format attribute. - * - * @param string $format The new value for the attribute. - */ - public function setAdditionalPropertyFormat($format = '[%s]') - { - $this->additional_property_format = $format; - } - - - /** - * The getter method for the $additional_property_format attribute. - * - * @returns string - */ - public function getAdditionalPropertyFormat() - { - return $this->additional_property_format; - } - - /** - * The setter method for the $searchable_resource_classes attribute. - * - * @param array $categories The new value for the attribute. - */ - public function setSearchableResourceClasses($classes = []) - { - $this->searchable_resource_classes = $classes; - } - - - /** - * The getter method for the $searchable_resource_classes attribute. - * - * @returns array - */ - public function getSearchableResourceClasses() - { - return $this->searchable_resource_classes; - } - - - //SearchType interface implementations: - - - public function getTitle() - { - return _('Ressourcensuche'); - } - - /** - * @param string $keyword The name of the resource or a part of it. - * @param array $contextual_data This parameter is not used at the moment. - */ - public function getResults( - $keyword, - $contextual_data = [], - $limit = PHP_INT_MAX, - $offset = 0 - ) - { - $user = User::findCurrent(); - if (!$user) { - return []; - } - - $sql = "INNER JOIN resource_categories rc - ON resources.category_id = rc.id - WHERE - resources.name LIKE CONCAT('%', :keyword, '%') "; - $sql_params = [ - 'keyword' => $keyword - ]; - - //Root users can access everything so that we don't need - //to check for permissions in case the current user is root. - if (!$GLOBALS['perm']->have_perm('root')) { - $global_permission = null; - if ($this->use_global_permissions) { - //Get the global permission level: - $global_permission = ResourceManager::getGlobalResourcePermission( - $user - ); - } - - if (in_array($global_permission, $this->accepted_permission_levels)) { - //Retrieve all resources where the user has sufficient permissions. - //There may be resources where the user has explicitly lower - //permissions and we must therefore exclude these resources. - $lower_permission_levels = ResourceManager::getLowerPermissionLevels( - $global_permission - ); - $sql .= "AND resources.id NOT IN ( - SELECT resource_id - FROM - resource_permissions - WHERE - user_id = :user_id - AND - perms IN ( :lower_perms ) - UNION - SELECT resource_id - FROM - resource_temporary_permissions - WHERE - user_id = :user_id - AND - perms IN ( :lower_perms ) - AND - begin <= :now - AND - end >= :now - )"; - $sql_params = array_merge( - $sql_params, - [ - 'user_id' => $user->id, - 'lower_perms' => $lower_permission_levels, - 'now' => time() - ] - ); - } else { - //Only retrieve those resources where the user has direct - //sufficient permissions. - $sql .= "AND resources.id IN ( - SELECT resource_id - FROM - resource_permissions - WHERE - user_id = :user_id - AND - perms IN ( :perms ) - UNION - SELECT resource_id - FROM - resource_temporary_permissions - WHERE - user_id = :user_id - AND - perms IN ( :perms ) - AND - begin <= :now - AND - end >= :now - )"; - $sql_params = array_merge( - $sql_params, - [ - 'user_id' => $user->id, - 'perms' => $this->accepted_permission_levels, - 'now' => time() - ] - ); - } - } - - $sql .= " ORDER BY resources.name ASC - LIMIT :limit OFFSET :offset "; - $sql_params['limit'] = $limit; - $sql_params['offset'] = $offset; - - $resources = Resource::findBySql( - $sql, - $sql_params - ); - - $results = []; - foreach ($resources as $resource) { - - if (!empty($this->searchable_resource_classes)) { - if(!in_array($resource->class_name, $this->searchable_resource_classes)) { - continue; - } - } - - $additional_text = ''; - if (count($this->additional_display_properties)) { - $additional_values = []; - foreach ($this->additional_display_properties as $ap) { - $additional_values[] = $resource->getProperty($ap); - } - $additional_text = implode(', ', $additional_values); - } - - $complete_text = $resource->name; - if ($additional_text) { - $complete_text .= ' ' . sprintf( - $this->additional_property_format, - $additional_text - ); - } - $results[] = [ - $resource->id, - $complete_text - ]; - } - - return $results; - } - - - public function includePath() - { - return __FILE__; - } -} diff --git a/lib/classes/searchtypes/ResourceSearch.php b/lib/classes/searchtypes/ResourceSearch.php new file mode 100644 index 0000000..24038a8 --- /dev/null +++ b/lib/classes/searchtypes/ResourceSearch.php @@ -0,0 +1,362 @@ + + * @author Timo Hartge + * @copyright 2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + + +class ResourceSearch extends SearchType +{ + //Attributes: + + + /** + * string[] + * Limits the search results to resources where a person has at least + * the permission levels specified in this attribute. + * Defaults to ['user', 'autor', 'tutor', 'admin']. + */ + protected $accepted_permission_levels = ['user', 'autor', 'tutor', 'admin']; + + /** + * bool + * Whether to use global permissions when searching for resources + * where a person has permissions on (true) or not. Defaults to true. + */ + protected $use_global_permissions = true; + + + /** + * string[] + * Additional properties that shall be appended to the name of the resource. + * The array must be one-dimensional. + */ + protected $additional_display_properties = []; + + + /** + * string + * A format specification for additional properties in the "sprintf syntax". + * This format specification must have only one placeholder named '%s' + * in which the additional properties will be inserted, separated by + * the string ", ". + * This attribute defaults to '[%s]'. + */ + protected $additional_property_format = '[%s]'; + + + /** + * array + * Array of class_names of Resource classes that will be included in the search. + * An empty array will include all resource classes. + */ + protected $searchable_resource_classes = []; + + + /** + * The setter method for the $accepted_permission_levels attribute. + * + * @param string[] $accepted_permission_levels The new value for the attribute. + */ + public function setAcceptedPermissionLevels($levels = []) + { + //Check the array: + if (!$levels) { + throw new InvalidArgumentException( + _('Es wurde keine gültige Berechtigungsstufe übergeben!') + ); + } + + if (!is_array($levels)) { + $levels = [$levels]; + } + + foreach ($levels as $level) { + if (!in_array($level, ['user', 'autor', 'tutor', 'admin'])) { + throw new InvalidArgumentException( + sprintf( + _('Die Berechtigungsstufe %s ist ungültig!'), + $level + ) + ); + } + } + + //The checks are finished: set the new levels: + $this->accepted_permission_levels = array_unique($levels); + } + + + /** + * The getter method for the $accepted_permission_levels attribute. + * + * @returns string[] + */ + public function getAcceptedPermissionLevels() + { + return $this->accepted_permission_levels; + } + + + /** + * The setter method for the $use_global_permission attribute. + * + * @param bool $use_global_permissions The new value for the attribute. + */ + public function setUseOfGlobalPermissions($use_global_permissions = true) + { + $this->use_global_permissions = (bool)$use_global_permissions; + } + + + /** + * The getter method for the $use_global_permission attribute. + * + * @returns bool + */ + public function getUseOfGlobalPermissions() + { + return $this->use_global_permissions; + } + + + /** + * The setter method for the $additional_display_properties attribute. + * + * @param array $properties The new value for the attribute. + */ + public function setAdditionalDisplayProperties($properties = []) + { + $this->additional_display_properties = $properties; + } + + + /** + * The getter method for the $additional_display_properties attribute. + * + * @returns array @see $additional_display_properties for the array format. + */ + public function getAdditionalDisplayProperties() + { + return $this->additional_display_properties; + } + + + /** + * The setter method for the $additional_property_format attribute. + * + * @param string $format The new value for the attribute. + */ + public function setAdditionalPropertyFormat($format = '[%s]') + { + $this->additional_property_format = $format; + } + + + /** + * The getter method for the $additional_property_format attribute. + * + * @returns string + */ + public function getAdditionalPropertyFormat() + { + return $this->additional_property_format; + } + + /** + * The setter method for the $searchable_resource_classes attribute. + * + * @param array $categories The new value for the attribute. + */ + public function setSearchableResourceClasses($classes = []) + { + $this->searchable_resource_classes = $classes; + } + + + /** + * The getter method for the $searchable_resource_classes attribute. + * + * @returns array + */ + public function getSearchableResourceClasses() + { + return $this->searchable_resource_classes; + } + + + //SearchType interface implementations: + + + public function getTitle() + { + return _('Ressourcensuche'); + } + + /** + * @param string $keyword The name of the resource or a part of it. + * @param array $contextual_data This parameter is not used at the moment. + */ + public function getResults( + $keyword, + $contextual_data = [], + $limit = PHP_INT_MAX, + $offset = 0 + ) + { + $user = User::findCurrent(); + if (!$user) { + return []; + } + + $sql = "INNER JOIN resource_categories rc + ON resources.category_id = rc.id + WHERE + resources.name LIKE CONCAT('%', :keyword, '%') "; + $sql_params = [ + 'keyword' => $keyword + ]; + + //Root users can access everything so that we don't need + //to check for permissions in case the current user is root. + if (!$GLOBALS['perm']->have_perm('root')) { + $global_permission = null; + if ($this->use_global_permissions) { + //Get the global permission level: + $global_permission = ResourceManager::getGlobalResourcePermission( + $user + ); + } + + if (in_array($global_permission, $this->accepted_permission_levels)) { + //Retrieve all resources where the user has sufficient permissions. + //There may be resources where the user has explicitly lower + //permissions and we must therefore exclude these resources. + $lower_permission_levels = ResourceManager::getLowerPermissionLevels( + $global_permission + ); + $sql .= "AND resources.id NOT IN ( + SELECT resource_id + FROM + resource_permissions + WHERE + user_id = :user_id + AND + perms IN ( :lower_perms ) + UNION + SELECT resource_id + FROM + resource_temporary_permissions + WHERE + user_id = :user_id + AND + perms IN ( :lower_perms ) + AND + begin <= :now + AND + end >= :now + )"; + $sql_params = array_merge( + $sql_params, + [ + 'user_id' => $user->id, + 'lower_perms' => $lower_permission_levels, + 'now' => time() + ] + ); + } else { + //Only retrieve those resources where the user has direct + //sufficient permissions. + $sql .= "AND resources.id IN ( + SELECT resource_id + FROM + resource_permissions + WHERE + user_id = :user_id + AND + perms IN ( :perms ) + UNION + SELECT resource_id + FROM + resource_temporary_permissions + WHERE + user_id = :user_id + AND + perms IN ( :perms ) + AND + begin <= :now + AND + end >= :now + )"; + $sql_params = array_merge( + $sql_params, + [ + 'user_id' => $user->id, + 'perms' => $this->accepted_permission_levels, + 'now' => time() + ] + ); + } + } + + $sql .= " ORDER BY resources.name ASC + LIMIT :limit OFFSET :offset "; + $sql_params['limit'] = $limit; + $sql_params['offset'] = $offset; + + $resources = Resource::findBySql( + $sql, + $sql_params + ); + + $results = []; + foreach ($resources as $resource) { + + if (!empty($this->searchable_resource_classes)) { + if(!in_array($resource->class_name, $this->searchable_resource_classes)) { + continue; + } + } + + $additional_text = ''; + if (count($this->additional_display_properties)) { + $additional_values = []; + foreach ($this->additional_display_properties as $ap) { + $additional_values[] = $resource->getProperty($ap); + } + $additional_text = implode(', ', $additional_values); + } + + $complete_text = $resource->name; + if ($additional_text) { + $complete_text .= ' ' . sprintf( + $this->additional_property_format, + $additional_text + ); + } + $results[] = [ + $resource->id, + $complete_text + ]; + } + + return $results; + } + + + public function includePath() + { + return __FILE__; + } +} diff --git a/lib/classes/searchtypes/RoomSearch.class.php b/lib/classes/searchtypes/RoomSearch.class.php deleted file mode 100644 index 002712e..0000000 --- a/lib/classes/searchtypes/RoomSearch.class.php +++ /dev/null @@ -1,192 +0,0 @@ - - * @copyright 2018 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - - -class RoomSearch extends ResourceSearch -{ - //Setup and search configuration methods: - public $with_seats = 0; - - //SearchType interface implementations: - - public function getTitle() - { - return _('Raumsuche'); - } - - /** - * @param string $keyword The name of the room or a part of it. - * @param array $contextual_data This parameter is not used at the moment. - */ - public function getResults( - $keyword, - $contextual_data = [], - $limit = PHP_INT_MAX, - $offset = 0 - ) - { - $user = User::findCurrent(); - if (!$user) { - return []; - } - if ($this->with_seats) { - $prop_id_seats = ResourcePropertyDefinition::findOneByName('seats')->id; - $sql = "INNER JOIN resource_categories rc - ON resources.category_id = rc.id - INNER JOIN resource_properties rp ON rp.resource_id = resources.id - WHERE - rc.class_name IN ( :room_class_names ) - AND - rp.property_id = :prop_id_seats - AND - rp.state > :seats - AND - resources.name LIKE CONCAT('%', :keyword, '%') "; - $sql_params = [ - 'keyword' => $keyword, - 'seats' => $this->with_seats, - 'prop_id_seats' => $prop_id_seats - ]; - } else { - $sql = "INNER JOIN resource_categories rc - ON resources.category_id = rc.id - WHERE - rc.class_name IN ( :room_class_names ) - AND - resources.name LIKE CONCAT('%', :keyword, '%') "; - $sql_params = [ - 'keyword' => $keyword - ]; - } - $sql_params['room_class_names'] = RoomManager::getAllRoomClassNames(); - - //Root users can access everything so that we don't need - //to check for permissions in case the current user is root. - if (!$GLOBALS['perm']->have_perm('root')) { - $global_permission = null; - if ($this->use_global_permissions) { - //Get the global permission level: - $global_permission = ResourceManager::getGlobalResourcePermission( - $user - ); - } - - if (in_array($global_permission, $this->accepted_permission_levels)) { - //Retrieve all resources where the user has sufficient permissions. - //There may be resources where the user has explicitly lower - //permissions and we must therefore exclude these resources. - $lower_permission_levels = ResourceManager::getLowerPermissionLevels( - $global_permission - ); - $sql .= "AND resources.id NOT IN ( - SELECT resource_id - FROM - resource_permissions - WHERE - user_id = :user_id - AND - perms IN ( :lower_perms ) - UNION - SELECT resource_id - FROM - resource_temporary_permissions - WHERE - user_id = :user_id - AND - perms IN ( :lower_perms ) - AND - begin <= :now - AND - end >= :now - )"; - $sql_params = array_merge( - $sql_params, - [ - 'user_id' => $user->id, - 'lower_perms' => $lower_permission_levels, - 'now' => time() - ] - ); - } else { - //Only retrieve those resources where the user has direct - //sufficient permissions. - $sql .= "AND resources.id IN ( - SELECT resource_id - FROM - resource_permissions - WHERE - user_id = :user_id - AND - perms IN ( :perms ) - UNION - SELECT resource_id - FROM - resource_temporary_permissions - WHERE - user_id = :user_id - AND - perms IN ( :perms ) - AND - begin <= :now - AND - end >= :now - )"; - $sql_params = array_merge( - $sql_params, - [ - 'user_id' => $user->id, - 'perms' => $this->accepted_permission_levels, - 'now' => time() - ] - ); - } - } - - $sql .= " ORDER BY resources.name ASC - LIMIT :limit OFFSET :offset "; - $sql_params['limit'] = $limit; - $sql_params['offset'] = $offset; - - $rooms = Room::findBySql( - $sql, - $sql_params - ); - - $results = []; - foreach ($rooms as $room) { - $additional_text = ''; - if (count($this->additional_display_properties)) { - $additional_values = []; - foreach ($this->additional_display_properties as $ap) { - $additional_values[] = $room->getProperty($ap); - } - $additional_text = implode(', ', $additional_values); - } - $name = $room->name; - if($additional_text) { - $name .= sprintf( - $this->additional_property_format, - $additional_text - ); - } - $results[] = [ - $room->id, - $name - ]; - } - return $results; - } -} diff --git a/lib/classes/searchtypes/RoomSearch.php b/lib/classes/searchtypes/RoomSearch.php new file mode 100644 index 0000000..002712e --- /dev/null +++ b/lib/classes/searchtypes/RoomSearch.php @@ -0,0 +1,192 @@ + + * @copyright 2018 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + + +class RoomSearch extends ResourceSearch +{ + //Setup and search configuration methods: + public $with_seats = 0; + + //SearchType interface implementations: + + public function getTitle() + { + return _('Raumsuche'); + } + + /** + * @param string $keyword The name of the room or a part of it. + * @param array $contextual_data This parameter is not used at the moment. + */ + public function getResults( + $keyword, + $contextual_data = [], + $limit = PHP_INT_MAX, + $offset = 0 + ) + { + $user = User::findCurrent(); + if (!$user) { + return []; + } + if ($this->with_seats) { + $prop_id_seats = ResourcePropertyDefinition::findOneByName('seats')->id; + $sql = "INNER JOIN resource_categories rc + ON resources.category_id = rc.id + INNER JOIN resource_properties rp ON rp.resource_id = resources.id + WHERE + rc.class_name IN ( :room_class_names ) + AND + rp.property_id = :prop_id_seats + AND + rp.state > :seats + AND + resources.name LIKE CONCAT('%', :keyword, '%') "; + $sql_params = [ + 'keyword' => $keyword, + 'seats' => $this->with_seats, + 'prop_id_seats' => $prop_id_seats + ]; + } else { + $sql = "INNER JOIN resource_categories rc + ON resources.category_id = rc.id + WHERE + rc.class_name IN ( :room_class_names ) + AND + resources.name LIKE CONCAT('%', :keyword, '%') "; + $sql_params = [ + 'keyword' => $keyword + ]; + } + $sql_params['room_class_names'] = RoomManager::getAllRoomClassNames(); + + //Root users can access everything so that we don't need + //to check for permissions in case the current user is root. + if (!$GLOBALS['perm']->have_perm('root')) { + $global_permission = null; + if ($this->use_global_permissions) { + //Get the global permission level: + $global_permission = ResourceManager::getGlobalResourcePermission( + $user + ); + } + + if (in_array($global_permission, $this->accepted_permission_levels)) { + //Retrieve all resources where the user has sufficient permissions. + //There may be resources where the user has explicitly lower + //permissions and we must therefore exclude these resources. + $lower_permission_levels = ResourceManager::getLowerPermissionLevels( + $global_permission + ); + $sql .= "AND resources.id NOT IN ( + SELECT resource_id + FROM + resource_permissions + WHERE + user_id = :user_id + AND + perms IN ( :lower_perms ) + UNION + SELECT resource_id + FROM + resource_temporary_permissions + WHERE + user_id = :user_id + AND + perms IN ( :lower_perms ) + AND + begin <= :now + AND + end >= :now + )"; + $sql_params = array_merge( + $sql_params, + [ + 'user_id' => $user->id, + 'lower_perms' => $lower_permission_levels, + 'now' => time() + ] + ); + } else { + //Only retrieve those resources where the user has direct + //sufficient permissions. + $sql .= "AND resources.id IN ( + SELECT resource_id + FROM + resource_permissions + WHERE + user_id = :user_id + AND + perms IN ( :perms ) + UNION + SELECT resource_id + FROM + resource_temporary_permissions + WHERE + user_id = :user_id + AND + perms IN ( :perms ) + AND + begin <= :now + AND + end >= :now + )"; + $sql_params = array_merge( + $sql_params, + [ + 'user_id' => $user->id, + 'perms' => $this->accepted_permission_levels, + 'now' => time() + ] + ); + } + } + + $sql .= " ORDER BY resources.name ASC + LIMIT :limit OFFSET :offset "; + $sql_params['limit'] = $limit; + $sql_params['offset'] = $offset; + + $rooms = Room::findBySql( + $sql, + $sql_params + ); + + $results = []; + foreach ($rooms as $room) { + $additional_text = ''; + if (count($this->additional_display_properties)) { + $additional_values = []; + foreach ($this->additional_display_properties as $ap) { + $additional_values[] = $room->getProperty($ap); + } + $additional_text = implode(', ', $additional_values); + } + $name = $room->name; + if($additional_text) { + $name .= sprintf( + $this->additional_property_format, + $additional_text + ); + } + $results[] = [ + $room->id, + $name + ]; + } + return $results; + } +} diff --git a/lib/classes/searchtypes/SQLSearch.class.php b/lib/classes/searchtypes/SQLSearch.class.php deleted file mode 100644 index 86aff6d..0000000 --- a/lib/classes/searchtypes/SQLSearch.class.php +++ /dev/null @@ -1,179 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * Class of type SearchType used for searches with QuickSearch - * (lib/classes/QuickSearch.class.php). You can search with a sql-syntax in the - * database. You just need to give in a query like for a PDB-prepare statement - * and at least the variable ":input" in the query (the :input will be replaced - * with the input of the QuickSearch userinput. - * [code] - * $search = new SQLSearch("SELECT username, Nachname " - * "FROM auth_user_md5 " . - * "WHERE Nachname LIKE :input ", _("Nachname suchen"), "username"); - * [/code] - * - * @author Rasmus Fuhse - * - */ - -class SQLSearch extends SearchType -{ - - protected $sql; - protected $avatarLike; - protected $title; - - /** - * - * @param string $query SQL with at least ":input" as parameter - * @param string $title - * @param string $avatarLike - * in this search. array("input_name" => "placeholder_in_sql_query") - * - * @return void - */ - public function __construct($query, $title = "", $avatarLike = "") - { - $this->sql = $query; - $this->title = $title; - $this->avatarLike = $avatarLike; - } - - /** - * returns an object of type SQLSearch with parameters to constructor - * - * @param string $query SQL with at least ":input" as parameter - * @param string $title - * @param string $avatarLike - * in this search. array("input_name" => "placeholder_in_sql_query") - * - * @return SQLSearch - */ - static public function get() - { - $class = get_called_class(); - $ref = new ReflectionClass($class); - return $ref->newInstanceArgs(func_get_args()); - } - - /** - * returns the title/description of the searchfield - * - * @return string title/description - */ - public function getTitle() - { - return $this->title; - } - - /** - * returns an adress of the avatar of the searched item (if avatar enabled) - * - * @param string $id id of the item which can be username, user_id, Seminar_id or Institut_id - * - * @return string adress of an image - */ - public function getAvatar($id) - { - switch ($this->avatarLike) { - case "username": - return Avatar::getAvatar(get_userid($id), $id)->getURL(Avatar::MEDIUM); - case "user_id": - return Avatar::getAvatar($id)->getURL(Avatar::MEDIUM); - case "Seminar_id": - case "Arbeitsgruppe_id": - return CourseAvatar::getAvatar($id)->getURL(Avatar::SMALL); - case "Institut_id": - return InstituteAvatar::getAvatar($id)->getURL(Avatar::SMALL); - default: - return ''; - } - } - - /** - * returns an html tag of the image of the searched item (if avatar enabled) - * - * @param string $id id of the item which can be username, user_id, Seminar_id or Institut_id - * @param string $size enum(NORMAL, SMALL, MEDIUM): size of the avatar - * @param array $options - * - * @return string like "" - */ - public function getAvatarImageTag($id, $size = Avatar::SMALL, $options = []) - { - switch ($this->avatarLike) { - case "username": - return Avatar::getAvatar(get_userid($id), $id)->getImageTag($size, $options); - case "user_id": - return Avatar::getAvatar($id)->getImageTag($size, $options); - case "Seminar_id": - case "Arbeitsgruppe_id": - return CourseAvatar::getAvatar($id)->getImageTag($size, $options); - case "Institut_id": - return InstituteAvatar::getAvatar($id)->getImageTag($size, $options); - default: - return ''; - } - } - - /** - * returns the results of a search - * Use the contextual_data variable to send more variables than just the input - * to the SQL. QuickSearch for example sends all other variables of the same - * -tag here. - * - * @param string $input the search-word(s) - * @param array $contextual_data an associative array with more variables - * @param int $limit maximum number of results (default: all) - * @param int $offset return results starting from this row (default: 0) - * - * @return array array(array(), ...) - */ - public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) - { - $db = DBManager::get(); - $sql = $this->sql; - if ($offset || $limit != PHP_INT_MAX) { - $sql .= sprintf(' LIMIT %d, %d', $offset, $limit); - } - $statement = $db->prepare($sql, [PDO::FETCH_NUM]); - $data = []; - if (is_array($contextual_data)) { - foreach ($contextual_data as $name => $value) { - if ($name !== "input" && mb_strpos($sql, ":".$name) !== false) { - $data[":".$name] = $value; - } - } - } - $data[":input"] = "%".$input."%"; - $statement->execute($data); - $results = $statement->fetchAll(); - return $results; - } - - /** - * A very simple overwrite of the same method from SearchType class. - * returns the absolute path to this class for autoincluding this class. - * - * @return string path to this class - */ - public function includePath() - { - return studip_relative_path(__FILE__); - } -} diff --git a/lib/classes/searchtypes/SQLSearch.php b/lib/classes/searchtypes/SQLSearch.php new file mode 100644 index 0000000..86aff6d --- /dev/null +++ b/lib/classes/searchtypes/SQLSearch.php @@ -0,0 +1,179 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * Class of type SearchType used for searches with QuickSearch + * (lib/classes/QuickSearch.class.php). You can search with a sql-syntax in the + * database. You just need to give in a query like for a PDB-prepare statement + * and at least the variable ":input" in the query (the :input will be replaced + * with the input of the QuickSearch userinput. + * [code] + * $search = new SQLSearch("SELECT username, Nachname " + * "FROM auth_user_md5 " . + * "WHERE Nachname LIKE :input ", _("Nachname suchen"), "username"); + * [/code] + * + * @author Rasmus Fuhse + * + */ + +class SQLSearch extends SearchType +{ + + protected $sql; + protected $avatarLike; + protected $title; + + /** + * + * @param string $query SQL with at least ":input" as parameter + * @param string $title + * @param string $avatarLike + * in this search. array("input_name" => "placeholder_in_sql_query") + * + * @return void + */ + public function __construct($query, $title = "", $avatarLike = "") + { + $this->sql = $query; + $this->title = $title; + $this->avatarLike = $avatarLike; + } + + /** + * returns an object of type SQLSearch with parameters to constructor + * + * @param string $query SQL with at least ":input" as parameter + * @param string $title + * @param string $avatarLike + * in this search. array("input_name" => "placeholder_in_sql_query") + * + * @return SQLSearch + */ + static public function get() + { + $class = get_called_class(); + $ref = new ReflectionClass($class); + return $ref->newInstanceArgs(func_get_args()); + } + + /** + * returns the title/description of the searchfield + * + * @return string title/description + */ + public function getTitle() + { + return $this->title; + } + + /** + * returns an adress of the avatar of the searched item (if avatar enabled) + * + * @param string $id id of the item which can be username, user_id, Seminar_id or Institut_id + * + * @return string adress of an image + */ + public function getAvatar($id) + { + switch ($this->avatarLike) { + case "username": + return Avatar::getAvatar(get_userid($id), $id)->getURL(Avatar::MEDIUM); + case "user_id": + return Avatar::getAvatar($id)->getURL(Avatar::MEDIUM); + case "Seminar_id": + case "Arbeitsgruppe_id": + return CourseAvatar::getAvatar($id)->getURL(Avatar::SMALL); + case "Institut_id": + return InstituteAvatar::getAvatar($id)->getURL(Avatar::SMALL); + default: + return ''; + } + } + + /** + * returns an html tag of the image of the searched item (if avatar enabled) + * + * @param string $id id of the item which can be username, user_id, Seminar_id or Institut_id + * @param string $size enum(NORMAL, SMALL, MEDIUM): size of the avatar + * @param array $options + * + * @return string like "" + */ + public function getAvatarImageTag($id, $size = Avatar::SMALL, $options = []) + { + switch ($this->avatarLike) { + case "username": + return Avatar::getAvatar(get_userid($id), $id)->getImageTag($size, $options); + case "user_id": + return Avatar::getAvatar($id)->getImageTag($size, $options); + case "Seminar_id": + case "Arbeitsgruppe_id": + return CourseAvatar::getAvatar($id)->getImageTag($size, $options); + case "Institut_id": + return InstituteAvatar::getAvatar($id)->getImageTag($size, $options); + default: + return ''; + } + } + + /** + * returns the results of a search + * Use the contextual_data variable to send more variables than just the input + * to the SQL. QuickSearch for example sends all other variables of the same + * -tag here. + * + * @param string $input the search-word(s) + * @param array $contextual_data an associative array with more variables + * @param int $limit maximum number of results (default: all) + * @param int $offset return results starting from this row (default: 0) + * + * @return array array(array(), ...) + */ + public function getResults($input, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) + { + $db = DBManager::get(); + $sql = $this->sql; + if ($offset || $limit != PHP_INT_MAX) { + $sql .= sprintf(' LIMIT %d, %d', $offset, $limit); + } + $statement = $db->prepare($sql, [PDO::FETCH_NUM]); + $data = []; + if (is_array($contextual_data)) { + foreach ($contextual_data as $name => $value) { + if ($name !== "input" && mb_strpos($sql, ":".$name) !== false) { + $data[":".$name] = $value; + } + } + } + $data[":input"] = "%".$input."%"; + $statement->execute($data); + $results = $statement->fetchAll(); + return $results; + } + + /** + * A very simple overwrite of the same method from SearchType class. + * returns the absolute path to this class for autoincluding this class. + * + * @return string path to this class + */ + public function includePath() + { + return studip_relative_path(__FILE__); + } +} diff --git a/lib/classes/searchtypes/SearchType.class.php b/lib/classes/searchtypes/SearchType.class.php deleted file mode 100644 index d46d57e..0000000 --- a/lib/classes/searchtypes/SearchType.class.php +++ /dev/null @@ -1,107 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * A class-structure for alle search-objects in Stud.IP. - * It is (mainly?) used in QuickSearch to display searchresults and the - * layout of them. - * - * @author Rasmus Fuhse - * - */ -abstract class SearchType -{ - public $extendedLayout = false; - - - /** - * title of the search like "search for courses" or just "courses" - * - * @return string - */ - public function getTitle() - { - return ""; - } - - /** - * Returns an URL to a picture of that type. Return "" for nothing found. - * For example: "return CourseAvatar::getAvatar($id)->getURL(Avatar::SMALL)". - * - * @param string $id - * - * @return: string URL to a picture - */ - public function getAvatar($id) - { - return ""; - } - - /** - * Returns an HTML-Tag of a picture of that type. Return "" for nothing found. - * For example: "return CourseAvatar::getAvatar($id)->getImageTag(Avatar::SMALL)". - * - * @param string $id - * - * @return string HTML of a picture - */ - public function getAvatarImageTag($id) - { - return ""; - } - - /** - * Returns the results to a given keyword. To get the results is the - * job of this routine and it does not even need to come from a database. - * The results should be an array in the form - * array ( - * array($key, $name), - * array($key, $name), - * ... - * ) - * where $key is an identifier like user_id and $name is a displayed text - * that should appear to represent that ID. - * - * @param string $keyword - * @param string $contextual_data - * @param int $limit maximum number of results (default: all) - * @param int $offset return results starting from this row (default: 0) - * - * @return array - */ - public function getResults($keyword, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) - { - return [["", _("Die Suchklasse, die Sie verwenden, enthält keine Methode getResults.")]]; - } - - public function __toString() - { - $query_id = md5(serialize($this)); - $_SESSION['QuickSearches'][$query_id]['object'] = serialize($this); - $_SESSION['QuickSearches'][$query_id]['includePath'] = $this->includePath(); - $_SESSION['QuickSearches'][$query_id]['time'] = time(); - return $query_id; - } - - /** - * Returns the path to this file, so that this class can be autoloaded and is - * always available when necessary. - * Should be: "return __file__;" - * - * @return string path to this file - */ - abstract public function includePath(); -} - diff --git a/lib/classes/searchtypes/SearchType.php b/lib/classes/searchtypes/SearchType.php new file mode 100644 index 0000000..d46d57e --- /dev/null +++ b/lib/classes/searchtypes/SearchType.php @@ -0,0 +1,107 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * A class-structure for alle search-objects in Stud.IP. + * It is (mainly?) used in QuickSearch to display searchresults and the + * layout of them. + * + * @author Rasmus Fuhse + * + */ +abstract class SearchType +{ + public $extendedLayout = false; + + + /** + * title of the search like "search for courses" or just "courses" + * + * @return string + */ + public function getTitle() + { + return ""; + } + + /** + * Returns an URL to a picture of that type. Return "" for nothing found. + * For example: "return CourseAvatar::getAvatar($id)->getURL(Avatar::SMALL)". + * + * @param string $id + * + * @return: string URL to a picture + */ + public function getAvatar($id) + { + return ""; + } + + /** + * Returns an HTML-Tag of a picture of that type. Return "" for nothing found. + * For example: "return CourseAvatar::getAvatar($id)->getImageTag(Avatar::SMALL)". + * + * @param string $id + * + * @return string HTML of a picture + */ + public function getAvatarImageTag($id) + { + return ""; + } + + /** + * Returns the results to a given keyword. To get the results is the + * job of this routine and it does not even need to come from a database. + * The results should be an array in the form + * array ( + * array($key, $name), + * array($key, $name), + * ... + * ) + * where $key is an identifier like user_id and $name is a displayed text + * that should appear to represent that ID. + * + * @param string $keyword + * @param string $contextual_data + * @param int $limit maximum number of results (default: all) + * @param int $offset return results starting from this row (default: 0) + * + * @return array + */ + public function getResults($keyword, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) + { + return [["", _("Die Suchklasse, die Sie verwenden, enthält keine Methode getResults.")]]; + } + + public function __toString() + { + $query_id = md5(serialize($this)); + $_SESSION['QuickSearches'][$query_id]['object'] = serialize($this); + $_SESSION['QuickSearches'][$query_id]['includePath'] = $this->includePath(); + $_SESSION['QuickSearches'][$query_id]['time'] = time(); + return $query_id; + } + + /** + * Returns the path to this file, so that this class can be autoloaded and is + * always available when necessary. + * Should be: "return __file__;" + * + * @return string path to this file + */ + abstract public function includePath(); +} + diff --git a/lib/classes/searchtypes/SeminarSearch.class.php b/lib/classes/searchtypes/SeminarSearch.class.php deleted file mode 100644 index e490228..0000000 --- a/lib/classes/searchtypes/SeminarSearch.class.php +++ /dev/null @@ -1,101 +0,0 @@ -, Suchi & Berg GmbH - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP -*/ - -class SeminarSearch extends SearchType -{ - - /** - * title of the search like "search for courses" or just "courses" - * @return string - */ - public function getTitle() { - return _("Veranstaltungen suchen"); - } - - /** - * Returns the results to a given keyword. To get the results is the - * job of this routine and it does not even need to come from a database. - * The results should be an array in the form - * array ( - * array($key, $name), - * array($key, $name), - * ... - * ) - * where $key is an identifier like user_id and $name is a displayed text - * that should appear to represent that ID. - * @param keyword: string - * @param array $contextual_data an associative array with more variables - * @param int $limit maximum number of results (default: all) - * @param int $offset return results starting from this row (default: 0) - * @return array - */ - public function getResults($keyword, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) { - $search_helper = new StudipSemSearchHelper(); - $search_helper->setParams( - [ - 'quick_search' => $keyword, - 'qs_choose' => $contextual_data['search_sem_qs_choose'] ?? 'all', - 'sem' => $contextual_data['search_sem_sem'] ?? 'all', - 'category' => $contextual_data['search_sem_category'] ?? null, - 'scope_choose' => $contextual_data['search_sem_scope_choose'] ?? null, - 'range_choose' => $contextual_data['search_sem_range_choose'] ?? null, - ], - !(is_object($GLOBALS['perm']) - && $GLOBALS['perm']->have_perm( - Config::Get()->SEM_VISIBILITY_PERM))); - $search_helper->doSearch(); - $result = $search_helper->getSearchResultAsArray(); - - if (empty($result)) { - return []; - } - - $query = "SELECT s.Seminar_id, CONCAT_WS(' ', s.VeranstaltungsNummer, s.name, CONCAT(' (', - IF(semester_courses.semester_id IS NULL, '" . _('unbegrenzt') . "', - IF(COUNT(DISTINCT semester_courses.semester_id) > 1, CONCAT_WS(' - ', (SELECT start_semester.name FROM `semester_data` AS start_semester WHERE start_semester.semester_id = semester_courses.semester_id ORDER BY `beginn` ASC LIMIT 1), (SELECT end_semester.name FROM `semester_data` AS end_semester WHERE end_semester.semester_id = semester_courses.semester_id ORDER BY `beginn` DESC LIMIT 1)), sem1.name)), ')')) AS Name - FROM seminare AS s - LEFT JOIN semester_courses ON (semester_courses.course_id = s.Seminar_id) - LEFT JOIN `semester_data` sem1 ON (semester_courses.semester_id = sem1.semester_id) - LEFT JOIN seminar_user AS su ON (su.Seminar_id = s.Seminar_id AND su.status='dozent') - LEFT JOIN auth_user_md5 USING (user_id) - WHERE s.Seminar_id IN (?) - GROUP BY s.Seminar_id"; - if (Config::get()->IMPORTANT_SEMNUMBER) { - $query .= " ORDER BY IFNULL(MAX(sem1.beginn), s.start_time) DESC, s.VeranstaltungsNummer, s.Name"; - } else { - $query .= " ORDER BY IFNULL(MAX(sem1.beginn), s.start_time) DESC, s.Name"; - } - $statement = DBManager::get()->prepare($query); - $statement->execute([ - array_slice($result, $offset, $limit) ?: '' - ]); - return $statement->fetchAll(PDO::FETCH_NUM); - } - - - /** - * Returns the path to this file, so that this class can be autoloaded and is - * always available when necessary. - * Should be: "return __file__;" - * - * @return string path to this file - */ - public function includePath() - { - return studip_relative_path(__FILE__); - } -} diff --git a/lib/classes/searchtypes/SeminarSearch.php b/lib/classes/searchtypes/SeminarSearch.php new file mode 100644 index 0000000..e490228 --- /dev/null +++ b/lib/classes/searchtypes/SeminarSearch.php @@ -0,0 +1,101 @@ +, Suchi & Berg GmbH + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class SeminarSearch extends SearchType +{ + + /** + * title of the search like "search for courses" or just "courses" + * @return string + */ + public function getTitle() { + return _("Veranstaltungen suchen"); + } + + /** + * Returns the results to a given keyword. To get the results is the + * job of this routine and it does not even need to come from a database. + * The results should be an array in the form + * array ( + * array($key, $name), + * array($key, $name), + * ... + * ) + * where $key is an identifier like user_id and $name is a displayed text + * that should appear to represent that ID. + * @param keyword: string + * @param array $contextual_data an associative array with more variables + * @param int $limit maximum number of results (default: all) + * @param int $offset return results starting from this row (default: 0) + * @return array + */ + public function getResults($keyword, $contextual_data = [], $limit = PHP_INT_MAX, $offset = 0) { + $search_helper = new StudipSemSearchHelper(); + $search_helper->setParams( + [ + 'quick_search' => $keyword, + 'qs_choose' => $contextual_data['search_sem_qs_choose'] ?? 'all', + 'sem' => $contextual_data['search_sem_sem'] ?? 'all', + 'category' => $contextual_data['search_sem_category'] ?? null, + 'scope_choose' => $contextual_data['search_sem_scope_choose'] ?? null, + 'range_choose' => $contextual_data['search_sem_range_choose'] ?? null, + ], + !(is_object($GLOBALS['perm']) + && $GLOBALS['perm']->have_perm( + Config::Get()->SEM_VISIBILITY_PERM))); + $search_helper->doSearch(); + $result = $search_helper->getSearchResultAsArray(); + + if (empty($result)) { + return []; + } + + $query = "SELECT s.Seminar_id, CONCAT_WS(' ', s.VeranstaltungsNummer, s.name, CONCAT(' (', + IF(semester_courses.semester_id IS NULL, '" . _('unbegrenzt') . "', + IF(COUNT(DISTINCT semester_courses.semester_id) > 1, CONCAT_WS(' - ', (SELECT start_semester.name FROM `semester_data` AS start_semester WHERE start_semester.semester_id = semester_courses.semester_id ORDER BY `beginn` ASC LIMIT 1), (SELECT end_semester.name FROM `semester_data` AS end_semester WHERE end_semester.semester_id = semester_courses.semester_id ORDER BY `beginn` DESC LIMIT 1)), sem1.name)), ')')) AS Name + FROM seminare AS s + LEFT JOIN semester_courses ON (semester_courses.course_id = s.Seminar_id) + LEFT JOIN `semester_data` sem1 ON (semester_courses.semester_id = sem1.semester_id) + LEFT JOIN seminar_user AS su ON (su.Seminar_id = s.Seminar_id AND su.status='dozent') + LEFT JOIN auth_user_md5 USING (user_id) + WHERE s.Seminar_id IN (?) + GROUP BY s.Seminar_id"; + if (Config::get()->IMPORTANT_SEMNUMBER) { + $query .= " ORDER BY IFNULL(MAX(sem1.beginn), s.start_time) DESC, s.VeranstaltungsNummer, s.Name"; + } else { + $query .= " ORDER BY IFNULL(MAX(sem1.beginn), s.start_time) DESC, s.Name"; + } + $statement = DBManager::get()->prepare($query); + $statement->execute([ + array_slice($result, $offset, $limit) ?: '' + ]); + return $statement->fetchAll(PDO::FETCH_NUM); + } + + + /** + * Returns the path to this file, so that this class can be autoloaded and is + * always available when necessary. + * Should be: "return __file__;" + * + * @return string path to this file + */ + public function includePath() + { + return studip_relative_path(__FILE__); + } +} diff --git a/lib/classes/searchtypes/StandardSearch.class.php b/lib/classes/searchtypes/StandardSearch.class.php deleted file mode 100644 index 837a849..0000000 --- a/lib/classes/searchtypes/StandardSearch.class.php +++ /dev/null @@ -1,206 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * Class of type SearchType used for searches with QuickSearch - * (lib/classes/QuickSearch.class.php). You can search with a sql-syntax in the - * database. You just need to give in a query like for a PDB-prepare statement - * and at least the variable ":input" in the query (the :input will be replaced - * with the input of the QuickSearch userinput. - * [code] - * $search = new SQLSearch("username"); - * [/code] - * - * @author Rasmus Fuhse - * - */ - -class StandardSearch extends SQLSearch -{ - - public $search; - public $search_settings; - - /** - * - * @param string $search The search type. - * - * @param Array $search_settings Settings for the selected seach type. - * Depending on the search type different settings are possible - * which can change the output or the display of the output - * of the search. The array must be an associative array - * with the setting as array key. - * The following settings are implemented: - * Search type 'room': - * - display_seats: If set to true, the seats will be displayed - * after the name of the room. - * - * @return void - */ - public function __construct($search, $search_settings = []) - { - if (is_array($search_settings)) { - $this->search_settings = $search_settings; - } - - $this->avatarLike = $this->search = $search; - $this->sql = $this->getSQL(); - } - - - /** - * returns the title/description of the searchfield - * - * @return string title/description - */ - public function getTitle() - { - switch ($this->search) { - case "username": - case "user_id": - return _("Person suchen"); - case "Seminar_id": - case "AnySeminar_id": - return _("Veranstaltung suchen"); - case "Arbeitsgruppe_id": - return _("Arbeitsgruppe suchen"); - case "Institut_id": - return _("Einrichtung suchen"); - default: - throw new UnexpectedValueException("Invalid search type {$this->search}"); - } - } - - /** - * returns a sql-string appropriate for the searchtype of the current class - * - * @return string - */ - private function getSQL() - { - $semester = " CONCAT('(',IFNULL(GROUP_CONCAT(DISTINCT sem1.name ORDER BY sem1.beginn SEPARATOR '-'),'" . _('unbegrenzt') . "'),')')"; - switch ($this->search) { - case "username": - $this->extendedLayout = true; - $sql = "SELECT DISTINCT auth_user_md5.username"; - if (empty($this->search_settings['simple_name'])) { - $sql .= ", CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms "; - } else { - $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) "; - } - $sql .= "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " . - "LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) " . - "WHERE (CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . - "OR auth_user_md5.username LIKE :input) AND " . get_vis_query('auth_user_md5', 'search') . - " ORDER BY Nachname ASC, Vorname ASC"; - return $sql; - case "user_id": - $this->extendedLayout = true; - $sql = "SELECT DISTINCT auth_user_md5.user_id"; - if (empty($this->search_settings['simple_name'])) { - $sql .= ", CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms "; - } else { - $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) "; - } - $sql .= "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " . - "LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) " . - "WHERE (CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . - "OR auth_user_md5.username LIKE :input) AND " . get_vis_query('auth_user_md5', 'search') . - " ORDER BY Nachname ASC, Vorname ASC"; - return $sql; - case "Seminar_id": - return "SELECT seminare.Seminar_id, CONCAT_WS(' ', seminare.VeranstaltungsNummer, seminare.Name, ".$semester.") " . - "FROM seminare " . - "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id) " . - "LEFT JOIN `semester_data` sem1 ON (semester_courses.semester_id = sem1.semester_id) " . - "LEFT JOIN seminar_user ON (seminar_user.Seminar_id = seminare.Seminar_id AND seminar_user.status = 'dozent') " . - "LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = seminar_user.user_id) " . - "WHERE (seminare.Name LIKE :input " . - "OR CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE :input " . - "OR seminare.VeranstaltungsNummer LIKE :input " . - "OR seminare.Untertitel LIKE :input " . - "OR seminare.Beschreibung LIKE :input " . - "OR seminare.Ort LIKE :input " . - "OR seminare.Sonstiges LIKE :input) " . - "AND seminare.visible = 1 " . - "AND seminare.status NOT IN ('".implode("', '", studygroup_sem_types())."') " . - " GROUP BY seminare.seminar_id ORDER BY sem1.`beginn` DESC, " . - (Config::get()->IMPORTANT_SEMNUMBER ? "seminare.`VeranstaltungsNummer`, " : "") . - "seminare.`Name`"; - case "AnySeminar_id": - return "SELECT seminare.Seminar_id, CONCAT_WS(' ', seminare.VeranstaltungsNummer, seminare.Name, ".$semester.") " . - "FROM seminare " . - "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id) " . - "LEFT JOIN `semester_data` sem1 ON (semester_courses.semester_id = sem1.semester_id) " . - "LEFT JOIN seminar_user ON (seminar_user.Seminar_id = seminare.Seminar_id AND seminar_user.status = 'dozent') " . - "LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = seminar_user.user_id) " . - "WHERE (seminare.Name LIKE :input " . - "OR CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . - "OR seminare.VeranstaltungsNummer LIKE :input " . - "OR seminare.Untertitel LIKE :input " . - "OR seminare.Beschreibung LIKE :input " . - "OR seminare.Ort LIKE :input " . - "OR seminare.Sonstiges LIKE :input) " . - " GROUP BY seminare.seminar_id ORDER BY sem1.`beginn` DESC, " . - (Config::get()->IMPORTANT_SEMNUMBER ? "seminare.`VeranstaltungsNummer`, " : "") . - "seminare.`Name`"; - case "Arbeitsgruppe_id": - return "SELECT DISTINCT seminare.Seminar_id, seminare.Name " . - "FROM seminare " . - "LEFT JOIN seminar_user ON (seminar_user.Seminar_id = seminare.Seminar_id AND seminar_user.status = 'dozent') " . - "LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = seminar_user.user_id) " . - "WHERE (seminare.Name LIKE :input " . - "OR CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . - "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . - "OR seminare.VeranstaltungsNummer LIKE :input " . - "OR seminare.Untertitel LIKE :input " . - "OR seminare.Beschreibung LIKE :input " . - "OR seminare.Ort LIKE :input " . - "OR seminare.Sonstiges LIKE :input) " . - "AND seminare.visible = 1 " . - "AND seminare.status IN ('".implode("', '", studygroup_sem_types())."') " . - "ORDER BY seminare.Name"; - case "Institut_id": - return "SELECT DISTINCT Institute.Institut_id, Institute.Name " . - "FROM Institute " . - "LEFT JOIN range_tree ON (range_tree.item_id = Institute.Institut_id) " . - "WHERE Institute.Name LIKE :input " . - "OR Institute.Strasse LIKE :input " . - "OR Institute.email LIKE :input " . - "OR range_tree.name LIKE :input " . - "ORDER BY Institute.Name"; - default: - throw new UnexpectedValueException("Invalid search type {$this->search}"); - } - } - - /** - * A very simple overwrite of the same method from SearchType class. - * returns the absolute path to this class for autoincluding this class. - * - * @return: path to this class - */ - public function includePath() - { - return studip_relative_path(__FILE__); - } -} diff --git a/lib/classes/searchtypes/StandardSearch.php b/lib/classes/searchtypes/StandardSearch.php new file mode 100644 index 0000000..837a849 --- /dev/null +++ b/lib/classes/searchtypes/StandardSearch.php @@ -0,0 +1,206 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * Class of type SearchType used for searches with QuickSearch + * (lib/classes/QuickSearch.class.php). You can search with a sql-syntax in the + * database. You just need to give in a query like for a PDB-prepare statement + * and at least the variable ":input" in the query (the :input will be replaced + * with the input of the QuickSearch userinput. + * [code] + * $search = new SQLSearch("username"); + * [/code] + * + * @author Rasmus Fuhse + * + */ + +class StandardSearch extends SQLSearch +{ + + public $search; + public $search_settings; + + /** + * + * @param string $search The search type. + * + * @param Array $search_settings Settings for the selected seach type. + * Depending on the search type different settings are possible + * which can change the output or the display of the output + * of the search. The array must be an associative array + * with the setting as array key. + * The following settings are implemented: + * Search type 'room': + * - display_seats: If set to true, the seats will be displayed + * after the name of the room. + * + * @return void + */ + public function __construct($search, $search_settings = []) + { + if (is_array($search_settings)) { + $this->search_settings = $search_settings; + } + + $this->avatarLike = $this->search = $search; + $this->sql = $this->getSQL(); + } + + + /** + * returns the title/description of the searchfield + * + * @return string title/description + */ + public function getTitle() + { + switch ($this->search) { + case "username": + case "user_id": + return _("Person suchen"); + case "Seminar_id": + case "AnySeminar_id": + return _("Veranstaltung suchen"); + case "Arbeitsgruppe_id": + return _("Arbeitsgruppe suchen"); + case "Institut_id": + return _("Einrichtung suchen"); + default: + throw new UnexpectedValueException("Invalid search type {$this->search}"); + } + } + + /** + * returns a sql-string appropriate for the searchtype of the current class + * + * @return string + */ + private function getSQL() + { + $semester = " CONCAT('(',IFNULL(GROUP_CONCAT(DISTINCT sem1.name ORDER BY sem1.beginn SEPARATOR '-'),'" . _('unbegrenzt') . "'),')')"; + switch ($this->search) { + case "username": + $this->extendedLayout = true; + $sql = "SELECT DISTINCT auth_user_md5.username"; + if (empty($this->search_settings['simple_name'])) { + $sql .= ", CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms "; + } else { + $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) "; + } + $sql .= "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " . + "LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) " . + "WHERE (CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . + "OR auth_user_md5.username LIKE :input) AND " . get_vis_query('auth_user_md5', 'search') . + " ORDER BY Nachname ASC, Vorname ASC"; + return $sql; + case "user_id": + $this->extendedLayout = true; + $sql = "SELECT DISTINCT auth_user_md5.user_id"; + if (empty($this->search_settings['simple_name'])) { + $sql .= ", CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms "; + } else { + $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) "; + } + $sql .= "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " . + "LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) " . + "WHERE (CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . + "OR auth_user_md5.username LIKE :input) AND " . get_vis_query('auth_user_md5', 'search') . + " ORDER BY Nachname ASC, Vorname ASC"; + return $sql; + case "Seminar_id": + return "SELECT seminare.Seminar_id, CONCAT_WS(' ', seminare.VeranstaltungsNummer, seminare.Name, ".$semester.") " . + "FROM seminare " . + "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id) " . + "LEFT JOIN `semester_data` sem1 ON (semester_courses.semester_id = sem1.semester_id) " . + "LEFT JOIN seminar_user ON (seminar_user.Seminar_id = seminare.Seminar_id AND seminar_user.status = 'dozent') " . + "LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = seminar_user.user_id) " . + "WHERE (seminare.Name LIKE :input " . + "OR CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE :input " . + "OR seminare.VeranstaltungsNummer LIKE :input " . + "OR seminare.Untertitel LIKE :input " . + "OR seminare.Beschreibung LIKE :input " . + "OR seminare.Ort LIKE :input " . + "OR seminare.Sonstiges LIKE :input) " . + "AND seminare.visible = 1 " . + "AND seminare.status NOT IN ('".implode("', '", studygroup_sem_types())."') " . + " GROUP BY seminare.seminar_id ORDER BY sem1.`beginn` DESC, " . + (Config::get()->IMPORTANT_SEMNUMBER ? "seminare.`VeranstaltungsNummer`, " : "") . + "seminare.`Name`"; + case "AnySeminar_id": + return "SELECT seminare.Seminar_id, CONCAT_WS(' ', seminare.VeranstaltungsNummer, seminare.Name, ".$semester.") " . + "FROM seminare " . + "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id) " . + "LEFT JOIN `semester_data` sem1 ON (semester_courses.semester_id = sem1.semester_id) " . + "LEFT JOIN seminar_user ON (seminar_user.Seminar_id = seminare.Seminar_id AND seminar_user.status = 'dozent') " . + "LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = seminar_user.user_id) " . + "WHERE (seminare.Name LIKE :input " . + "OR CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . + "OR seminare.VeranstaltungsNummer LIKE :input " . + "OR seminare.Untertitel LIKE :input " . + "OR seminare.Beschreibung LIKE :input " . + "OR seminare.Ort LIKE :input " . + "OR seminare.Sonstiges LIKE :input) " . + " GROUP BY seminare.seminar_id ORDER BY sem1.`beginn` DESC, " . + (Config::get()->IMPORTANT_SEMNUMBER ? "seminare.`VeranstaltungsNummer`, " : "") . + "seminare.`Name`"; + case "Arbeitsgruppe_id": + return "SELECT DISTINCT seminare.Seminar_id, seminare.Name " . + "FROM seminare " . + "LEFT JOIN seminar_user ON (seminar_user.Seminar_id = seminare.Seminar_id AND seminar_user.status = 'dozent') " . + "LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = seminar_user.user_id) " . + "WHERE (seminare.Name LIKE :input " . + "OR CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " . + "OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " . + "OR seminare.VeranstaltungsNummer LIKE :input " . + "OR seminare.Untertitel LIKE :input " . + "OR seminare.Beschreibung LIKE :input " . + "OR seminare.Ort LIKE :input " . + "OR seminare.Sonstiges LIKE :input) " . + "AND seminare.visible = 1 " . + "AND seminare.status IN ('".implode("', '", studygroup_sem_types())."') " . + "ORDER BY seminare.Name"; + case "Institut_id": + return "SELECT DISTINCT Institute.Institut_id, Institute.Name " . + "FROM Institute " . + "LEFT JOIN range_tree ON (range_tree.item_id = Institute.Institut_id) " . + "WHERE Institute.Name LIKE :input " . + "OR Institute.Strasse LIKE :input " . + "OR Institute.email LIKE :input " . + "OR range_tree.name LIKE :input " . + "ORDER BY Institute.Name"; + default: + throw new UnexpectedValueException("Invalid search type {$this->search}"); + } + } + + /** + * A very simple overwrite of the same method from SearchType class. + * returns the absolute path to this class for autoincluding this class. + * + * @return: path to this class + */ + public function includePath() + { + return studip_relative_path(__FILE__); + } +} diff --git a/lib/classes/searchtypes/TreeSearch.class.php b/lib/classes/searchtypes/TreeSearch.class.php deleted file mode 100644 index 2fecf60..0000000 --- a/lib/classes/searchtypes/TreeSearch.class.php +++ /dev/null @@ -1,96 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -class TreeSearch extends StandardSearch -{ - /** - * - * @param string $search The search type. - * - * @param Array $search_settings Settings for the selected seach type. - * Depending on the search type different settings are possible - * which can change the output or the display of the output - * of the search. The array must be an associative array - * with the setting as array key. - * The following settings are implemented: - * Search type 'room': - * - display_seats: If set to true, the seats will be displayed - * after the name of the room. - * - * @return void - */ - public function __construct($search, $search_settings = []) - { - if (is_array($search_settings)) { - $this->search_settings = $search_settings; - } - - $this->avatarLike = $this->search = $search; - $this->sql = $this->getSQL(); - } - - /** - * returns the title/description of the searchfield - * - * @return string title/description - */ - public function getTitle() - { - switch ($this->search) { - case 'sem_tree_id': - return _('Studienbereich suchen'); - case 'range_tree_id': - return _('Eintrag in der Einrichtungshierarchie suchen'); - default: - throw new UnexpectedValueException('Invalid search type {$this->search}'); - } - } - - /** - * returns a sql-string appropriate for the searchtype of the current class - * - * @return string - */ - private function getSQL() - { - switch ($this->search) { - case 'sem_tree_id': - return "SELECT `sem_tree_id`, `name` - FROM `sem_tree` - WHERE `name` LIKE :input - OR `info` LIKE :input - ORDER BY `name`"; - case 'range_tree_id': - return "SELECT t.`item_id`, IF(t.`studip_object_id` IS NULL, t.`name`, i.`name`) - FROM `range_tree` t - LEFT JOIN `Institute` i ON (i.`Institut_id` = t.`studip_object_id`) - WHERE t.`name` LIKE :input - OR i.`Name` LIKE :input - ORDER BY t.`name`, i.`Name`"; - default: - throw new UnexpectedValueException("Invalid search type {$this->search}"); - } - } - - /** - * A very simple overwrite of the same method from SearchType class. - * returns the absolute path to this class for autoincluding this class. - * - * @return: path to this class - */ - public function includePath() - { - return studip_relative_path(__FILE__); - } -} diff --git a/lib/classes/searchtypes/TreeSearch.php b/lib/classes/searchtypes/TreeSearch.php new file mode 100644 index 0000000..2fecf60 --- /dev/null +++ b/lib/classes/searchtypes/TreeSearch.php @@ -0,0 +1,96 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class TreeSearch extends StandardSearch +{ + /** + * + * @param string $search The search type. + * + * @param Array $search_settings Settings for the selected seach type. + * Depending on the search type different settings are possible + * which can change the output or the display of the output + * of the search. The array must be an associative array + * with the setting as array key. + * The following settings are implemented: + * Search type 'room': + * - display_seats: If set to true, the seats will be displayed + * after the name of the room. + * + * @return void + */ + public function __construct($search, $search_settings = []) + { + if (is_array($search_settings)) { + $this->search_settings = $search_settings; + } + + $this->avatarLike = $this->search = $search; + $this->sql = $this->getSQL(); + } + + /** + * returns the title/description of the searchfield + * + * @return string title/description + */ + public function getTitle() + { + switch ($this->search) { + case 'sem_tree_id': + return _('Studienbereich suchen'); + case 'range_tree_id': + return _('Eintrag in der Einrichtungshierarchie suchen'); + default: + throw new UnexpectedValueException('Invalid search type {$this->search}'); + } + } + + /** + * returns a sql-string appropriate for the searchtype of the current class + * + * @return string + */ + private function getSQL() + { + switch ($this->search) { + case 'sem_tree_id': + return "SELECT `sem_tree_id`, `name` + FROM `sem_tree` + WHERE `name` LIKE :input + OR `info` LIKE :input + ORDER BY `name`"; + case 'range_tree_id': + return "SELECT t.`item_id`, IF(t.`studip_object_id` IS NULL, t.`name`, i.`name`) + FROM `range_tree` t + LEFT JOIN `Institute` i ON (i.`Institut_id` = t.`studip_object_id`) + WHERE t.`name` LIKE :input + OR i.`Name` LIKE :input + ORDER BY t.`name`, i.`Name`"; + default: + throw new UnexpectedValueException("Invalid search type {$this->search}"); + } + } + + /** + * A very simple overwrite of the same method from SearchType class. + * returns the absolute path to this class for autoincluding this class. + * + * @return: path to this class + */ + public function includePath() + { + return studip_relative_path(__FILE__); + } +} diff --git a/lib/classes/sidebar/ClipboardWidget.class.php b/lib/classes/sidebar/ClipboardWidget.class.php deleted file mode 100644 index abc0d6c..0000000 --- a/lib/classes/sidebar/ClipboardWidget.class.php +++ /dev/null @@ -1,143 +0,0 @@ - - * @license GNU General Public License v2 or later. - * @since 4.5 - */ -class ClipboardWidget extends SidebarWidget -{ - protected $draggable_items; - - /** - * clipboard_widget_id is required in the case that multiple - * clipboard widgets exist on one page. The JavaScript code - * can then distinguish each clipboard widget by its unique ID. - */ - protected $clipboard_widget_id; - - - /** - * This attribute holds the ID of the clipboard which is stored in the - * session as currently selected clipboard. - */ - protected $current_clipboard_id; - - - /** - * This attribute contains a string that shall be the title of the button - * for applying the selected clipbard to the main area - * the widget is showing when in read only mode. - */ - protected $apply_button_title; - - /** - * This widget can be initialised with the class names of allowed classes - * to limit the displayed items in a clipboard to items of specific - * classes. - */ - public function __construct($allowed_item_classes = []) - { - parent::__construct(); - - if ($allowed_item_classes) { - //Check if all allowed item classes are SimpleORMap objects - //and if the classes implement the StudipItem interface: - foreach ($allowed_item_classes as $class) { - if (!is_subclass_of($class, 'StudipItem')) { - throw new InvalidArgumentException( - sprintf( - 'The class %s does not implement the StudipItem interface which is required for clipboard items!', - htmlReady($class) - ) - ); - } - } - } else { - //Allow all StudipItem implementations: - $allowed_item_classes = ['StudipItem']; - } - - $this->allowed_item_classes = $allowed_item_classes; - $this->template = 'sidebar/clipboard-widget'; - $this->title = _('Eigene Merkzettel'); - $this->apply_button_title = _('Hauptbereich aktualisieren'); - - $this->clipboard_widget_id = md5(uniqid('clipboard_widget_id')); - - $this->updateSessionVariables(); - $this->current_clipboard_id = $_SESSION['selected_clipboard_id'] ?? ''; - - $this->setId("ClipboardWidget_{$this->clipboard_widget_id}"); - $this->setAdditionalAttribute('data-widget_id', $this->clipboard_widget_id); - $this->addLayoutCSSClass('clipboard-widget'); - } - - /** - * Updates session variables if a special POST request is made. - */ - public function updateSessionVariables() - { - if (Request::submitted('clipboard_update_session_special_action')) { - CSRFProtection::verifyUnsafeRequest(); - - $_SESSION['selected_clipboard_id'] = Request::get('selected_clipboard_id'); - } - } - - public function setApplyButtonTitle($title = '') - { - if ($title) { - $this->apply_button_title = $title; - } - } - - public function getClipboardWidgetId() - { - return $this->clipboard_widget_id; - } - - public function render($variables = []) - { - $clipboards = Clipboard::getClipboardsForUser( - $GLOBALS['user']->id - ); - - if (!$this->current_clipboard_id && $clipboards) { - $_SESSION['selected_clipboard_id'] = $clipboards[0]->id; - $this->current_clipboard_id = $clipboards[0]->id; - } - - return parent::render($variables + [ - 'clipboards' => $clipboards, - 'allowed_item_classes' => $this->allowed_item_classes, - 'clipboard_widget_id' => $this->clipboard_widget_id, - 'draggable_items' => $this->draggable_items, - 'apply_button_title' => $this->apply_button_title, - 'elements' => $this->elements, - 'selected_clipboard_id' => $this->current_clipboard_id, - ]); - } - - /** - * Adds a link to the widget - * - * @param String $label Label/content of the link - * @param String $url URL/Location of the link - * @param Icon $icon instance of class Icon for the link - * @param bool $active Pass true if the link is currently active, - * defaults to false - */ - public function &addLink($label, $url, $icon = null, $attributes = array(), $index = null) - { - if ($index === null) { - $index = 'link-' . md5($url); - } - $element = new LinkElement($label, $url, $icon, $attributes); - $this->addElement($element, $index); - return $element; - } -} diff --git a/lib/classes/sidebar/ClipboardWidget.php b/lib/classes/sidebar/ClipboardWidget.php new file mode 100644 index 0000000..abc0d6c --- /dev/null +++ b/lib/classes/sidebar/ClipboardWidget.php @@ -0,0 +1,143 @@ + + * @license GNU General Public License v2 or later. + * @since 4.5 + */ +class ClipboardWidget extends SidebarWidget +{ + protected $draggable_items; + + /** + * clipboard_widget_id is required in the case that multiple + * clipboard widgets exist on one page. The JavaScript code + * can then distinguish each clipboard widget by its unique ID. + */ + protected $clipboard_widget_id; + + + /** + * This attribute holds the ID of the clipboard which is stored in the + * session as currently selected clipboard. + */ + protected $current_clipboard_id; + + + /** + * This attribute contains a string that shall be the title of the button + * for applying the selected clipbard to the main area + * the widget is showing when in read only mode. + */ + protected $apply_button_title; + + /** + * This widget can be initialised with the class names of allowed classes + * to limit the displayed items in a clipboard to items of specific + * classes. + */ + public function __construct($allowed_item_classes = []) + { + parent::__construct(); + + if ($allowed_item_classes) { + //Check if all allowed item classes are SimpleORMap objects + //and if the classes implement the StudipItem interface: + foreach ($allowed_item_classes as $class) { + if (!is_subclass_of($class, 'StudipItem')) { + throw new InvalidArgumentException( + sprintf( + 'The class %s does not implement the StudipItem interface which is required for clipboard items!', + htmlReady($class) + ) + ); + } + } + } else { + //Allow all StudipItem implementations: + $allowed_item_classes = ['StudipItem']; + } + + $this->allowed_item_classes = $allowed_item_classes; + $this->template = 'sidebar/clipboard-widget'; + $this->title = _('Eigene Merkzettel'); + $this->apply_button_title = _('Hauptbereich aktualisieren'); + + $this->clipboard_widget_id = md5(uniqid('clipboard_widget_id')); + + $this->updateSessionVariables(); + $this->current_clipboard_id = $_SESSION['selected_clipboard_id'] ?? ''; + + $this->setId("ClipboardWidget_{$this->clipboard_widget_id}"); + $this->setAdditionalAttribute('data-widget_id', $this->clipboard_widget_id); + $this->addLayoutCSSClass('clipboard-widget'); + } + + /** + * Updates session variables if a special POST request is made. + */ + public function updateSessionVariables() + { + if (Request::submitted('clipboard_update_session_special_action')) { + CSRFProtection::verifyUnsafeRequest(); + + $_SESSION['selected_clipboard_id'] = Request::get('selected_clipboard_id'); + } + } + + public function setApplyButtonTitle($title = '') + { + if ($title) { + $this->apply_button_title = $title; + } + } + + public function getClipboardWidgetId() + { + return $this->clipboard_widget_id; + } + + public function render($variables = []) + { + $clipboards = Clipboard::getClipboardsForUser( + $GLOBALS['user']->id + ); + + if (!$this->current_clipboard_id && $clipboards) { + $_SESSION['selected_clipboard_id'] = $clipboards[0]->id; + $this->current_clipboard_id = $clipboards[0]->id; + } + + return parent::render($variables + [ + 'clipboards' => $clipboards, + 'allowed_item_classes' => $this->allowed_item_classes, + 'clipboard_widget_id' => $this->clipboard_widget_id, + 'draggable_items' => $this->draggable_items, + 'apply_button_title' => $this->apply_button_title, + 'elements' => $this->elements, + 'selected_clipboard_id' => $this->current_clipboard_id, + ]); + } + + /** + * Adds a link to the widget + * + * @param String $label Label/content of the link + * @param String $url URL/Location of the link + * @param Icon $icon instance of class Icon for the link + * @param bool $active Pass true if the link is currently active, + * defaults to false + */ + public function &addLink($label, $url, $icon = null, $attributes = array(), $index = null) + { + if ($index === null) { + $index = 'link-' . md5($url); + } + $element = new LinkElement($label, $url, $icon, $attributes); + $this->addElement($element, $index); + return $element; + } +} diff --git a/lib/classes/sidebar/InstituteSelectWidget.class.php b/lib/classes/sidebar/InstituteSelectWidget.class.php deleted file mode 100644 index ec05311..0000000 --- a/lib/classes/sidebar/InstituteSelectWidget.class.php +++ /dev/null @@ -1,111 +0,0 @@ - - * @see SelectWidget - * @since 4.5 - * @license GPL2 or any later version - * @copyright Stud.IP Core Group - */ -class InstituteSelectWidget extends SelectWidget -{ - protected $include_all_option; - - /** - * The IDs of the selected institutes. - */ - protected $selected_element_ids; - - public function __construct($url, $name, $method = 'get', $multiple = false) - { - parent::__construct(_('Einrichtung'), $url, $name, $method, $multiple); - - $this->include_all_option = false; - $this->selected_element_ids = []; - } - - - /** - * Sets the $include_all_option attribute that specifies whether an option - * for selecting all institutes shall be provided (true) or not (false). - * This defaults to true. - */ - public function includeAllOption($include_all_option = true) - { - $this->include_all_option = $include_all_option; - } - - - /** - * This method allows setting the selected elements from other sources - * than the select's name from a request. - * - * @param Array|string $element_ids The ID of one element (as string) - * or the IDs of more than one element (as array) - * which have been selected. - */ - public function setSelectedElementIds($element_ids = []) - { - if ($element_ids && !is_array($element_ids)) { - $element_ids = [$element_ids]; - } - $this->selected_element_ids = $element_ids; - } - - - public function render($variables = []) - { - if (!$this->selected_element_ids) { - if ($this->multiple) { - $this->selected_element_ids = Request::getArray($this->template_variables['name']); - } else { - $this->selected_element_ids = [Request::get($this->template_variables['name'])]; - } - } - - $institutes = Institute::getMyInstitutes($GLOBALS['user']->id); - - if ($this->include_all_option) { - $element = new SelectElement( - 'all', - _('Alle'), - in_array('all', $this->selected_element_ids) - ); - $element->setAsHeader(true); - $this->addElement($element); - } - - foreach ($institutes as $institute) { - $element = new SelectElement( - $institute['Institut_id'], - $institute['Name'], - in_array($institute['Institut_id'], $this->selected_element_ids) - ); - if ($institute['is_fak']) { - $element->setAsHeader(true); - } else { - $element->setIndentLevel(1); - } - $this->addElement($element); - - if ($institute['is_fak']) { - $sub_element_id = $institute['Institut_id'] . '_withinst'; - $sub_element = new SelectElement( - $sub_element_id, - sprintf( - _('%s + Institute'), - $institute['Name'] - ), - in_array($sub_element_id, $this->selected_element_ids) - ); - $this->addElement($sub_element); - } - } - - return parent::render($variables); - } -} diff --git a/lib/classes/sidebar/InstituteSelectWidget.php b/lib/classes/sidebar/InstituteSelectWidget.php new file mode 100644 index 0000000..ec05311 --- /dev/null +++ b/lib/classes/sidebar/InstituteSelectWidget.php @@ -0,0 +1,111 @@ + + * @see SelectWidget + * @since 4.5 + * @license GPL2 or any later version + * @copyright Stud.IP Core Group + */ +class InstituteSelectWidget extends SelectWidget +{ + protected $include_all_option; + + /** + * The IDs of the selected institutes. + */ + protected $selected_element_ids; + + public function __construct($url, $name, $method = 'get', $multiple = false) + { + parent::__construct(_('Einrichtung'), $url, $name, $method, $multiple); + + $this->include_all_option = false; + $this->selected_element_ids = []; + } + + + /** + * Sets the $include_all_option attribute that specifies whether an option + * for selecting all institutes shall be provided (true) or not (false). + * This defaults to true. + */ + public function includeAllOption($include_all_option = true) + { + $this->include_all_option = $include_all_option; + } + + + /** + * This method allows setting the selected elements from other sources + * than the select's name from a request. + * + * @param Array|string $element_ids The ID of one element (as string) + * or the IDs of more than one element (as array) + * which have been selected. + */ + public function setSelectedElementIds($element_ids = []) + { + if ($element_ids && !is_array($element_ids)) { + $element_ids = [$element_ids]; + } + $this->selected_element_ids = $element_ids; + } + + + public function render($variables = []) + { + if (!$this->selected_element_ids) { + if ($this->multiple) { + $this->selected_element_ids = Request::getArray($this->template_variables['name']); + } else { + $this->selected_element_ids = [Request::get($this->template_variables['name'])]; + } + } + + $institutes = Institute::getMyInstitutes($GLOBALS['user']->id); + + if ($this->include_all_option) { + $element = new SelectElement( + 'all', + _('Alle'), + in_array('all', $this->selected_element_ids) + ); + $element->setAsHeader(true); + $this->addElement($element); + } + + foreach ($institutes as $institute) { + $element = new SelectElement( + $institute['Institut_id'], + $institute['Name'], + in_array($institute['Institut_id'], $this->selected_element_ids) + ); + if ($institute['is_fak']) { + $element->setAsHeader(true); + } else { + $element->setIndentLevel(1); + } + $this->addElement($element); + + if ($institute['is_fak']) { + $sub_element_id = $institute['Institut_id'] . '_withinst'; + $sub_element = new SelectElement( + $sub_element_id, + sprintf( + _('%s + Institute'), + $institute['Name'] + ), + in_array($sub_element_id, $this->selected_element_ids) + ); + $this->addElement($sub_element); + } + } + + return parent::render($variables); + } +} diff --git a/lib/classes/sidebar/ResourceTreeWidget.class.php b/lib/classes/sidebar/ResourceTreeWidget.class.php deleted file mode 100644 index 60d4b29..0000000 --- a/lib/classes/sidebar/ResourceTreeWidget.class.php +++ /dev/null @@ -1,145 +0,0 @@ - - * @copyright 2017-2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.5 - */ -class ResourceTreeWidget extends SidebarWidget -{ - /** - * $root_resource is the resource whose resource tree - * shall be displayed by this widget. - * The resource itself and all its children are displayed. - */ - protected $root_resources = []; - protected $parameter_name = ''; - protected $foldable = false; - protected $current_resource_id = null; - - /** - * This widget must be initialised by providing at least one - * Resource object in an array. - * - * @param array $root_resources The root resource objects which will be - * displayed by this tree view. - * @param string $title The title of this widget. - * @param string|null $parameter_name The name of the URL parameter which - * will be set when one of the resources in the tree is selected. - * If parameter_name is set to null the items in the resource tree - * widget will link to the resource's details page. - */ - public function __construct( - array $root_resources = [], - $title = '', - $parameter_name = 'tree_selected_resource' - ) - { - parent::__construct(); - - if (!$root_resources) { - throw new InvalidArgumentException( - 'ResourceTreeWidget instances must be initalised with at least one resource object!' - ); - } - - //Extra check to make sure the root_resources attribute of this instance - //is an array containing only Resource objects or objects derived - //from the Resource class: - foreach ($root_resources as $root_resource) { - if ($root_resource instanceof Resource) { - $this->root_resources[] = $root_resource; - } - } - - if (!$this->root_resources) { - throw new InvalidArgumentException( - 'No Resource object has been provided to the constructor of the ResourceTreeWidget class!' - ); - } - - $this->root_resources = SimpleORMapCollection::createFromArray( - $this->root_resources - ); - $this->root_resources->orderBy('sort_position DESC, name ASC, mkdate ASC'); - - $this->template = 'sidebar/resource-tree-widget'; - - if ($title) { - $this->title = $title; - } else { - $this->title = _('Ressourcenbaum'); - } - - $this->parameter_name = $parameter_name; - $this->forced_rendering = true; - } - - /** - * The render method will attach the root resource - * of this object to the set of variables which is - * passed to the template. - */ - public function render($variables = []) - { - if (!is_array($variables)) { - $variables = []; - } - - $variables['resources'] = $this->root_resources; - $variables['title'] = $this->title; - $variables['parameter_name'] = $this->parameter_name; - if ($this->parameter_name) { - $variables['selected_resource'] = Request::get($this->parameter_name); - } else { - $variables['selected_resource'] = $this->current_resource_id; - } - - $resource_path = []; - //If a resource is selected we get the IDs of all parent resources - //so that we know in the template which tree items shall be visible. - if ($variables['selected_resource']) { - $resource = Resource::find($variables['selected_resource']); - if ($resource) { - $resource_path[] = $resource->id; - $current_parent = $resource->parent; - while ($current_parent) { - $resource_path[] = $current_parent->id; - $current_parent = $current_parent->parent; - } - } - } - $variables['resource_path'] = $resource_path; - $variables['max_open_depth'] = 0; - $variables['layout_css_classes'] = $this->layout_css_classes; - $variables['hide'] = false; - - return parent::render($variables); - } - - public function setCurrentResource(Resource $resource) - { - $this->current_resource_id = $resource->id; - } - - public function setCurrentResourceId($resource_id = null) - { - if ($resource_id) { - $this->current_resource_id = $resource_id; - } - } - - public function setFoldable($foldable = false) - { - $this->foldable = (bool)$foldable; - } - - public function isFoldable() - { - return $this->foldable; - } -} diff --git a/lib/classes/sidebar/ResourceTreeWidget.php b/lib/classes/sidebar/ResourceTreeWidget.php new file mode 100644 index 0000000..60d4b29 --- /dev/null +++ b/lib/classes/sidebar/ResourceTreeWidget.php @@ -0,0 +1,145 @@ + + * @copyright 2017-2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.5 + */ +class ResourceTreeWidget extends SidebarWidget +{ + /** + * $root_resource is the resource whose resource tree + * shall be displayed by this widget. + * The resource itself and all its children are displayed. + */ + protected $root_resources = []; + protected $parameter_name = ''; + protected $foldable = false; + protected $current_resource_id = null; + + /** + * This widget must be initialised by providing at least one + * Resource object in an array. + * + * @param array $root_resources The root resource objects which will be + * displayed by this tree view. + * @param string $title The title of this widget. + * @param string|null $parameter_name The name of the URL parameter which + * will be set when one of the resources in the tree is selected. + * If parameter_name is set to null the items in the resource tree + * widget will link to the resource's details page. + */ + public function __construct( + array $root_resources = [], + $title = '', + $parameter_name = 'tree_selected_resource' + ) + { + parent::__construct(); + + if (!$root_resources) { + throw new InvalidArgumentException( + 'ResourceTreeWidget instances must be initalised with at least one resource object!' + ); + } + + //Extra check to make sure the root_resources attribute of this instance + //is an array containing only Resource objects or objects derived + //from the Resource class: + foreach ($root_resources as $root_resource) { + if ($root_resource instanceof Resource) { + $this->root_resources[] = $root_resource; + } + } + + if (!$this->root_resources) { + throw new InvalidArgumentException( + 'No Resource object has been provided to the constructor of the ResourceTreeWidget class!' + ); + } + + $this->root_resources = SimpleORMapCollection::createFromArray( + $this->root_resources + ); + $this->root_resources->orderBy('sort_position DESC, name ASC, mkdate ASC'); + + $this->template = 'sidebar/resource-tree-widget'; + + if ($title) { + $this->title = $title; + } else { + $this->title = _('Ressourcenbaum'); + } + + $this->parameter_name = $parameter_name; + $this->forced_rendering = true; + } + + /** + * The render method will attach the root resource + * of this object to the set of variables which is + * passed to the template. + */ + public function render($variables = []) + { + if (!is_array($variables)) { + $variables = []; + } + + $variables['resources'] = $this->root_resources; + $variables['title'] = $this->title; + $variables['parameter_name'] = $this->parameter_name; + if ($this->parameter_name) { + $variables['selected_resource'] = Request::get($this->parameter_name); + } else { + $variables['selected_resource'] = $this->current_resource_id; + } + + $resource_path = []; + //If a resource is selected we get the IDs of all parent resources + //so that we know in the template which tree items shall be visible. + if ($variables['selected_resource']) { + $resource = Resource::find($variables['selected_resource']); + if ($resource) { + $resource_path[] = $resource->id; + $current_parent = $resource->parent; + while ($current_parent) { + $resource_path[] = $current_parent->id; + $current_parent = $current_parent->parent; + } + } + } + $variables['resource_path'] = $resource_path; + $variables['max_open_depth'] = 0; + $variables['layout_css_classes'] = $this->layout_css_classes; + $variables['hide'] = false; + + return parent::render($variables); + } + + public function setCurrentResource(Resource $resource) + { + $this->current_resource_id = $resource->id; + } + + public function setCurrentResourceId($resource_id = null) + { + if ($resource_id) { + $this->current_resource_id = $resource_id; + } + } + + public function setFoldable($foldable = false) + { + $this->foldable = (bool)$foldable; + } + + public function isFoldable() + { + return $this->foldable; + } +} diff --git a/lib/classes/sidebar/RoomClipboardWidget.class.php b/lib/classes/sidebar/RoomClipboardWidget.class.php deleted file mode 100644 index 3d90c36..0000000 --- a/lib/classes/sidebar/RoomClipboardWidget.class.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @copyright 2018-2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.5 - */ -class RoomClipboardWidget extends ClipboardWidget -{ - public function __construct() - { - parent::__construct(['Room']); - $this->allowed_item_class = ''; - $this->setTitle(_('Individuelle Raumgruppen')); - $this->template = 'sidebar/room-clipboard-widget'; - - $current_user = User::findCurrent(); - - $this->addLink( - _('Gruppenbelegungsplan anzeigen'), - URLHelper::getURL('dispatch.php/room_management/planning/index/CLIPBOARD_ID'), - Icon::create('link-intern'), - [ - 'class' => 'room-clipboard-group-action', - 'target' => '_blank' - ] - ); - - if (ResourceManager::userHasGlobalPermission($current_user, 'autor')) { - $this->addLink( - _('Raumgruppe buchen'), - URLHelper::getURL('dispatch.php/resources/booking/add/clipboard_CLIPBOARD_ID'), - Icon::create('link-intern'), - [ - 'class' => 'room-clipboard-group-action', - 'data-show_in_dialog' => 'size=auto', - 'data-needs_items '=> '1' - ] - ); - } - if (ResourceManager::userHasGlobalPermission($current_user, 'admin')) { - $this->addLink( - _('Berechtigungen für die gesamte Raumgruppe setzen'), - URLHelper::getURL('dispatch.php/resources/room_group/permissions/CLIPBOARD_ID'), - Icon::create('link-intern'), - [ - 'class' => 'room-clipboard-group-action', - 'data-show_in_dialog' => '1' - ] - ); - } - } -} diff --git a/lib/classes/sidebar/RoomClipboardWidget.php b/lib/classes/sidebar/RoomClipboardWidget.php new file mode 100644 index 0000000..3d90c36 --- /dev/null +++ b/lib/classes/sidebar/RoomClipboardWidget.php @@ -0,0 +1,57 @@ + + * @copyright 2018-2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.5 + */ +class RoomClipboardWidget extends ClipboardWidget +{ + public function __construct() + { + parent::__construct(['Room']); + $this->allowed_item_class = ''; + $this->setTitle(_('Individuelle Raumgruppen')); + $this->template = 'sidebar/room-clipboard-widget'; + + $current_user = User::findCurrent(); + + $this->addLink( + _('Gruppenbelegungsplan anzeigen'), + URLHelper::getURL('dispatch.php/room_management/planning/index/CLIPBOARD_ID'), + Icon::create('link-intern'), + [ + 'class' => 'room-clipboard-group-action', + 'target' => '_blank' + ] + ); + + if (ResourceManager::userHasGlobalPermission($current_user, 'autor')) { + $this->addLink( + _('Raumgruppe buchen'), + URLHelper::getURL('dispatch.php/resources/booking/add/clipboard_CLIPBOARD_ID'), + Icon::create('link-intern'), + [ + 'class' => 'room-clipboard-group-action', + 'data-show_in_dialog' => 'size=auto', + 'data-needs_items '=> '1' + ] + ); + } + if (ResourceManager::userHasGlobalPermission($current_user, 'admin')) { + $this->addLink( + _('Berechtigungen für die gesamte Raumgruppe setzen'), + URLHelper::getURL('dispatch.php/resources/room_group/permissions/CLIPBOARD_ID'), + Icon::create('link-intern'), + [ + 'class' => 'room-clipboard-group-action', + 'data-show_in_dialog' => '1' + ] + ); + } + } +} diff --git a/lib/classes/sidebar/RoomSearchTreeWidget.class.php b/lib/classes/sidebar/RoomSearchTreeWidget.class.php deleted file mode 100644 index c3aaa07..0000000 --- a/lib/classes/sidebar/RoomSearchTreeWidget.class.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license GNU General Public License v2 or later. - * @since 4.5 - */ -class RoomSearchTreeWidget extends ResourceTreeWidget -{ - /** - * This widget must be initialised by providing at least one - * Resource object in an array. - * - * @param array $root_resources The root resource objects which will be - * displayed by this tree view. - * @param string $title The title of this widget. - * @param string|null $parameter_name The name of the URL parameter which - * will be set when one of the resources in the tree is selected. - * If parameter_name is set to null the items in the resource tree - * widget will link to the resource's details page. - */ - public function __construct( - array $root_resources = [], - $title = '', - $parameter_name = null - ) - { - parent::__construct($root_resources, $title, $parameter_name); - $this->addLayoutCSSClass('room-search-tree-widget'); - $this->template = 'sidebar/room-search-tree-widget'; - - if ($title) { - $this->title = $title; - } else { - $this->title = _('Ressourcenbaum'); - } - } - -} diff --git a/lib/classes/sidebar/RoomSearchTreeWidget.php b/lib/classes/sidebar/RoomSearchTreeWidget.php new file mode 100644 index 0000000..c3aaa07 --- /dev/null +++ b/lib/classes/sidebar/RoomSearchTreeWidget.php @@ -0,0 +1,41 @@ + + * @license GNU General Public License v2 or later. + * @since 4.5 + */ +class RoomSearchTreeWidget extends ResourceTreeWidget +{ + /** + * This widget must be initialised by providing at least one + * Resource object in an array. + * + * @param array $root_resources The root resource objects which will be + * displayed by this tree view. + * @param string $title The title of this widget. + * @param string|null $parameter_name The name of the URL parameter which + * will be set when one of the resources in the tree is selected. + * If parameter_name is set to null the items in the resource tree + * widget will link to the resource's details page. + */ + public function __construct( + array $root_resources = [], + $title = '', + $parameter_name = null + ) + { + parent::__construct($root_resources, $title, $parameter_name); + $this->addLayoutCSSClass('room-search-tree-widget'); + $this->template = 'sidebar/room-search-tree-widget'; + + if ($title) { + $this->title = $title; + } else { + $this->title = _('Ressourcenbaum'); + } + } + +} diff --git a/lib/classes/sidebar/RoomSearchWidget.class.php b/lib/classes/sidebar/RoomSearchWidget.class.php deleted file mode 100644 index 1fa465f..0000000 --- a/lib/classes/sidebar/RoomSearchWidget.class.php +++ /dev/null @@ -1,595 +0,0 @@ -semesters = array_reverse(Semester::getAll()); - $this->defined_properties = RoomManager::getAllRoomPropertyDefinitions( - true, - [ - 'seats', 'room_type','room_category_id' - ] - ); - - $resource_categories = ResourceCategory::findBySQL("class_name = 'Room' ORDER by name"); - $categories = [ - '' => _('Alle Kategorien') - ]; - if($resource_categories) { - foreach($resource_categories as $resource_category) { - $categories[$resource_category->id] = $resource_category->name; - } - } - - $room_types = Room::getAllRoomTypes(); - if (!empty($room_types)) { - $filtered_room_types = []; - foreach ($room_types as $type) { - $filtered_room_types[$type] = $type; - } - $room_types = array_merge( - ['' => _('Alle Raumtypen')], - $filtered_room_types - ); - } - - $this->criteria = []; - - if ($this->defined_properties) { - foreach ($this->defined_properties as $property) { - $this->criteria[$property->name] = [ - 'name' => $property->id, - 'title' => ( - $property->display_name != '' - ? $property->display_name - : $property->name - ), - 'type' => $property->type, - 'range_search' => $property->range_search, - 'optional' => true - ]; - if ($property->type === 'select') { - $this->criteria[$property->name]['options'] = $property->getOptionsArray(); - } - } - } - - //Add special criteria: - $this->criteria['special__room_name'] = [ - 'name' => 'special__room_name', - 'title' => _('Raumname'), - 'type' => 'text', - 'range_search' => false, - 'switch' => false, - 'value' => '', - 'optional' => false - ]; - $this->criteria['room_category_id'] = [ - 'name' => 'room_category_id', - 'title' => _('Kategorie'), - 'type' => 'select', - 'range_search' => false, - 'options' => $categories, - 'switch' => false, - 'value' => '', - 'optional' => false - ]; - if (!empty($room_types)) { - $this->criteria['room_type'] = [ - 'name' => 'room_type', - 'title' => _('Raumtyp'), - 'type' => 'select', - 'range_search' => false, - 'options' => $room_types, - 'switch' => false, - 'value' => '', - 'optional' => false - ]; - } - $this->criteria['special__building_location'] = [ - 'name' => 'special__building_location', - 'title' => _('Standort / Gebäude'), - 'type' => 'hidden', - 'range_search' => false, - 'switch' => false, - 'value' => '', - 'optional' => false - ]; - - if (Request::get('special__building_location') && !Request::submitted('room_search_reset')) { - $res_id = explode('_', Request::get('special__building_location')); - $selected_res =Resource::find($res_id[1]); - if ($selected_res) { - $this->criteria['special__building_location_label'] = [ - 'name' => 'special__building_location_label', - 'title' => _('Standort / Gebäude'), - 'type' => 'disabled_text', - 'range_search' => false, - 'switch' => false, - 'value' => $selected_res->name, - 'optional' => false - ]; - } - } - - $current_semester = Semester::findCurrent(); - $begin = new DateTime(); - $begin = $begin->setTimestamp($current_semester->beginn); - $begin->setTime(intval(date('H')), 0, 0); - $end = clone $begin; - $end = $end->setTimestamp($current_semester->ende); - - $this->criteria['special__time_range'] = [ - 'name' => 'special__time_range', - 'title' => _('Frei in einem Zeitbereich'), - 'optional' => false, - 'enabled' => false, - 'semester' => [ - 'value' => $current_semester->id - ], - 'range' => [ - 'begin' => $begin, - 'end' => $end - ], - 'day_of_week' => [ - 'options' => [ - '1' => _('Montag'), - '2' => _('Dienstag'), - '3' => _('Mittwoch'), - '4' => _('Donnerstag'), - '5' => _('Freitag'), - '6' => _('Samstag'), - '7' => _('Sonntag') - ], - 'value' => '' - ] - ]; - - $this->criteria['special__seats'] = [ - 'name' => 'special__seats', - 'title' => _('Sitzplätze'), - 'type' => 'num', - 'range_search' => true, - 'switch' => true, - 'value' => [10, 100], - 'optional' => false - ]; - - } - - - protected function handleSearchRequest() - { - $this->selected_criteria = []; - - //If the reset button has been pressed, reset the search - //and do nothing else. - if ($this->searchResetRequested() || !$this->searchRequested()) { - //If no room search is requested we can stop here. - return; - } - - $default_begin = new DateTime(); - $default_begin = $default_begin->add(new DateInterval('P1D')); - $default_begin->setTime(intval(date('H')), 0, 0); - $default_end = clone $default_begin; - $default_end = $default_end->add(new DateInterval('PT30M')); - - foreach ($this->criteria as $name => $data) { - if ($name == 'special__time_range') { - if (Request::get($data['name'] . '_enabled')) { - $data['enabled'] = true; - $this->selected_criteria[$name] = $data; - if (Request::submittedSome( - $data['name'] . '_begin_date', - $data['name'] . '_begin_time', - $data['name'] . '_end_date', - $data['name'] . '_end_time' - )) { - $submitted_begin = Request::getDateTime( - $data['name'] . '_begin_date', - 'd.m.Y', - $data['name'] . '_begin_time', - 'H:i' - ); - $submitted_end = Request::getDateTime( - $data['name'] . '_end_date', - 'd.m.Y', - $data['name'] . '_end_time', - 'H:i' - ); - if(!$submitted_begin || !$submitted_end) { - $submitted_begin = $default_begin; - $submitted_end = $default_end; - } - $this->selected_criteria[$name]['range'] = [ - 'begin' => $submitted_begin, - 'end' => $submitted_end - ]; - } - $this->selected_criteria[$name]['day_of_week']['value'] = - Request::get($data['name'] . '_day_of_week'); - $this->selected_criteria[$name]['semester']['value'] = - Request::get($data['name'] . '_semester_id'); - } - } else { - if (!empty($data['switch'])) { - if (Request::get($data['name'] . '_enabled')) { - $data['enabled'] = true; - } else { - //The criteria isn't enabled. We can move on to the - //next criteria. - continue; - } - } - if ($data['type'] == 'date') { - if ($data['range_search']) { - if (Request::submittedSome( - $data['name'] . '_begin_date', - $data['name'] . '_begin_time', - $data['name'] . '_end_date', - $data['name'] . '_end_time' - )) { - $this->selected_criteria[$name] = $data; - $submitted_begin = Request::getDateTime( - $data['name'] . '_begin_date', - 'd.m.Y', - $data['name'] . '_begin_time', - 'H:i' - ); - $submitted_end = Request::getDateTime( - $data['name'] . '_end_date', - 'd.m.Y', - $data['name'] . '_end_time', - 'H:i' - ); - if(!$submitted_begin || !$submitted_end) { - $submitted_begin = $default_begin; - $submitted_end = $default_end; - } - $this->selected_criteria[$name]['value'] = [ - 'begin' => $submitted_begin, - 'end' => $submitted_end - ]; - } - } else { - if (Request::submittedSome( - $data['name'] . '_date', - $data['name'] . '_time' - )) { - $this->selected_criteria[$name] = $data; - $this->selected_criteria[$name]['value'] = - Request::getDateTime( - $data['name'] . '_date', - 'd.m.Y', - $data['name'] . '_time', - 'H:i' - ); - } - } - } elseif ($data['type'] === 'num' && $data['range_search']) { - if (Request::submittedSome( - $data['name'] . '_min', - $data['name'] . '_max' - )) { - $this->selected_criteria[$name] = $data; - $this->selected_criteria[$name]['value'] = [ - Request::get($data['name'] . '_min'), - Request::get($data['name'] . '_max') - ]; - } - } elseif ($data['type'] === 'bool') { - if (Request::submitted('options_' . $data['name'])) { - $this->selected_criteria[$name] = $data; - $this->selected_criteria[$name]['value'] = Request::get($data['name']); - } - } else { - if (Request::submitted($data['name'])) { - $this->selected_criteria[$name] = $data; - $this->selected_criteria[$name]['value'] = Request::get($data['name']); - } - } - } - } - - $_SESSION['room_search_criteria']['room_search'] = - $this->selected_criteria; - } - - protected function restoreSearchFromSession() - { - if (!empty($_SESSION['room_search_criteria']['room_search']) && is_array($_SESSION['room_search_criteria']['room_search'])) { - $this->selected_criteria = - $_SESSION['room_search_criteria']['room_search']; - } else { - $this->selected_criteria = []; - } - } - - protected function search() - { - //The properties array is a "simplified" version of the - //$selected_criteria array, stripped from all special search criteria, - //except the "seats" search criteria. - - $properties = []; - if ($this->selected_criteria) { - foreach ($this->selected_criteria as $name => $criteria) { - - //Do not add the special properties - //into the $properties array: - if (preg_match('/special__/', $name) && ($name != 'special__seats')) { - continue; - } - if ($name == 'room_type' && empty($criteria['value'])) { - continue; - } - if ($name == 'room_category_id' && empty($criteria['value'])) { - continue; - } - if ($name == 'special__seats') { - if ($criteria['value'][0] || $criteria['value'][1]) { - $properties['seats'] = $criteria['value']; - } - $name = 'seats'; - } else { - $properties[$name] = $criteria['value']; - } - - if ( - isset($properties[$name][0], $properties[$name][1]) - && $properties[$name][0] && $properties[$name][1] - && $properties[$name][0] > $properties[$name][1] - && $name !== 'room_category_id' - ) { - //A range is selected, but the range start is bigger - //then the range end. That's an error! - - //Resolve the property name for a "beautiful" property name: - $property = ResourcePropertyDefinition::findOneBySql( - 'name = :name', - ['name' => $name] - ); - $property_name = $name; - if ($property) { - $property_name = $property->display_name; - } - - PageLayout::postError( - sprintf( - _('Für die Eigenschaft %1$s wurde ein ungültiger Bereich angegeben (von %2$s bis %3$s)!'), - htmlReady($property_name), - htmlReady($properties[$name][0]), - htmlReady($properties[$name][1]) - ) - ); - return; - } - } - } - - $building_or_location_id = explode( - '_', - $this->selected_criteria['special__building_location']['value'] - ); - - $this->location_id = null; - $this->building_id = null; - - if ($building_or_location_id[0] == 'building') { - $this->building_id = $building_or_location_id[1]; - } elseif ($building_or_location_id[0] == 'location') { - $this->location_id = $building_or_location_id[1]; - } elseif($building_or_location_id[0] == 'resourcelabel') { - $resourcelabel = ResourceLabel::find($building_or_location_id[1]); - if ($resourcelabel) { - $sub_buildings = []; - foreach($resourcelabel->findChildrenByClassName('Building') as $sub_building) { - $sub_buildings[] = $sub_building->id; - } - $this->building_id = $sub_buildings; - } - } elseif($building_or_location_id[0] == 'room') { - $this->rooms = [Room::find($building_or_location_id[1])]; - return; - } - - //The time intervals have to be calculated by the selected time range - //and the selected day of week. - //The selected semester is represented by the selected time range - //since its begin and end date are set on the client side in - //the special__available_range property when a semester is selected. - $time_intervals = []; - if (!empty($this->selected_criteria['special__time_range'])) { - $time_range_criteria = $this->selected_criteria['special__time_range']; - - //Get and check day of week: - if ($time_range_criteria['day_of_week']['value']) { - $selected_dow = $time_range_criteria['day_of_week']['value']; - if (($selected_dow >= 1) && ($selected_dow <= 7)) { - - //Get and check the time range: - if (($time_range_criteria['range']['begin'] instanceof DateTime) - && ($time_range_criteria['range']['end'] instanceof DateTime)) { - //Start from the begin date and make time intervals - //for the specified time on the specified day of week. - $begin = clone $time_range_criteria['range']['begin']; - $begin_dow = $begin->format('N'); - if ($begin_dow < $selected_dow) { - $diff = $selected_dow - $begin_dow; - $begin = $begin->add( - new DateInterval( - 'P' . $diff . 'D' - ) - ); - } elseif ($begin_dow > $selected_dow) { - $diff = $begin_dow - $selected_dow; - $begin = $begin->sub( - new DateInterval( - 'P' . $diff . 'D' - ) - ); - } - $end = clone $time_range_criteria['range']['end']; - $current_begin = clone $begin; - do { - $current_end = clone $current_begin; - $current_end->setTime( - intval($end->format('H')), - intval($end->format('i')), - intval($end->format('s')) - ); - $time_intervals[] = [ - 'begin' => clone $current_begin, - 'end' => clone $current_end - ]; - $current_begin = $current_begin->add( - new DateInterval('P1W') - ); - } while ($current_begin < $end); - } else { - //Get the next occurrence of the specified day of week. - $begin = new DateTime(); - $begin_dow = $begin->format('N'); - if ($begin_dow < $selected_dow) { - $diff = $selected_dow - $begin_dow; - $begin = $begin->add( - new DateInterval( - 'P' . $diff . 'D' - ) - ); - } elseif ($begin_dow > $selected_dow) { - $diff = $begin_dow - $selected_dow; - $begin = $begin->sub( - new DateInterval( - 'P' . $diff . 'D' - ) - ); - } - $begin->setTime(0,0); - $end = clone $begin; - $end = $end->add( - new DateInterval('P1D') - )->sub( - new DateInterval('PT1S') - ); - - $time_intervals[] = [ - 'begin' => $begin, - 'end' => $end - ]; - } - } - } elseif ($time_range_criteria['range']) { - //A time range without a day of week is specified. - $time_intervals[] = $time_range_criteria['range']; - } - } - - try { - $this->rooms = RoomManager::findRooms( - $this->selected_criteria['special__room_name']['value'], - $this->location_id, - $this->building_id, - $properties, - $time_intervals, - 'name ASC, mkdate ASC', - false - ); - } catch (\InvalidArgumentException $e) { - PageLayout::postError($e->getMessage()); - } - } - - public function resetSearch() - { - $this->selected_criteria = []; - $_SESSION['room_search_criteria']['room_search'] = []; - } - - public function __construct($action_link = '') - { - parent::__construct(); - - $this->template = 'sidebar/room-search-widget'; - - if ($action_link) { - $this->action_link = $action_link; - } - - $this->setupSearchParameters(); - if ($this->searchRequested()) { - $this->handleSearchRequest(); - } elseif ($this->searchResetRequested()) { - $this->resetSearch(); - } else { - $this->restoreSearchFromSession(); - } - - if ($this->selected_criteria) { - $this->search(); - } - } - - public function searchRequested() - { - return Request::submitted('room_search'); - } - - public function searchResetRequested() - { - return Request::submitted('room_search_reset'); - } - - public function getResults() - { - return $this->rooms; - } - - public function setActionLink($action_link = '') - { - if (!$action_link) { - return; - } - - $this->action_link = $action_link; - } - - public function getActionLink() - { - return $this->action_link; - } - - public function getSelectedCriteria() - { - return $this->selected_criteria; - } - - public function render($variables = []) - { - $variables = array_merge($variables, [ - 'title' => _('Suchkriterien für Räume'), - 'criteria' => $this->criteria, - 'selected_criteria' => $this->selected_criteria, - 'action_link' => $this->action_link, - 'semesters' => $this->semesters - ]); - - return $GLOBALS['template_factory']->render( - $this->template, - $variables, - 'widgets/widget-layout' - ); - } -} diff --git a/lib/classes/sidebar/RoomSearchWidget.php b/lib/classes/sidebar/RoomSearchWidget.php new file mode 100644 index 0000000..1fa465f --- /dev/null +++ b/lib/classes/sidebar/RoomSearchWidget.php @@ -0,0 +1,595 @@ +semesters = array_reverse(Semester::getAll()); + $this->defined_properties = RoomManager::getAllRoomPropertyDefinitions( + true, + [ + 'seats', 'room_type','room_category_id' + ] + ); + + $resource_categories = ResourceCategory::findBySQL("class_name = 'Room' ORDER by name"); + $categories = [ + '' => _('Alle Kategorien') + ]; + if($resource_categories) { + foreach($resource_categories as $resource_category) { + $categories[$resource_category->id] = $resource_category->name; + } + } + + $room_types = Room::getAllRoomTypes(); + if (!empty($room_types)) { + $filtered_room_types = []; + foreach ($room_types as $type) { + $filtered_room_types[$type] = $type; + } + $room_types = array_merge( + ['' => _('Alle Raumtypen')], + $filtered_room_types + ); + } + + $this->criteria = []; + + if ($this->defined_properties) { + foreach ($this->defined_properties as $property) { + $this->criteria[$property->name] = [ + 'name' => $property->id, + 'title' => ( + $property->display_name != '' + ? $property->display_name + : $property->name + ), + 'type' => $property->type, + 'range_search' => $property->range_search, + 'optional' => true + ]; + if ($property->type === 'select') { + $this->criteria[$property->name]['options'] = $property->getOptionsArray(); + } + } + } + + //Add special criteria: + $this->criteria['special__room_name'] = [ + 'name' => 'special__room_name', + 'title' => _('Raumname'), + 'type' => 'text', + 'range_search' => false, + 'switch' => false, + 'value' => '', + 'optional' => false + ]; + $this->criteria['room_category_id'] = [ + 'name' => 'room_category_id', + 'title' => _('Kategorie'), + 'type' => 'select', + 'range_search' => false, + 'options' => $categories, + 'switch' => false, + 'value' => '', + 'optional' => false + ]; + if (!empty($room_types)) { + $this->criteria['room_type'] = [ + 'name' => 'room_type', + 'title' => _('Raumtyp'), + 'type' => 'select', + 'range_search' => false, + 'options' => $room_types, + 'switch' => false, + 'value' => '', + 'optional' => false + ]; + } + $this->criteria['special__building_location'] = [ + 'name' => 'special__building_location', + 'title' => _('Standort / Gebäude'), + 'type' => 'hidden', + 'range_search' => false, + 'switch' => false, + 'value' => '', + 'optional' => false + ]; + + if (Request::get('special__building_location') && !Request::submitted('room_search_reset')) { + $res_id = explode('_', Request::get('special__building_location')); + $selected_res =Resource::find($res_id[1]); + if ($selected_res) { + $this->criteria['special__building_location_label'] = [ + 'name' => 'special__building_location_label', + 'title' => _('Standort / Gebäude'), + 'type' => 'disabled_text', + 'range_search' => false, + 'switch' => false, + 'value' => $selected_res->name, + 'optional' => false + ]; + } + } + + $current_semester = Semester::findCurrent(); + $begin = new DateTime(); + $begin = $begin->setTimestamp($current_semester->beginn); + $begin->setTime(intval(date('H')), 0, 0); + $end = clone $begin; + $end = $end->setTimestamp($current_semester->ende); + + $this->criteria['special__time_range'] = [ + 'name' => 'special__time_range', + 'title' => _('Frei in einem Zeitbereich'), + 'optional' => false, + 'enabled' => false, + 'semester' => [ + 'value' => $current_semester->id + ], + 'range' => [ + 'begin' => $begin, + 'end' => $end + ], + 'day_of_week' => [ + 'options' => [ + '1' => _('Montag'), + '2' => _('Dienstag'), + '3' => _('Mittwoch'), + '4' => _('Donnerstag'), + '5' => _('Freitag'), + '6' => _('Samstag'), + '7' => _('Sonntag') + ], + 'value' => '' + ] + ]; + + $this->criteria['special__seats'] = [ + 'name' => 'special__seats', + 'title' => _('Sitzplätze'), + 'type' => 'num', + 'range_search' => true, + 'switch' => true, + 'value' => [10, 100], + 'optional' => false + ]; + + } + + + protected function handleSearchRequest() + { + $this->selected_criteria = []; + + //If the reset button has been pressed, reset the search + //and do nothing else. + if ($this->searchResetRequested() || !$this->searchRequested()) { + //If no room search is requested we can stop here. + return; + } + + $default_begin = new DateTime(); + $default_begin = $default_begin->add(new DateInterval('P1D')); + $default_begin->setTime(intval(date('H')), 0, 0); + $default_end = clone $default_begin; + $default_end = $default_end->add(new DateInterval('PT30M')); + + foreach ($this->criteria as $name => $data) { + if ($name == 'special__time_range') { + if (Request::get($data['name'] . '_enabled')) { + $data['enabled'] = true; + $this->selected_criteria[$name] = $data; + if (Request::submittedSome( + $data['name'] . '_begin_date', + $data['name'] . '_begin_time', + $data['name'] . '_end_date', + $data['name'] . '_end_time' + )) { + $submitted_begin = Request::getDateTime( + $data['name'] . '_begin_date', + 'd.m.Y', + $data['name'] . '_begin_time', + 'H:i' + ); + $submitted_end = Request::getDateTime( + $data['name'] . '_end_date', + 'd.m.Y', + $data['name'] . '_end_time', + 'H:i' + ); + if(!$submitted_begin || !$submitted_end) { + $submitted_begin = $default_begin; + $submitted_end = $default_end; + } + $this->selected_criteria[$name]['range'] = [ + 'begin' => $submitted_begin, + 'end' => $submitted_end + ]; + } + $this->selected_criteria[$name]['day_of_week']['value'] = + Request::get($data['name'] . '_day_of_week'); + $this->selected_criteria[$name]['semester']['value'] = + Request::get($data['name'] . '_semester_id'); + } + } else { + if (!empty($data['switch'])) { + if (Request::get($data['name'] . '_enabled')) { + $data['enabled'] = true; + } else { + //The criteria isn't enabled. We can move on to the + //next criteria. + continue; + } + } + if ($data['type'] == 'date') { + if ($data['range_search']) { + if (Request::submittedSome( + $data['name'] . '_begin_date', + $data['name'] . '_begin_time', + $data['name'] . '_end_date', + $data['name'] . '_end_time' + )) { + $this->selected_criteria[$name] = $data; + $submitted_begin = Request::getDateTime( + $data['name'] . '_begin_date', + 'd.m.Y', + $data['name'] . '_begin_time', + 'H:i' + ); + $submitted_end = Request::getDateTime( + $data['name'] . '_end_date', + 'd.m.Y', + $data['name'] . '_end_time', + 'H:i' + ); + if(!$submitted_begin || !$submitted_end) { + $submitted_begin = $default_begin; + $submitted_end = $default_end; + } + $this->selected_criteria[$name]['value'] = [ + 'begin' => $submitted_begin, + 'end' => $submitted_end + ]; + } + } else { + if (Request::submittedSome( + $data['name'] . '_date', + $data['name'] . '_time' + )) { + $this->selected_criteria[$name] = $data; + $this->selected_criteria[$name]['value'] = + Request::getDateTime( + $data['name'] . '_date', + 'd.m.Y', + $data['name'] . '_time', + 'H:i' + ); + } + } + } elseif ($data['type'] === 'num' && $data['range_search']) { + if (Request::submittedSome( + $data['name'] . '_min', + $data['name'] . '_max' + )) { + $this->selected_criteria[$name] = $data; + $this->selected_criteria[$name]['value'] = [ + Request::get($data['name'] . '_min'), + Request::get($data['name'] . '_max') + ]; + } + } elseif ($data['type'] === 'bool') { + if (Request::submitted('options_' . $data['name'])) { + $this->selected_criteria[$name] = $data; + $this->selected_criteria[$name]['value'] = Request::get($data['name']); + } + } else { + if (Request::submitted($data['name'])) { + $this->selected_criteria[$name] = $data; + $this->selected_criteria[$name]['value'] = Request::get($data['name']); + } + } + } + } + + $_SESSION['room_search_criteria']['room_search'] = + $this->selected_criteria; + } + + protected function restoreSearchFromSession() + { + if (!empty($_SESSION['room_search_criteria']['room_search']) && is_array($_SESSION['room_search_criteria']['room_search'])) { + $this->selected_criteria = + $_SESSION['room_search_criteria']['room_search']; + } else { + $this->selected_criteria = []; + } + } + + protected function search() + { + //The properties array is a "simplified" version of the + //$selected_criteria array, stripped from all special search criteria, + //except the "seats" search criteria. + + $properties = []; + if ($this->selected_criteria) { + foreach ($this->selected_criteria as $name => $criteria) { + + //Do not add the special properties + //into the $properties array: + if (preg_match('/special__/', $name) && ($name != 'special__seats')) { + continue; + } + if ($name == 'room_type' && empty($criteria['value'])) { + continue; + } + if ($name == 'room_category_id' && empty($criteria['value'])) { + continue; + } + if ($name == 'special__seats') { + if ($criteria['value'][0] || $criteria['value'][1]) { + $properties['seats'] = $criteria['value']; + } + $name = 'seats'; + } else { + $properties[$name] = $criteria['value']; + } + + if ( + isset($properties[$name][0], $properties[$name][1]) + && $properties[$name][0] && $properties[$name][1] + && $properties[$name][0] > $properties[$name][1] + && $name !== 'room_category_id' + ) { + //A range is selected, but the range start is bigger + //then the range end. That's an error! + + //Resolve the property name for a "beautiful" property name: + $property = ResourcePropertyDefinition::findOneBySql( + 'name = :name', + ['name' => $name] + ); + $property_name = $name; + if ($property) { + $property_name = $property->display_name; + } + + PageLayout::postError( + sprintf( + _('Für die Eigenschaft %1$s wurde ein ungültiger Bereich angegeben (von %2$s bis %3$s)!'), + htmlReady($property_name), + htmlReady($properties[$name][0]), + htmlReady($properties[$name][1]) + ) + ); + return; + } + } + } + + $building_or_location_id = explode( + '_', + $this->selected_criteria['special__building_location']['value'] + ); + + $this->location_id = null; + $this->building_id = null; + + if ($building_or_location_id[0] == 'building') { + $this->building_id = $building_or_location_id[1]; + } elseif ($building_or_location_id[0] == 'location') { + $this->location_id = $building_or_location_id[1]; + } elseif($building_or_location_id[0] == 'resourcelabel') { + $resourcelabel = ResourceLabel::find($building_or_location_id[1]); + if ($resourcelabel) { + $sub_buildings = []; + foreach($resourcelabel->findChildrenByClassName('Building') as $sub_building) { + $sub_buildings[] = $sub_building->id; + } + $this->building_id = $sub_buildings; + } + } elseif($building_or_location_id[0] == 'room') { + $this->rooms = [Room::find($building_or_location_id[1])]; + return; + } + + //The time intervals have to be calculated by the selected time range + //and the selected day of week. + //The selected semester is represented by the selected time range + //since its begin and end date are set on the client side in + //the special__available_range property when a semester is selected. + $time_intervals = []; + if (!empty($this->selected_criteria['special__time_range'])) { + $time_range_criteria = $this->selected_criteria['special__time_range']; + + //Get and check day of week: + if ($time_range_criteria['day_of_week']['value']) { + $selected_dow = $time_range_criteria['day_of_week']['value']; + if (($selected_dow >= 1) && ($selected_dow <= 7)) { + + //Get and check the time range: + if (($time_range_criteria['range']['begin'] instanceof DateTime) + && ($time_range_criteria['range']['end'] instanceof DateTime)) { + //Start from the begin date and make time intervals + //for the specified time on the specified day of week. + $begin = clone $time_range_criteria['range']['begin']; + $begin_dow = $begin->format('N'); + if ($begin_dow < $selected_dow) { + $diff = $selected_dow - $begin_dow; + $begin = $begin->add( + new DateInterval( + 'P' . $diff . 'D' + ) + ); + } elseif ($begin_dow > $selected_dow) { + $diff = $begin_dow - $selected_dow; + $begin = $begin->sub( + new DateInterval( + 'P' . $diff . 'D' + ) + ); + } + $end = clone $time_range_criteria['range']['end']; + $current_begin = clone $begin; + do { + $current_end = clone $current_begin; + $current_end->setTime( + intval($end->format('H')), + intval($end->format('i')), + intval($end->format('s')) + ); + $time_intervals[] = [ + 'begin' => clone $current_begin, + 'end' => clone $current_end + ]; + $current_begin = $current_begin->add( + new DateInterval('P1W') + ); + } while ($current_begin < $end); + } else { + //Get the next occurrence of the specified day of week. + $begin = new DateTime(); + $begin_dow = $begin->format('N'); + if ($begin_dow < $selected_dow) { + $diff = $selected_dow - $begin_dow; + $begin = $begin->add( + new DateInterval( + 'P' . $diff . 'D' + ) + ); + } elseif ($begin_dow > $selected_dow) { + $diff = $begin_dow - $selected_dow; + $begin = $begin->sub( + new DateInterval( + 'P' . $diff . 'D' + ) + ); + } + $begin->setTime(0,0); + $end = clone $begin; + $end = $end->add( + new DateInterval('P1D') + )->sub( + new DateInterval('PT1S') + ); + + $time_intervals[] = [ + 'begin' => $begin, + 'end' => $end + ]; + } + } + } elseif ($time_range_criteria['range']) { + //A time range without a day of week is specified. + $time_intervals[] = $time_range_criteria['range']; + } + } + + try { + $this->rooms = RoomManager::findRooms( + $this->selected_criteria['special__room_name']['value'], + $this->location_id, + $this->building_id, + $properties, + $time_intervals, + 'name ASC, mkdate ASC', + false + ); + } catch (\InvalidArgumentException $e) { + PageLayout::postError($e->getMessage()); + } + } + + public function resetSearch() + { + $this->selected_criteria = []; + $_SESSION['room_search_criteria']['room_search'] = []; + } + + public function __construct($action_link = '') + { + parent::__construct(); + + $this->template = 'sidebar/room-search-widget'; + + if ($action_link) { + $this->action_link = $action_link; + } + + $this->setupSearchParameters(); + if ($this->searchRequested()) { + $this->handleSearchRequest(); + } elseif ($this->searchResetRequested()) { + $this->resetSearch(); + } else { + $this->restoreSearchFromSession(); + } + + if ($this->selected_criteria) { + $this->search(); + } + } + + public function searchRequested() + { + return Request::submitted('room_search'); + } + + public function searchResetRequested() + { + return Request::submitted('room_search_reset'); + } + + public function getResults() + { + return $this->rooms; + } + + public function setActionLink($action_link = '') + { + if (!$action_link) { + return; + } + + $this->action_link = $action_link; + } + + public function getActionLink() + { + return $this->action_link; + } + + public function getSelectedCriteria() + { + return $this->selected_criteria; + } + + public function render($variables = []) + { + $variables = array_merge($variables, [ + 'title' => _('Suchkriterien für Räume'), + 'criteria' => $this->criteria, + 'selected_criteria' => $this->selected_criteria, + 'action_link' => $this->action_link, + 'semesters' => $this->semesters + ]); + + return $GLOBALS['template_factory']->render( + $this->template, + $variables, + 'widgets/widget-layout' + ); + } +} diff --git a/lib/cronjobs/check_admission.class.php b/lib/cronjobs/check_admission.class.php deleted file mode 100644 index dfd493c..0000000 --- a/lib/cronjobs/check_admission.class.php +++ /dev/null @@ -1,147 +0,0 @@ -, Suchi & Berg GmbH -* @access public -* @since 2.4 -*/ - -class CheckAdmissionJob extends CronJob -{ - public static function getName() - { - return _('Losverfahren überprüfen'); - } - - public static function getDescription() - { - return _('Überprüft, ob Losverfahren anstehen und führt diese aus'); - } - - public static function getParameters() - { - return [ - 'verbose' => [ - 'type' => 'boolean', - 'default' => false, - 'status' => 'optional', - 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), - ], - 'send_messages' => [ - 'type' => 'boolean', - 'default' => true, - 'status' => 'optional', - 'description' => _('Sollen interne Nachrichten an alle betroffenen Nutzer gesendet werden)'), - ], - 'send_applications_to_owner' => [ - 'type' => 'boolean', - 'default' => false, - 'status' => 'optional', - 'description' => _('Die Liste mit Anmeldungen an die Person senden, der das Anmeldeset gehört.') - ] - ]; - } - - public function setUp() - { - require_once 'lib/classes/admission/CourseSet.class.php'; - if (empty($GLOBALS['ABSOLUTE_URI_STUDIP'])) { - throw new Exception('To use check_admission job you MUST set correct values for $ABSOLUTE_URI_STUDIP in config_local.inc.php!'); - } - } - - public function execute($last_result, $parameters = []) - { - $verbose = $parameters['verbose']; - - $query = "SELECT DISTINCT cr.set_id - FROM courseset_rule AS cr - INNER JOIN coursesets USING(set_id) - WHERE type = 'ParticipantRestrictedAdmission' - AND algorithm_run = 0"; - $sets = DBManager::get()->fetchFirst($query); - if (count($sets) > 0) { - if ($verbose) { - echo date('r') . ' - Starting seat distribution ' . chr(10); - - $oldLogger = Log::getInstance(); - $logdir = $GLOBALS['TMP_PATH'] . '/seat_distribution_logs'; - @mkdir($logdir); - $logfile = $logdir . '/' . date('Y-m-d-H-i') . '_seat_distribution.log'; - - if (is_dir($logdir)) { - Log::setInstance( - new Logger('seat-distributions', [new StreamHandler($logfile, Logger::DEBUG)]) - ); - echo 'logging to ' . $logfile . chr(10); - } else { - echo 'could not create directory ' . $logdir . chr(10); - } - } - $i = 0; - foreach ($sets as $set_id) { - $courseset = new CourseSet($set_id); - if ($courseset->isSeatDistributionEnabled() && !$courseset->hasAlgorithmRun() && $courseset->getSeatDistributionTime() < time()) { - if ($verbose) { - echo ++$i . ' ' . $courseset->getId() . ' : ' . $courseset->getName() . chr(10); - $applicants = AdmissionPriority::getPriorities($set_id); - $courses = SimpleCollection::createFromArray(Course::findMany($courseset->getCourses()))->toGroupedArray('seminar_id', words('name veranstaltungsnummer')); - $captions = [_("Nachname"), _("Vorname"), _("Nutzername"),_('Nutzer-ID'), _('Veranstaltung-ID'), _("Veranstaltung"), _("Nummer"), _("Priorität")]; - $data = []; - $users = User::findEachMany(function($user) use ($courses,$applicants,&$data) { - $app_courses = $applicants[$user->id]; - asort($app_courses); - foreach ($app_courses as $course_id => $prio) { - $row = []; - $row[] = $user->nachname; - $row[] = $user->vorname; - $row[] = $user->username; - $row[] = $user->id; - $row[] = $course_id; - $row[] = $courses[$course_id]['name']; - $row[] = $courses[$course_id]['veranstaltungsnummer']; - $row[] = $prio; - $data[] = $row; - } - }, array_keys($applicants), 'ORDER BY Nachname,Vorname'); - $applicants_file = $GLOBALS['TMP_PATH'] . '/seat_distribution_logs/applicants_' . $set_id . '.csv'; - if (array_to_csv($data, $applicants_file, $captions)) { - echo 'applicants written to ' . $applicants_file . chr(10); - if ($parameters['send_applications_to_owner']) { - //Send a mail to the owner of the course set: - $owner = User::find($courseset->getUserId()); - if ($owner) { - setTempLanguage($owner->id); - $mail = new StudipMail(); - $mail->addRecipient($owner->email) - ->setSubject( - sprintf(_('Das Stud.IP Anmeldeset %s wird gelost'), $courseset->getName())) - ->setBodyText(sprintf( - _('Ihr Anmeldeset %s wird jetzt gelost. Im Anhang finden Sie die Liste der Anmeldungen.'), - $courseset->getName() - )) - ->addFileAttachment($applicants_file) - ->send(); - restoreLanguage(); - } - } - } - } - $courseset->distributeSeats(); - } - } - if ($verbose) { - Log::setInstance($oldLogger); - } - } else { - if ($verbose) { - echo date('r') . ' - Nothing to do' . chr(10); - } - } - } -} diff --git a/lib/cronjobs/check_admission.php b/lib/cronjobs/check_admission.php new file mode 100644 index 0000000..dfd493c --- /dev/null +++ b/lib/cronjobs/check_admission.php @@ -0,0 +1,147 @@ +, Suchi & Berg GmbH +* @access public +* @since 2.4 +*/ + +class CheckAdmissionJob extends CronJob +{ + public static function getName() + { + return _('Losverfahren überprüfen'); + } + + public static function getDescription() + { + return _('Überprüft, ob Losverfahren anstehen und führt diese aus'); + } + + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), + ], + 'send_messages' => [ + 'type' => 'boolean', + 'default' => true, + 'status' => 'optional', + 'description' => _('Sollen interne Nachrichten an alle betroffenen Nutzer gesendet werden)'), + ], + 'send_applications_to_owner' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Die Liste mit Anmeldungen an die Person senden, der das Anmeldeset gehört.') + ] + ]; + } + + public function setUp() + { + require_once 'lib/classes/admission/CourseSet.class.php'; + if (empty($GLOBALS['ABSOLUTE_URI_STUDIP'])) { + throw new Exception('To use check_admission job you MUST set correct values for $ABSOLUTE_URI_STUDIP in config_local.inc.php!'); + } + } + + public function execute($last_result, $parameters = []) + { + $verbose = $parameters['verbose']; + + $query = "SELECT DISTINCT cr.set_id + FROM courseset_rule AS cr + INNER JOIN coursesets USING(set_id) + WHERE type = 'ParticipantRestrictedAdmission' + AND algorithm_run = 0"; + $sets = DBManager::get()->fetchFirst($query); + if (count($sets) > 0) { + if ($verbose) { + echo date('r') . ' - Starting seat distribution ' . chr(10); + + $oldLogger = Log::getInstance(); + $logdir = $GLOBALS['TMP_PATH'] . '/seat_distribution_logs'; + @mkdir($logdir); + $logfile = $logdir . '/' . date('Y-m-d-H-i') . '_seat_distribution.log'; + + if (is_dir($logdir)) { + Log::setInstance( + new Logger('seat-distributions', [new StreamHandler($logfile, Logger::DEBUG)]) + ); + echo 'logging to ' . $logfile . chr(10); + } else { + echo 'could not create directory ' . $logdir . chr(10); + } + } + $i = 0; + foreach ($sets as $set_id) { + $courseset = new CourseSet($set_id); + if ($courseset->isSeatDistributionEnabled() && !$courseset->hasAlgorithmRun() && $courseset->getSeatDistributionTime() < time()) { + if ($verbose) { + echo ++$i . ' ' . $courseset->getId() . ' : ' . $courseset->getName() . chr(10); + $applicants = AdmissionPriority::getPriorities($set_id); + $courses = SimpleCollection::createFromArray(Course::findMany($courseset->getCourses()))->toGroupedArray('seminar_id', words('name veranstaltungsnummer')); + $captions = [_("Nachname"), _("Vorname"), _("Nutzername"),_('Nutzer-ID'), _('Veranstaltung-ID'), _("Veranstaltung"), _("Nummer"), _("Priorität")]; + $data = []; + $users = User::findEachMany(function($user) use ($courses,$applicants,&$data) { + $app_courses = $applicants[$user->id]; + asort($app_courses); + foreach ($app_courses as $course_id => $prio) { + $row = []; + $row[] = $user->nachname; + $row[] = $user->vorname; + $row[] = $user->username; + $row[] = $user->id; + $row[] = $course_id; + $row[] = $courses[$course_id]['name']; + $row[] = $courses[$course_id]['veranstaltungsnummer']; + $row[] = $prio; + $data[] = $row; + } + }, array_keys($applicants), 'ORDER BY Nachname,Vorname'); + $applicants_file = $GLOBALS['TMP_PATH'] . '/seat_distribution_logs/applicants_' . $set_id . '.csv'; + if (array_to_csv($data, $applicants_file, $captions)) { + echo 'applicants written to ' . $applicants_file . chr(10); + if ($parameters['send_applications_to_owner']) { + //Send a mail to the owner of the course set: + $owner = User::find($courseset->getUserId()); + if ($owner) { + setTempLanguage($owner->id); + $mail = new StudipMail(); + $mail->addRecipient($owner->email) + ->setSubject( + sprintf(_('Das Stud.IP Anmeldeset %s wird gelost'), $courseset->getName())) + ->setBodyText(sprintf( + _('Ihr Anmeldeset %s wird jetzt gelost. Im Anhang finden Sie die Liste der Anmeldungen.'), + $courseset->getName() + )) + ->addFileAttachment($applicants_file) + ->send(); + restoreLanguage(); + } + } + } + } + $courseset->distributeSeats(); + } + } + if ($verbose) { + Log::setInstance($oldLogger); + } + } else { + if ($verbose) { + echo date('r') . ' - Nothing to do' . chr(10); + } + } + } +} diff --git a/lib/cronjobs/cleanup_log.class.php b/lib/cronjobs/cleanup_log.class.php deleted file mode 100644 index 378a6ec..0000000 --- a/lib/cronjobs/cleanup_log.class.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @author Jan-Hendrik Willms - * @access public - * @since 2.4 - */ - -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// cleanup_log.class.php -// -// Copyright (C) 2013 Jan-Hendrik Willms -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -require_once 'lib/classes/CronJob.class.php'; - -class CleanupLogJob extends CronJob -{ - /** - * Returns the name of the cronjob. - */ - public static function getName() - { - return _('Logs aufräumen'); - } - - /** - * Returns the description of the cronjob. - */ - public static function getDescription() - { - return _('Entfernt abgelaufene Log-Einträge sowohl für das ' - .'Eventsystem als auch für die Cronjobs'); - } - - /** - * Return the paremeters for this cronjob. - * - * @return Array Parameters. - */ - public static function getParameters() - { - return [ - 'cronjobs' => [ - 'type' => 'boolean', - 'default' => true, - 'status' => 'optional', - 'description' => _('Sollen die Logeinträge für Cronjobs auch gelöscht werden'), - ], - 'cronjobs-success' => [ - 'type' => 'integer', - 'default' => 7, - 'status' => 'optional', - 'description' => _('Nach wievielen Tagen sollen Logeinträge für ' - .'erfolgreiche Cronjobs gelöscht werden (0 für nie)'), - ], - 'cronjobs-error' => [ - 'type' => 'integer', - 'default' => 28, - 'status' => 'optional', - 'description' => _('Nach wievielen Tagen sollen Logeinträge für ' - .'fehlgeschlagene Cronjobs gelöscht werden (0 für nie)'), - ], - ]; - } - - /** - * Executes the cronjob. - * - * @param mixed $last_result What the last execution of this cronjob - * returned. - * @param Array $parameters Parameters for this cronjob instance which - * were defined during scheduling. - */ - public function execute($last_result, $parameters = []) - { - $event_log = new EventLog(); - $event_log->cleanup_log_events(); - - if (!empty($parameters['cronjobs'])) { - $delete = function($l) {$l->delete();}; - if ($parameters['cronjobs-error'] > 0) { - CronjobLog::findEachBySql( - $delete, - "exception IS NOT NULL AND executed + ? < UNIX_TIMESTAMP()", - [$parameters['cronjobs-error'] * 24 * 60 * 60] - ); - } - if ($parameters['cronjobs-success'] > 0) { - CronjobLog::findEachBySql( - $delete, - "exception IS NULL AND executed + ? < UNIX_TIMESTAMP()", - [$parameters['cronjobs-success'] * 24 * 60 * 60] - ); - } - } - } -} diff --git a/lib/cronjobs/cleanup_log.php b/lib/cronjobs/cleanup_log.php new file mode 100644 index 0000000..378a6ec --- /dev/null +++ b/lib/cronjobs/cleanup_log.php @@ -0,0 +1,114 @@ + + * @author Jan-Hendrik Willms + * @access public + * @since 2.4 + */ + +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// cleanup_log.class.php +// +// Copyright (C) 2013 Jan-Hendrik Willms +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +require_once 'lib/classes/CronJob.class.php'; + +class CleanupLogJob extends CronJob +{ + /** + * Returns the name of the cronjob. + */ + public static function getName() + { + return _('Logs aufräumen'); + } + + /** + * Returns the description of the cronjob. + */ + public static function getDescription() + { + return _('Entfernt abgelaufene Log-Einträge sowohl für das ' + .'Eventsystem als auch für die Cronjobs'); + } + + /** + * Return the paremeters for this cronjob. + * + * @return Array Parameters. + */ + public static function getParameters() + { + return [ + 'cronjobs' => [ + 'type' => 'boolean', + 'default' => true, + 'status' => 'optional', + 'description' => _('Sollen die Logeinträge für Cronjobs auch gelöscht werden'), + ], + 'cronjobs-success' => [ + 'type' => 'integer', + 'default' => 7, + 'status' => 'optional', + 'description' => _('Nach wievielen Tagen sollen Logeinträge für ' + .'erfolgreiche Cronjobs gelöscht werden (0 für nie)'), + ], + 'cronjobs-error' => [ + 'type' => 'integer', + 'default' => 28, + 'status' => 'optional', + 'description' => _('Nach wievielen Tagen sollen Logeinträge für ' + .'fehlgeschlagene Cronjobs gelöscht werden (0 für nie)'), + ], + ]; + } + + /** + * Executes the cronjob. + * + * @param mixed $last_result What the last execution of this cronjob + * returned. + * @param Array $parameters Parameters for this cronjob instance which + * were defined during scheduling. + */ + public function execute($last_result, $parameters = []) + { + $event_log = new EventLog(); + $event_log->cleanup_log_events(); + + if (!empty($parameters['cronjobs'])) { + $delete = function($l) {$l->delete();}; + if ($parameters['cronjobs-error'] > 0) { + CronjobLog::findEachBySql( + $delete, + "exception IS NOT NULL AND executed + ? < UNIX_TIMESTAMP()", + [$parameters['cronjobs-error'] * 24 * 60 * 60] + ); + } + if ($parameters['cronjobs-success'] > 0) { + CronjobLog::findEachBySql( + $delete, + "exception IS NULL AND executed + ? < UNIX_TIMESTAMP()", + [$parameters['cronjobs-success'] * 24 * 60 * 60] + ); + } + } + } +} diff --git a/lib/cronjobs/garbage_collector.class.php b/lib/cronjobs/garbage_collector.class.php deleted file mode 100644 index bab44b2..0000000 --- a/lib/cronjobs/garbage_collector.class.php +++ /dev/null @@ -1,202 +0,0 @@ -, Suchi & Berg GmbH -* @access public -* @since 2.4 -*/ -require_once 'lib/classes/CronJob.class.php'; - -class GarbageCollectorJob extends CronJob -{ - - public static function getName() - { - return _('Datenbank bereinigen'); - } - - public static function getDescription() - { - return _('Entfernt endgültig gelöschte Nachrichten, nicht zugehörige Dateianhänge, abgelaufene Ankündigungen, ' - . 'alte Aktivitäten, veraltete Plugin-Assets sowie veraltete OAuth-Servernonces und abgelaufene ' - . 'Terminblöcke'); - } - - public static function getParameters() - { - return [ - 'verbose' => [ - 'type' => 'boolean', - 'default' => false, - 'status' => 'optional', - 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), - ], - 'news_deletion_days' => [ - 'type' => 'integer', - 'default' => 365, - 'status' => 'optional', - 'description' => _('(Ankündigungen): Nach wie vielen Tagen sollen die abgelaufenen ' - .'Ankündigungen gelöscht werden (0 für Zeitpunkt des Ablaufdatums, Default: 365 Tage)?'), - ], - 'message_deletion_days' => [ - 'type' => 'integer', - 'default' => 30, - 'status' => 'optional', - 'description' => _('(Systemnachrichten): Nach wie vielen Tagen sollen die ' - .'Systemnachrichten gelöscht werden (0 für sofort, Default: 30 Tage)?'), - ], - ]; - } - - public function execute($last_result, $parameters = []) - { - $db = DBManager::get(); - - if ($parameters['verbose']) { - $message_count_before = DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `message`"); - } - - // delete outdated news - if (Config::get()->NEWS_DISABLE_GARBAGE_COLLECT) { - $news_deletion_days = false; - } else { - $news_deletion_days = $parameters['news_deletion_days'] * 86400; - } - $deleted_news = StudipNews::DoGarbageCollect($news_deletion_days); - - // delete messages - $query = "DELETE `message`, `message_user`, `message_tags` - FROM `message` - RIGHT JOIN ( - SELECT `message_id` - FROM `message_user` - GROUP BY `message_id` - HAVING COUNT(`message_id`) = SUM(`deleted`) - ) AS tmp USING (`message_id`) - LEFT JOIN `message_user` USING (`message_id`) - LEFT JOIN `message_tags` USING (`message_id`)"; - DBManager::get()->execute($query); - - // delete system messages - $query = "DELETE `message`, `message_user`, `message_tags` - FROM `message` - LEFT JOIN `message_user` USING (`message_id`) - LEFT JOIN `message_tags` USING (`message_id`) - WHERE `autor_id` = '____%system%____' - AND DATE(FROM_UNIXTIME(`message`.`mkdate`)) + INTERVAL :days DAY < DATE(NOW())"; - DBManager::get()->execute($query, [ - ':days' => $parameters['message_deletion_days'], - ]); - - // Remove outdated opengraph urls - $query = "DELETE FROM `opengraphdata` - WHERE `last_update` < UNIX_TIMESTAMP(NOW() - INTERVAL 1 WEEK)"; - DBManager::get()->exec($query); - - //delete old attachments of non-sent and deleted messages: - //A folder is old and not attached to a message when it has the - //range type 'message', belongs to the folder type 'MessageFolder', - //is older than 2 hours and has a range-ID that doesn't exist - //in the "message" table. - $unsent_attachment_folders = Folder::deleteBySql( - "folder_type = 'MessageFolder' - AND - range_type = 'message' - AND - chdate < UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL -2 HOUR)) - AND - range_id NOT IN ( - SELECT message_id FROM message - )" - ); - - //delete old attachments of non-stored and deleted mvv objects: - //A folder is old and not attached to a mvv object when it has a mvv - //range type, belongs to the folder type 'MVVFolder', - //is older than 2 hours and has a range-ID that doesn't exist. - $unsent_mvv_folders = Folder::deleteBySql( - "LEFT JOIN `file_refs` ON (`file_refs`.`folder_id` = `folders`.`id`) - LEFT JOIN `mvv_files_filerefs` ON (`file_refs`.`id` = `mvv_files_filerefs`.`fileref_id`) - LEFT JOIN `mvv_files_ranges` USING (`mvvfile_id`) - WHERE `folders`.`folder_type` = 'MVVFolder' - AND `folders`.`chdate` < UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL -2 HOUR)) - AND ((`mvv_files_ranges`.`range_type` = 'Studiengang' AND `mvv_files_ranges`.`range_id` NOT IN ( SELECT `studiengang_id` FROM `mvv_studiengang`)) - OR (`mvv_files_ranges`.`range_type` = 'AbschlussKategorie' AND `mvv_files_ranges`.`range_id` NOT IN ( SELECT `kategorie_id` FROM `mvv_abschl_kategorie`)) - OR (`mvv_files_ranges`.`range_type` = 'StgteilVersion' AND `mvv_files_ranges`.`range_id` NOT IN ( SELECT `version_id` FROM `mvv_stgteilversion`)))" - ); - if ($unsent_mvv_folders) { - $db->exec("DELETE FROM mvv_files_filerefs WHERE fileref_id NOT IN (SELECT id FROM file_refs)"); - $db->exec("DELETE FROM mvv_files WHERE mvvfile_id NOT IN (SELECT mvvfile_id FROM mvv_files_filerefs)"); - $db->exec("DELETE FROM mvv_files_ranges WHERE mvvfile_id NOT IN (SELECT mvvfile_id FROM mvv_files)"); - } - - if ($parameters['verbose']) { - $message_count_after = DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `message`"); - - printf(_("Gelöschte Ankündigungen: %u") . "\n", (int)$deleted_news); - printf(_("Gelöschte Nachrichten: %u") . "\n", $message_count_before - $message_count_after); - printf(_("Gelöschte Dateianhänge: %u") . "\n", $unsent_attachment_folders); - printf(_("Gelöschte MVV-Dateien: %u") . "\n", $unsent_mvv_folders); - } - - Token::deleteBySQL('expiration < UNIX_TIMESTAMP()'); - PersonalNotifications::doGarbageCollect(); - - Studip\Activity\Activity::doGarbageCollect(); - - // remove old entries from the table "object_user_visits". - if (Config::get()->NEW_INDICATOR_THRESHOLD) { - $query = "DELETE FROM `object_user_visits` - WHERE GREATEST(`visitdate`, `last_visitdate`) < UNIX_TIMESTAMP(NOW() - INTERVAL :expires DAY)"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':expires', (int) Config::get()->NEW_INDICATOR_THRESHOLD, PDO::PARAM_INT); - $statement->execute(); - } - - // Remove outdated entries from forum_visits - $query = "DELETE FROM `forum_visits` - WHERE GREATEST(`visitdate`, `last_visitdate`) < UNIX_TIMESTAMP() - :threshold"; - DBManager::get()->execute($query, [ - ':threshold' => ForumVisit::LAST_VISIT_MAX, - ]); - - // clean db cache - $cache = new Studip\Cache\DbCache(); - $cache->purge(); - - // Remove old plugin assets - PluginAsset::deleteBySQL('chdate < ?', [time() - PluginAsset::CACHE_DURATION]); - - // Remove expired oauth server nonces - $query = "DELETE FROM `oauth_server_nonce` - WHERE `osn_timestamp` < UNIX_TIMESTAMP(NOW() - INTERVAL 6 HOUR)"; - $removed = DBManager::get()->exec($query); - - if ($removed > 0 && $parameters['verbose']) { - printf(_('Gelöschte Server-Nonces: %u') . "\n", (int)$removed); - } - - // Remove expired consultation slots - $condition = "LEFT JOIN `consultation_slots` USING (`block_id`) - JOIN `config_values` - ON `config_values`.`range_id` = `consultation_blocks`.`range_id` - AND `field` = 'CONSULTATION_GARBAGE_COLLECT' - AND `value` = '1' - GROUP BY `block_id` - HAVING COUNT(`slot_id`) = SUM(`end_time` < UNIX_TIMESTAMP())"; - $removed = ConsultationBlock::deleteBySQL($condition); - if ($removed > 0 && $parameters['verbose']) { - printf(_('Gelöschte Terminblöcke: %u') . "\n", $removed); - } - - // Remove expired tfa tokens - TFAToken::deleteBySQL( - 'mkdate < UNIX_TIMESTAMP() - ?', - [TFASecret::getGreatestValidityDuration()] - ); - - // Remove expired solved captcha challenges - CaptchaChallenge::gc(); - } -} diff --git a/lib/cronjobs/garbage_collector.php b/lib/cronjobs/garbage_collector.php new file mode 100644 index 0000000..bab44b2 --- /dev/null +++ b/lib/cronjobs/garbage_collector.php @@ -0,0 +1,202 @@ +, Suchi & Berg GmbH +* @access public +* @since 2.4 +*/ +require_once 'lib/classes/CronJob.class.php'; + +class GarbageCollectorJob extends CronJob +{ + + public static function getName() + { + return _('Datenbank bereinigen'); + } + + public static function getDescription() + { + return _('Entfernt endgültig gelöschte Nachrichten, nicht zugehörige Dateianhänge, abgelaufene Ankündigungen, ' + . 'alte Aktivitäten, veraltete Plugin-Assets sowie veraltete OAuth-Servernonces und abgelaufene ' + . 'Terminblöcke'); + } + + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), + ], + 'news_deletion_days' => [ + 'type' => 'integer', + 'default' => 365, + 'status' => 'optional', + 'description' => _('(Ankündigungen): Nach wie vielen Tagen sollen die abgelaufenen ' + .'Ankündigungen gelöscht werden (0 für Zeitpunkt des Ablaufdatums, Default: 365 Tage)?'), + ], + 'message_deletion_days' => [ + 'type' => 'integer', + 'default' => 30, + 'status' => 'optional', + 'description' => _('(Systemnachrichten): Nach wie vielen Tagen sollen die ' + .'Systemnachrichten gelöscht werden (0 für sofort, Default: 30 Tage)?'), + ], + ]; + } + + public function execute($last_result, $parameters = []) + { + $db = DBManager::get(); + + if ($parameters['verbose']) { + $message_count_before = DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `message`"); + } + + // delete outdated news + if (Config::get()->NEWS_DISABLE_GARBAGE_COLLECT) { + $news_deletion_days = false; + } else { + $news_deletion_days = $parameters['news_deletion_days'] * 86400; + } + $deleted_news = StudipNews::DoGarbageCollect($news_deletion_days); + + // delete messages + $query = "DELETE `message`, `message_user`, `message_tags` + FROM `message` + RIGHT JOIN ( + SELECT `message_id` + FROM `message_user` + GROUP BY `message_id` + HAVING COUNT(`message_id`) = SUM(`deleted`) + ) AS tmp USING (`message_id`) + LEFT JOIN `message_user` USING (`message_id`) + LEFT JOIN `message_tags` USING (`message_id`)"; + DBManager::get()->execute($query); + + // delete system messages + $query = "DELETE `message`, `message_user`, `message_tags` + FROM `message` + LEFT JOIN `message_user` USING (`message_id`) + LEFT JOIN `message_tags` USING (`message_id`) + WHERE `autor_id` = '____%system%____' + AND DATE(FROM_UNIXTIME(`message`.`mkdate`)) + INTERVAL :days DAY < DATE(NOW())"; + DBManager::get()->execute($query, [ + ':days' => $parameters['message_deletion_days'], + ]); + + // Remove outdated opengraph urls + $query = "DELETE FROM `opengraphdata` + WHERE `last_update` < UNIX_TIMESTAMP(NOW() - INTERVAL 1 WEEK)"; + DBManager::get()->exec($query); + + //delete old attachments of non-sent and deleted messages: + //A folder is old and not attached to a message when it has the + //range type 'message', belongs to the folder type 'MessageFolder', + //is older than 2 hours and has a range-ID that doesn't exist + //in the "message" table. + $unsent_attachment_folders = Folder::deleteBySql( + "folder_type = 'MessageFolder' + AND + range_type = 'message' + AND + chdate < UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL -2 HOUR)) + AND + range_id NOT IN ( + SELECT message_id FROM message + )" + ); + + //delete old attachments of non-stored and deleted mvv objects: + //A folder is old and not attached to a mvv object when it has a mvv + //range type, belongs to the folder type 'MVVFolder', + //is older than 2 hours and has a range-ID that doesn't exist. + $unsent_mvv_folders = Folder::deleteBySql( + "LEFT JOIN `file_refs` ON (`file_refs`.`folder_id` = `folders`.`id`) + LEFT JOIN `mvv_files_filerefs` ON (`file_refs`.`id` = `mvv_files_filerefs`.`fileref_id`) + LEFT JOIN `mvv_files_ranges` USING (`mvvfile_id`) + WHERE `folders`.`folder_type` = 'MVVFolder' + AND `folders`.`chdate` < UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL -2 HOUR)) + AND ((`mvv_files_ranges`.`range_type` = 'Studiengang' AND `mvv_files_ranges`.`range_id` NOT IN ( SELECT `studiengang_id` FROM `mvv_studiengang`)) + OR (`mvv_files_ranges`.`range_type` = 'AbschlussKategorie' AND `mvv_files_ranges`.`range_id` NOT IN ( SELECT `kategorie_id` FROM `mvv_abschl_kategorie`)) + OR (`mvv_files_ranges`.`range_type` = 'StgteilVersion' AND `mvv_files_ranges`.`range_id` NOT IN ( SELECT `version_id` FROM `mvv_stgteilversion`)))" + ); + if ($unsent_mvv_folders) { + $db->exec("DELETE FROM mvv_files_filerefs WHERE fileref_id NOT IN (SELECT id FROM file_refs)"); + $db->exec("DELETE FROM mvv_files WHERE mvvfile_id NOT IN (SELECT mvvfile_id FROM mvv_files_filerefs)"); + $db->exec("DELETE FROM mvv_files_ranges WHERE mvvfile_id NOT IN (SELECT mvvfile_id FROM mvv_files)"); + } + + if ($parameters['verbose']) { + $message_count_after = DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `message`"); + + printf(_("Gelöschte Ankündigungen: %u") . "\n", (int)$deleted_news); + printf(_("Gelöschte Nachrichten: %u") . "\n", $message_count_before - $message_count_after); + printf(_("Gelöschte Dateianhänge: %u") . "\n", $unsent_attachment_folders); + printf(_("Gelöschte MVV-Dateien: %u") . "\n", $unsent_mvv_folders); + } + + Token::deleteBySQL('expiration < UNIX_TIMESTAMP()'); + PersonalNotifications::doGarbageCollect(); + + Studip\Activity\Activity::doGarbageCollect(); + + // remove old entries from the table "object_user_visits". + if (Config::get()->NEW_INDICATOR_THRESHOLD) { + $query = "DELETE FROM `object_user_visits` + WHERE GREATEST(`visitdate`, `last_visitdate`) < UNIX_TIMESTAMP(NOW() - INTERVAL :expires DAY)"; + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':expires', (int) Config::get()->NEW_INDICATOR_THRESHOLD, PDO::PARAM_INT); + $statement->execute(); + } + + // Remove outdated entries from forum_visits + $query = "DELETE FROM `forum_visits` + WHERE GREATEST(`visitdate`, `last_visitdate`) < UNIX_TIMESTAMP() - :threshold"; + DBManager::get()->execute($query, [ + ':threshold' => ForumVisit::LAST_VISIT_MAX, + ]); + + // clean db cache + $cache = new Studip\Cache\DbCache(); + $cache->purge(); + + // Remove old plugin assets + PluginAsset::deleteBySQL('chdate < ?', [time() - PluginAsset::CACHE_DURATION]); + + // Remove expired oauth server nonces + $query = "DELETE FROM `oauth_server_nonce` + WHERE `osn_timestamp` < UNIX_TIMESTAMP(NOW() - INTERVAL 6 HOUR)"; + $removed = DBManager::get()->exec($query); + + if ($removed > 0 && $parameters['verbose']) { + printf(_('Gelöschte Server-Nonces: %u') . "\n", (int)$removed); + } + + // Remove expired consultation slots + $condition = "LEFT JOIN `consultation_slots` USING (`block_id`) + JOIN `config_values` + ON `config_values`.`range_id` = `consultation_blocks`.`range_id` + AND `field` = 'CONSULTATION_GARBAGE_COLLECT' + AND `value` = '1' + GROUP BY `block_id` + HAVING COUNT(`slot_id`) = SUM(`end_time` < UNIX_TIMESTAMP())"; + $removed = ConsultationBlock::deleteBySQL($condition); + if ($removed > 0 && $parameters['verbose']) { + printf(_('Gelöschte Terminblöcke: %u') . "\n", $removed); + } + + // Remove expired tfa tokens + TFAToken::deleteBySQL( + 'mkdate < UNIX_TIMESTAMP() - ?', + [TFASecret::getGreatestValidityDuration()] + ); + + // Remove expired solved captcha challenges + CaptchaChallenge::gc(); + } +} diff --git a/lib/cronjobs/purge_cache.class.php b/lib/cronjobs/purge_cache.class.php deleted file mode 100644 index d2e3697..0000000 --- a/lib/cronjobs/purge_cache.class.php +++ /dev/null @@ -1,92 +0,0 @@ -, Suchi & Berg GmbH - * @author Jan-Hendrik Willms - * @access public - * @since 2.4 - */ - -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// purge_cache.class.php -// -// Copyright (C) 2013 Jan-Hendrik Willms -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -require_once 'lib/classes/CronJob.class.php'; - -class PurgeCacheJob extends CronJob -{ - /** - * Returns the name of the cronjob. - */ - public static function getName() - { - return _('Dateicache leeren'); - } - - /** - * Returns the description of the cronjob. - */ - public static function getDescription() - { - return _('Leert den dateibasierten Cache'); - } - - /** - * Return the paremeters for this cronjob. - * - * @return Array Parameters. - */ - public static function getParameters() - { - return [ - 'verbose' => [ - 'type' => 'boolean', - 'default' => false, - 'status' => 'optional', - 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), - ], - ]; - } - - /** - * Setup method. Loads the neccessary classes. - */ - public function setUp() - { - require_once 'lib/classes/cache/FileCache.class.php'; - } - - /** - * Execute the cronjob. - * - * @param mixed $last_result What the last execution of this cronjob - * returned. - * @param Array $parameters Parameters for this cronjob instance which - * were defined during scheduling. - * Only valid parameter at the moment is - * "verbose" which toggles verbose output while - * purging the cache. - */ - public function execute($last_result, $parameters = []) - { - $cache = new \Studip\Cache\FileCache(); - $cache->purge(empty($parameters['verbose'])); - } -} diff --git a/lib/cronjobs/purge_cache.php b/lib/cronjobs/purge_cache.php new file mode 100644 index 0000000..d2e3697 --- /dev/null +++ b/lib/cronjobs/purge_cache.php @@ -0,0 +1,92 @@ +, Suchi & Berg GmbH + * @author Jan-Hendrik Willms + * @access public + * @since 2.4 + */ + +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// purge_cache.class.php +// +// Copyright (C) 2013 Jan-Hendrik Willms +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +require_once 'lib/classes/CronJob.class.php'; + +class PurgeCacheJob extends CronJob +{ + /** + * Returns the name of the cronjob. + */ + public static function getName() + { + return _('Dateicache leeren'); + } + + /** + * Returns the description of the cronjob. + */ + public static function getDescription() + { + return _('Leert den dateibasierten Cache'); + } + + /** + * Return the paremeters for this cronjob. + * + * @return Array Parameters. + */ + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), + ], + ]; + } + + /** + * Setup method. Loads the neccessary classes. + */ + public function setUp() + { + require_once 'lib/classes/cache/FileCache.class.php'; + } + + /** + * Execute the cronjob. + * + * @param mixed $last_result What the last execution of this cronjob + * returned. + * @param Array $parameters Parameters for this cronjob instance which + * were defined during scheduling. + * Only valid parameter at the moment is + * "verbose" which toggles verbose output while + * purging the cache. + */ + public function execute($last_result, $parameters = []) + { + $cache = new \Studip\Cache\FileCache(); + $cache->purge(empty($parameters['verbose'])); + } +} diff --git a/lib/cronjobs/remind_oer_upload.class.php b/lib/cronjobs/remind_oer_upload.class.php deleted file mode 100644 index a1a8ee1..0000000 --- a/lib/cronjobs/remind_oer_upload.class.php +++ /dev/null @@ -1,73 +0,0 @@ -, Suchi & Berg GmbH - * @access public - * @since 5.2 - */ - -require_once 'lib/classes/CronJob.class.php'; - -class RemindOerUpload extends CronJob -{ - - public static function getName() - { - return _('An OER-Campus Upload erinnern'); - } - - public static function getDescription() - { - return _('Erinnert den Autor am Ende des Semesters an eine Datei, die in den OER-Campus hochgeladen werden soll.'); - } - - public function execute($last_result, $parameters = []) - { - // check the reminder date, which now is in past - $query = "SELECT `file_ref_id` FROM `oer_post_upload` - WHERE `reminder_date` < UNIX_TIMESTAMP()"; - $results = DBManager::get()->fetchAll($query); - - // get file information from file_ref_id - foreach ($results as $result) { - $file_ref = FileRef::find($result['file_ref_id']); - - if (!FileRef::countBySql('id = ?', [$result['file_ref_id']])) { - // file might be deleted meanwhile, so do not try to send a reminder for it - } else { - $filetype = $file_ref->getFileType(); - $file_to_suggest = $filetype->convertToStandardFile(); - - $author = $file_ref->owner->username; - $link_to_share = URLHelper::getURL('dispatch.php/file/share_oer/' . $result['file_ref_id']); - $linktext = _('Klicken Sie hier, um das Material im OER-Campus zu veröffentlichen.'); - $formatted_link = '['. $linktext .']' . $link_to_share; - - $oer_reminder_message = sprintf(_("Sie wollten daran erinnert werden, die folgende Datei im OER-Campus zu veröffentlichen:\n\n" - . "Dateiname: %s \n" - . "Beschreibung: %s \n" - . "%s \n\n"), - $file_to_suggest->getFilename(), - $file_to_suggest->getDescription(), - $formatted_link - ); - - $messaging = new messaging(); - - $messaging->insert_message( - $oer_reminder_message, - $author, - '____%system%____', - '', - Request::option('message_id'), - '', - null, - _('Erinnerung zur Veröffentlichung einer Datei im OER-Campus') - ); - - OERPostUpload::deleteBySQL("file_ref_id = ?", [$result['file_ref_id']]); - } - } - } -} diff --git a/lib/cronjobs/remind_oer_upload.php b/lib/cronjobs/remind_oer_upload.php new file mode 100644 index 0000000..a1a8ee1 --- /dev/null +++ b/lib/cronjobs/remind_oer_upload.php @@ -0,0 +1,73 @@ +, Suchi & Berg GmbH + * @access public + * @since 5.2 + */ + +require_once 'lib/classes/CronJob.class.php'; + +class RemindOerUpload extends CronJob +{ + + public static function getName() + { + return _('An OER-Campus Upload erinnern'); + } + + public static function getDescription() + { + return _('Erinnert den Autor am Ende des Semesters an eine Datei, die in den OER-Campus hochgeladen werden soll.'); + } + + public function execute($last_result, $parameters = []) + { + // check the reminder date, which now is in past + $query = "SELECT `file_ref_id` FROM `oer_post_upload` + WHERE `reminder_date` < UNIX_TIMESTAMP()"; + $results = DBManager::get()->fetchAll($query); + + // get file information from file_ref_id + foreach ($results as $result) { + $file_ref = FileRef::find($result['file_ref_id']); + + if (!FileRef::countBySql('id = ?', [$result['file_ref_id']])) { + // file might be deleted meanwhile, so do not try to send a reminder for it + } else { + $filetype = $file_ref->getFileType(); + $file_to_suggest = $filetype->convertToStandardFile(); + + $author = $file_ref->owner->username; + $link_to_share = URLHelper::getURL('dispatch.php/file/share_oer/' . $result['file_ref_id']); + $linktext = _('Klicken Sie hier, um das Material im OER-Campus zu veröffentlichen.'); + $formatted_link = '['. $linktext .']' . $link_to_share; + + $oer_reminder_message = sprintf(_("Sie wollten daran erinnert werden, die folgende Datei im OER-Campus zu veröffentlichen:\n\n" + . "Dateiname: %s \n" + . "Beschreibung: %s \n" + . "%s \n\n"), + $file_to_suggest->getFilename(), + $file_to_suggest->getDescription(), + $formatted_link + ); + + $messaging = new messaging(); + + $messaging->insert_message( + $oer_reminder_message, + $author, + '____%system%____', + '', + Request::option('message_id'), + '', + null, + _('Erinnerung zur Veröffentlichung einer Datei im OER-Campus') + ); + + OERPostUpload::deleteBySQL("file_ref_id = ?", [$result['file_ref_id']]); + } + } + } +} diff --git a/lib/cronjobs/send_mail_notifications.class.php b/lib/cronjobs/send_mail_notifications.class.php deleted file mode 100644 index 5565218..0000000 --- a/lib/cronjobs/send_mail_notifications.class.php +++ /dev/null @@ -1,141 +0,0 @@ -, Suchi & Berg GmbH - * @author Jan-Hendrik Willms - * @access public - */ - -// +---------------------------------------------------------------------------+ -// This file is part of Stud.IP -// send_mail_notifications.php -// -// Copyright (C) 2013 Jan-Hendrik Willms -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - - -// TODO: notifications for plugins not implemented - -class SendMailNotificationsJob extends CronJob -{ - /** - * Returns the name of the cronjob. - */ - public static function getName() - { - return _('Versendet tägliche E-Mailbenachrichtigungen'); - } - - /** - * Returns the description of the cronjob. - */ - public static function getDescription() - { - return _('Versendet die täglichen E-Mailbenachrichtigungen an alle Nutzer, die diese aktiviert haben'); - } - - /** - * Setup method. Loads neccessary classes and checks environment. Will - * bail out with an exception if environment does not match requirements. - */ - public function setUp() - { - require_once 'lib/dates.inc.php'; - - if (!Config::get()->MAIL_NOTIFICATION_ENABLE) { - throw new Exception('Mail notifications are disabled in this Stud.IP installation.'); - } - if (empty($GLOBALS['ABSOLUTE_URI_STUDIP'])) { - throw new Exception('To use mail notifications you MUST set correct values for $ABSOLUTE_URI_STUDIP in config_local.inc.php!'); - } - } - - /** - * Return the paremeters for this cronjob. - * - * @return Array Parameters. - */ - public static function getParameters() - { - return [ - 'verbose' => [ - 'type' => 'boolean', - 'default' => false, - 'status' => 'optional', - 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), - ], - ]; - } - - /** - * Executes the cronjob. - * - * @param mixed $last_result What the last execution of this cronjob - * returned. - * @param Array $parameters Parameters for this cronjob instance which - * were defined during scheduling. - * Only valid parameter at the moment is - * "verbose" which toggles verbose output while - * purging the cache. - */ - public function execute($last_result, $parameters = []) - { - $cli_user = $GLOBALS['user']; - - $notification = new ModulesNotification(); - - $query = "SELECT DISTINCT user_id - FROM seminar_user_notifications - JOIN seminar_user USING (user_id, seminar_id)"; - DBManager::get()->fetchFirst( - $query, - [], - function ($user_id) use ($parameters, $notification) { - $user = User::find($user_id); - if (!$user || $user->isBlocked()) { - return; - } - - $GLOBALS['user'] = new Seminar_User($user); - - $ok = false; - $mailmessage = $notification->getAllNotifications($user->id); - - if ($mailmessage) { - setTempLanguage('', $user->preferred_language); - - $ok = StudipMail::sendMessage( - $user->email, - "[" . Config::get()->UNI_NAME_CLEAN . "] " . _('Tägliche Benachrichtigung'), - $mailmessage['text'], - $user->config->MAIL_AS_HTML ? $mailmessage['html'] : null - ); - } - - // Unset user configuration cache to preserve memory - UserConfig::set($user->id, null); - - // Log results - if ($ok !== false && $parameters['verbose']) { - echo $user->username . ':' . $ok . "\n"; - } - } - ); - - $GLOBALS['user'] = $cli_user; - } -} diff --git a/lib/cronjobs/send_mail_notifications.php b/lib/cronjobs/send_mail_notifications.php new file mode 100644 index 0000000..5565218 --- /dev/null +++ b/lib/cronjobs/send_mail_notifications.php @@ -0,0 +1,141 @@ +, Suchi & Berg GmbH + * @author Jan-Hendrik Willms + * @access public + */ + +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// send_mail_notifications.php +// +// Copyright (C) 2013 Jan-Hendrik Willms +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + + +// TODO: notifications for plugins not implemented + +class SendMailNotificationsJob extends CronJob +{ + /** + * Returns the name of the cronjob. + */ + public static function getName() + { + return _('Versendet tägliche E-Mailbenachrichtigungen'); + } + + /** + * Returns the description of the cronjob. + */ + public static function getDescription() + { + return _('Versendet die täglichen E-Mailbenachrichtigungen an alle Nutzer, die diese aktiviert haben'); + } + + /** + * Setup method. Loads neccessary classes and checks environment. Will + * bail out with an exception if environment does not match requirements. + */ + public function setUp() + { + require_once 'lib/dates.inc.php'; + + if (!Config::get()->MAIL_NOTIFICATION_ENABLE) { + throw new Exception('Mail notifications are disabled in this Stud.IP installation.'); + } + if (empty($GLOBALS['ABSOLUTE_URI_STUDIP'])) { + throw new Exception('To use mail notifications you MUST set correct values for $ABSOLUTE_URI_STUDIP in config_local.inc.php!'); + } + } + + /** + * Return the paremeters for this cronjob. + * + * @return Array Parameters. + */ + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), + ], + ]; + } + + /** + * Executes the cronjob. + * + * @param mixed $last_result What the last execution of this cronjob + * returned. + * @param Array $parameters Parameters for this cronjob instance which + * were defined during scheduling. + * Only valid parameter at the moment is + * "verbose" which toggles verbose output while + * purging the cache. + */ + public function execute($last_result, $parameters = []) + { + $cli_user = $GLOBALS['user']; + + $notification = new ModulesNotification(); + + $query = "SELECT DISTINCT user_id + FROM seminar_user_notifications + JOIN seminar_user USING (user_id, seminar_id)"; + DBManager::get()->fetchFirst( + $query, + [], + function ($user_id) use ($parameters, $notification) { + $user = User::find($user_id); + if (!$user || $user->isBlocked()) { + return; + } + + $GLOBALS['user'] = new Seminar_User($user); + + $ok = false; + $mailmessage = $notification->getAllNotifications($user->id); + + if ($mailmessage) { + setTempLanguage('', $user->preferred_language); + + $ok = StudipMail::sendMessage( + $user->email, + "[" . Config::get()->UNI_NAME_CLEAN . "] " . _('Tägliche Benachrichtigung'), + $mailmessage['text'], + $user->config->MAIL_AS_HTML ? $mailmessage['html'] : null + ); + } + + // Unset user configuration cache to preserve memory + UserConfig::set($user->id, null); + + // Log results + if ($ok !== false && $parameters['verbose']) { + echo $user->username . ':' . $ok . "\n"; + } + } + ); + + $GLOBALS['user'] = $cli_user; + } +} diff --git a/lib/cronjobs/send_mail_queue.class.php b/lib/cronjobs/send_mail_queue.class.php deleted file mode 100644 index 8f72918..0000000 --- a/lib/cronjobs/send_mail_queue.class.php +++ /dev/null @@ -1,70 +0,0 @@ -, Suchi & Berg GmbH -* @access public -* @since 3.0 -*/ - -/** - * Cronjob class to send the mailqueue each interval. - */ -class SendMailQueueJob extends CronJob -{ - - /** - * Returns the name of the cronjob. - * @return string : name of the cronjob - */ - public static function getName() - { - return _('Mailqueue senden'); - } - - /** - * Returns the description of the cronjob. - * @return string : description of the cronjob. - */ - public static function getDescription() - { - return _('Sendet alle Einträge in der Mailqueue bis zu 24 Stunden, nachdem sie hinzugefügt wurden.'); - } - - /** - * Sends all mails in the queue. - * @param integer $last_result : not evaluated for execution, so any integer - * will do. Usually it would be a unix-timestamp of last execution. But in - * this case we don't care at all. - * @param array $parameters : not needed here - */ - public function execute($last_result, $parameters = []) - { - $status_messages = MailQueueEntry::sendAll( - Config::get()->MAILQUEUE_SEND_LIMIT, - (bool)$parameters['verbose'] - ); - - //We output one status message per line: - echo implode("\n", $status_messages); - } - - /** - * Returns a list of available parameters for this cronjob. - * See the description in the CronJob class for a specification - * for the returned array. - * - * @return array A list of available parameters for this cronjob. - */ - public static function getParameters() - { - return [ - 'verbose' => [ - 'type' => 'boolean', - 'default' => false, - 'status' => 'optional', - 'description' => _('Sollen Ausgaben erzeugt werden? Diese sind später im Log des Cronjobs sichtbar.'), - ], - ]; - } -} diff --git a/lib/cronjobs/send_mail_queue.php b/lib/cronjobs/send_mail_queue.php new file mode 100644 index 0000000..8f72918 --- /dev/null +++ b/lib/cronjobs/send_mail_queue.php @@ -0,0 +1,70 @@ +, Suchi & Berg GmbH +* @access public +* @since 3.0 +*/ + +/** + * Cronjob class to send the mailqueue each interval. + */ +class SendMailQueueJob extends CronJob +{ + + /** + * Returns the name of the cronjob. + * @return string : name of the cronjob + */ + public static function getName() + { + return _('Mailqueue senden'); + } + + /** + * Returns the description of the cronjob. + * @return string : description of the cronjob. + */ + public static function getDescription() + { + return _('Sendet alle Einträge in der Mailqueue bis zu 24 Stunden, nachdem sie hinzugefügt wurden.'); + } + + /** + * Sends all mails in the queue. + * @param integer $last_result : not evaluated for execution, so any integer + * will do. Usually it would be a unix-timestamp of last execution. But in + * this case we don't care at all. + * @param array $parameters : not needed here + */ + public function execute($last_result, $parameters = []) + { + $status_messages = MailQueueEntry::sendAll( + Config::get()->MAILQUEUE_SEND_LIMIT, + (bool)$parameters['verbose'] + ); + + //We output one status message per line: + echo implode("\n", $status_messages); + } + + /** + * Returns a list of available parameters for this cronjob. + * See the description in the CronJob class for a specification + * for the returned array. + * + * @return array A list of available parameters for this cronjob. + */ + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden? Diese sind später im Log des Cronjobs sichtbar.'), + ], + ]; + } +} diff --git a/lib/cronjobs/session_gc.class.php b/lib/cronjobs/session_gc.class.php deleted file mode 100644 index 4289a29..0000000 --- a/lib/cronjobs/session_gc.class.php +++ /dev/null @@ -1,29 +0,0 @@ -, Suchi & Berg GmbH -* @access public -* @since 2.4 -*/ - -class SessionGcJob extends CronJob -{ - - public static function getName() - { - return _('Sessions bereinigen'); - } - - public static function getDescription() - { - return _('Entfernt abgelaufene session Daten'); - } - - public function execute($last_result, $parameters = []) - { - $sess = new Seminar_Session(); - $sess->set_container(); - return $sess->gc(); - } -} diff --git a/lib/cronjobs/session_gc.php b/lib/cronjobs/session_gc.php new file mode 100644 index 0000000..4289a29 --- /dev/null +++ b/lib/cronjobs/session_gc.php @@ -0,0 +1,29 @@ +, Suchi & Berg GmbH +* @access public +* @since 2.4 +*/ + +class SessionGcJob extends CronJob +{ + + public static function getName() + { + return _('Sessions bereinigen'); + } + + public static function getDescription() + { + return _('Entfernt abgelaufene session Daten'); + } + + public function execute($last_result, $parameters = []) + { + $sess = new Seminar_Session(); + $sess->set_container(); + return $sess->gc(); + } +} diff --git a/lib/elearning/ConnectedCMS.class.php b/lib/elearning/ConnectedCMS.class.php deleted file mode 100644 index 3dd3bfb..0000000 --- a/lib/elearning/ConnectedCMS.class.php +++ /dev/null @@ -1,466 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ConnectedCMS -* @package ELearning-Interface -*/ -class ConnectedCMS -{ - public $title; - - public $is_active; - public $cms_type; - public $name = null; - public $ABSOLUTE_PATH_ELEARNINGMODULES = null; - public $ABSOLUTE_PATH_SOAP = null; - public $RELATIVE_PATH_DB_CLASSES = false; - public $CLASS_PREFIX = null; - public $auth_necessary = null; - public $USER_AUTO_CREATE = null; - public $USER_PREFIX = null; - public $target_file = null; - public $logo_file = null; - public $db_classes; - public $soap_data = null; - public $soap_client; - public $types = null; - public $roles = null; - - public $db; - public $db_class; - public $link; - public $user; - public $permissions; - public $content_module; - - /** - * constructor - * - * init class. don't call directly but by extending class ("new Ilias3ConnectedCMS($cms)" for example), except for basic administration - * @access - * @param string $cms system-type - */ - public function __construct($cms = "") - { - $this->cms_type = $cms; - $this->is_active = (bool) Config::get()->getValue("ELEARNING_INTERFACE_{$cms}_ACTIVE"); - - if ($cms) { - $this->init($cms); - } - } - - /** - * init settings - * - * gets settings from config-array and initializes db - * @access private - * @param string $cms system-type - */ - public function init($cms) - { - global $ELEARNING_INTERFACE_MODULES; - - $this->name = $ELEARNING_INTERFACE_MODULES[$cms]['name'] ?? null; - $this->ABSOLUTE_PATH_ELEARNINGMODULES = $ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_ELEARNINGMODULES"]; - $this->ABSOLUTE_PATH_SOAP = $ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_SOAP"]; - if (isset($ELEARNING_INTERFACE_MODULES[$cms]["RELATIVE_PATH_DB_CLASSES"])) { - $this->RELATIVE_PATH_DB_CLASSES = $ELEARNING_INTERFACE_MODULES[$cms]["RELATIVE_PATH_DB_CLASSES"]; - $this->db_classes = $ELEARNING_INTERFACE_MODULES[$cms]["db_classes"]; - } else { - $this->RELATIVE_PATH_DB_CLASSES = false; - } - $this->CLASS_PREFIX = $ELEARNING_INTERFACE_MODULES[$cms]['CLASS_PREFIX']; - $this->auth_necessary = $ELEARNING_INTERFACE_MODULES[$cms]['auth_necessary']; - $this->USER_AUTO_CREATE = $ELEARNING_INTERFACE_MODULES[$cms]['USER_AUTO_CREATE'] ?? null; - $this->USER_PREFIX = $ELEARNING_INTERFACE_MODULES[$cms]['USER_PREFIX'] ?? null; - $this->target_file = $ELEARNING_INTERFACE_MODULES[$cms]['target_file'] ?? null; - $this->logo_file = $ELEARNING_INTERFACE_MODULES[$cms]['logo_file'] ?? null; - $this->soap_data = $ELEARNING_INTERFACE_MODULES[$cms]['soap_data'] ?? null; - $this->types = $ELEARNING_INTERFACE_MODULES[$cms]['types'] ?? null; - $this->roles = $ELEARNING_INTERFACE_MODULES[$cms]['roles'] ?? null; - } - - /** - * init subclasses - * - * loads classes for user-functions - * @access public - */ - public function initSubclasses() - { - if ($this->auth_necessary) { - require_once $this->CLASS_PREFIX . "ConnectedUser.class.php"; - $classname = $this->CLASS_PREFIX . "ConnectedUser"; - $this->user = new $classname($this->cms_type); - - require_once $this->CLASS_PREFIX . "ConnectedPermissions.class.php"; - $classname = $this->CLASS_PREFIX . "ConnectedPermissions"; - $this->permissions = new $classname($this->cms_type); - } - require_once $this->CLASS_PREFIX . "ConnectedLink.class.php"; - $classname = $this->CLASS_PREFIX . "ConnectedLink"; - $this->link = new $classname($this->cms_type); - } - - /** - * get connection status - * - * checks settings - * @access public - * @param string $cms system-type - * @return array messages - */ - public function getConnectionStatus($cms = "") - { - $msg = [ - 'path' => [], - ]; - - if ($this->cms_type == "") { - $this->init($cms); - } - - // check connection to CMS - if (!$this->auth_necessary) { - $msg['auth'] = [ - 'info' => _('Eine Authentifizierung ist für dieses System nicht vorgesehen.') - ]; - } - - // check for SOAP-Interface - if (in_array($this->CLASS_PREFIX, ['Ilias3','Ilias4','Ilias5'])) { - $ch = curl_init($this->ABSOLUTE_PATH_ELEARNINGMODULES . 'login.php'); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_exec($ch); - - if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) { - $msg['path']['error'] = sprintf( - _('Die Verbindung zum System "%s" konnte nicht hergestellt werden. Der Pfad "%s" ist ungültig.'), - $this->name, - $this->ABSOLUTE_PATH_ELEARNINGMODULES - ); - - } else { - $msg['path']['info'] = sprintf( - _('Die %s-Installation wurde gefunden.'), - $this->name - ); - } - - $msg['soap'] = []; - if (!Config::get()->SOAP_ENABLE) { - $msg['soap']['error'] = _('Das Stud.IP-Modul für die SOAP-Schnittstelle ist nicht aktiviert. Ändern Sie den entsprechenden Eintrag in der Konfigurationsdatei "local.inc".'); - } elseif (!is_array($this->soap_data)) { - $msg['soap']['error'] = _('Die SOAP-Verbindungsdaten sind für dieses System nicht gesetzt. Ergänzen Sie die Einstellungen für dieses Systems um den Eintrag "soap_data" in der Konfigurationsdatei "local.inc".'); - } else { - $this->soap_client = new StudipSoapClient($this->ABSOLUTE_PATH_SOAP); - $msg['soap']['info'] = _('Das SOAP-Modul ist aktiv.'); - } - } else { - $file = fopen($this->ABSOLUTE_PATH_ELEARNINGMODULES, 'r'); - if ($file === false) { - $msg['path']['error'] = sprintf( - _('Die Verbindung zum System "%s" konnte nicht hergestellt werden. Der Pfad "%s" ist ungültig.'), - $this->name, - $this->ABSOLUTE_PATH_ELEARNINGMODULES - ); - } else { - fclose($file); - $msg['path']['info'] = sprintf( - _("Die %s-Installation wurde gefunden."), - $this->name - ); - - // check if target-file exists - $msg['auth'] = []; - - $file = fopen($this->ABSOLUTE_PATH_ELEARNINGMODULES . $this->target_file, 'r'); - if ($file === false) { - $msg['auth']['error'] = sprintf( - _('Die Zieldatei "%s" liegt nicht im Hauptverzeichnis der %s-Installation.'), - $this->target_file, - $this->name - ); - } else { - fclose($file); - $msg['auth']['info'] = _('Die Zieldatei ist vorhanden.'); - } - } - } - - $el_path = $GLOBALS['STUDIP_BASE_PATH'] . '/lib/elearning'; - // check if needed classes exist - $files = [ - 'class_link' => "{$el_path}/{$this->CLASS_PREFIX}ConnectedLink.class.php", - 'class_content' => "{$el_path}/{$this->CLASS_PREFIX}ContentModule.class.php", - 'class_cms' => "{$el_path}/{$this->CLASS_PREFIX}ConnectedCMS.class.php", - ]; - - if ($this->auth_necessary) { - $files['class_user'] = "{$el_path}/{$this->CLASS_PREFIX}ConnectedUser.class.php"; - $files['class_perm'] = "{$el_path}/{$this->CLASS_PREFIX}ConnectedPermissions.class.php"; - } - - $errors = 0; - foreach ($files as $index => $file) { - if (!file_exists($file)) { - $msg[$index] = [ - 'error' => sprintf(_('Die Datei "%s" existiert nicht.'), $file), - ]; - $errors += 1; - } - } - - $msg['classes'] = []; - if ($errors === 0) { - require_once $files['class_cms']; - $msg['classes']['info'] = sprintf( - _('Die Klassen der Schnittstelle zum System "%s" wurden geladen.'), - $this->name - ); - } else { - $msg['classes']['error'] = sprintf( - _('Die Klassen der Schnittstelle zum System "%s" wurden nicht geladen.'), - $this->name - ); - } - - return $msg; - } - - /** - * get preferences - * - * shows additional settings. can be overwritten by subclass. - * @access public - */ - public function getPreferences() - { - if ($this->types != "") - { - echo "" . _("Angebundene Lernmodul-Typen: ") . ""; - echo "
\n"; - foreach($this->types as $key => $type) - echo Icon::create($type["icon"], Icon::ROLE_INACTIVE)->asImg() . $type["name"] . " ($key)
\n"; - echo "
\n"; - } - - if ($this->db_classes != "") - { - echo "" . _("Verwendete DB-Zugriffs-Klassen: ") . ""; - echo "
\n"; - foreach($this->db_classes as $key => $type) { - echo $type["file"] . " ($key)
\n"; - } - echo "
\n"; - } - } - - /** - * create new instance of subclass content-module with given values - * - * creates new instance of subclass content-module with given values - * @access public - * @param array $data module-data - * @param boolean $is_connected is module connected to seminar? - */ - public function setContentModule($data, $is_connected = false) - { - global $current_module; - $current_module = $data["ref_id"]; - - require_once($this->CLASS_PREFIX . "ContentModule.class.php"); - $classname = $this->CLASS_PREFIX . "ContentModule"; - - $this->content_module[$current_module] = new $classname("", $data["type"], $this->cms_type); - $this->content_module[$current_module]->setId($data["ref_id"]); - $this->content_module[$current_module]->setTitle($data["title"]); - $this->content_module[$current_module]->setDescription($data["description"]); - $this->content_module[$current_module]->setConnectionType($is_connected); - } - - /** - * create new instance of subclass content-module - * - * creates new instance of subclass content-module - * @access public - * @param string $module_id module-id - * @param string $module_type module-type - * @param boolean $is_connected is module connected to seminar? - */ - public function newContentModule($module_id, $module_type, $is_connected = false) - { - global $current_module; - $current_module = $module_id; - - require_once($this->CLASS_PREFIX . "ContentModule.class.php"); - $classname = $this->CLASS_PREFIX . "ContentModule"; - - if ($is_connected == false) - { - $this->content_module[$module_id] = new $classname("", $module_type, $this->cms_type); - $this->content_module[$module_id]->setId($module_id); - } - else - { - $this->content_module[$module_id] = new $classname($module_id, $module_type, $this->cms_type); - } - - $this->content_module[$module_id]->setConnectionType($is_connected); - } - - /** - * get name of cms - * - * returns name of cms - * @access public - * @return string name - */ - public function getName() - { - return $this->name; - } - - /** - * get type of cms - * - * returns type of cms - * @access public - * @return string type - */ - public function getCMSType() - { - return $this->cms_type; - } - - /** - * get path of cms - * - * returns path of cms - * @access public - * @return string path - */ - public function getAbsolutePath() - { - return $this->ABSOLUTE_PATH_ELEARNINGMODULES; - } - - /** - * get target file of cms - * - * returns target file of cms - * @access public - * @return string target file - */ - public function getTargetFile() - { - return $this->target_file; - } - - /** - * get class prefix - * - * returns class prefix - * @access public - * @return string class prefix - */ - public function getClassPrefix() - { - return $this->CLASS_PREFIX; - } - - /** - * get authentification-setting - * - * returns true, if authentification is necessary - * @access public - * @return boolean authentification-setting - */ - public function isAuthNecessary() - { - return $this->auth_necessary; - } - - /** - * get active-setting - * - * returns true, if cms is active - * @access public - * @return boolean active-setting - function isActive($cms = "") - { - return $this->is_active; - } - */ - - /** - * get user prefix - * - * returns user prefix - * @access public - * @return string user prefix - */ - public function getUserPrefix() - { - return $this->USER_PREFIX; - } - - /** - * get logo-image - * - * returns logo-image - * @access public - * @return string logo-image - */ - public function getLogo() - { - return "logo_file . "\">"; - } - - /** - * get user modules - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - public function getUserContentModules() - { - return false; - } - - /** - * search modules - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - public function searchContentModules($key) - { - return false; - } - - /** - * dummy-method. can be overwritten by subclass. - */ - public function terminate() - { - } - - public function deleteConnectedModules($object_id){ - return ObjectConnections::DeleteAllConnections($object_id, $this->cms_type); - } -} diff --git a/lib/elearning/ConnectedCMS.php b/lib/elearning/ConnectedCMS.php new file mode 100644 index 0000000..3dd3bfb --- /dev/null +++ b/lib/elearning/ConnectedCMS.php @@ -0,0 +1,466 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ConnectedCMS +* @package ELearning-Interface +*/ +class ConnectedCMS +{ + public $title; + + public $is_active; + public $cms_type; + public $name = null; + public $ABSOLUTE_PATH_ELEARNINGMODULES = null; + public $ABSOLUTE_PATH_SOAP = null; + public $RELATIVE_PATH_DB_CLASSES = false; + public $CLASS_PREFIX = null; + public $auth_necessary = null; + public $USER_AUTO_CREATE = null; + public $USER_PREFIX = null; + public $target_file = null; + public $logo_file = null; + public $db_classes; + public $soap_data = null; + public $soap_client; + public $types = null; + public $roles = null; + + public $db; + public $db_class; + public $link; + public $user; + public $permissions; + public $content_module; + + /** + * constructor + * + * init class. don't call directly but by extending class ("new Ilias3ConnectedCMS($cms)" for example), except for basic administration + * @access + * @param string $cms system-type + */ + public function __construct($cms = "") + { + $this->cms_type = $cms; + $this->is_active = (bool) Config::get()->getValue("ELEARNING_INTERFACE_{$cms}_ACTIVE"); + + if ($cms) { + $this->init($cms); + } + } + + /** + * init settings + * + * gets settings from config-array and initializes db + * @access private + * @param string $cms system-type + */ + public function init($cms) + { + global $ELEARNING_INTERFACE_MODULES; + + $this->name = $ELEARNING_INTERFACE_MODULES[$cms]['name'] ?? null; + $this->ABSOLUTE_PATH_ELEARNINGMODULES = $ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_ELEARNINGMODULES"]; + $this->ABSOLUTE_PATH_SOAP = $ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_SOAP"]; + if (isset($ELEARNING_INTERFACE_MODULES[$cms]["RELATIVE_PATH_DB_CLASSES"])) { + $this->RELATIVE_PATH_DB_CLASSES = $ELEARNING_INTERFACE_MODULES[$cms]["RELATIVE_PATH_DB_CLASSES"]; + $this->db_classes = $ELEARNING_INTERFACE_MODULES[$cms]["db_classes"]; + } else { + $this->RELATIVE_PATH_DB_CLASSES = false; + } + $this->CLASS_PREFIX = $ELEARNING_INTERFACE_MODULES[$cms]['CLASS_PREFIX']; + $this->auth_necessary = $ELEARNING_INTERFACE_MODULES[$cms]['auth_necessary']; + $this->USER_AUTO_CREATE = $ELEARNING_INTERFACE_MODULES[$cms]['USER_AUTO_CREATE'] ?? null; + $this->USER_PREFIX = $ELEARNING_INTERFACE_MODULES[$cms]['USER_PREFIX'] ?? null; + $this->target_file = $ELEARNING_INTERFACE_MODULES[$cms]['target_file'] ?? null; + $this->logo_file = $ELEARNING_INTERFACE_MODULES[$cms]['logo_file'] ?? null; + $this->soap_data = $ELEARNING_INTERFACE_MODULES[$cms]['soap_data'] ?? null; + $this->types = $ELEARNING_INTERFACE_MODULES[$cms]['types'] ?? null; + $this->roles = $ELEARNING_INTERFACE_MODULES[$cms]['roles'] ?? null; + } + + /** + * init subclasses + * + * loads classes for user-functions + * @access public + */ + public function initSubclasses() + { + if ($this->auth_necessary) { + require_once $this->CLASS_PREFIX . "ConnectedUser.class.php"; + $classname = $this->CLASS_PREFIX . "ConnectedUser"; + $this->user = new $classname($this->cms_type); + + require_once $this->CLASS_PREFIX . "ConnectedPermissions.class.php"; + $classname = $this->CLASS_PREFIX . "ConnectedPermissions"; + $this->permissions = new $classname($this->cms_type); + } + require_once $this->CLASS_PREFIX . "ConnectedLink.class.php"; + $classname = $this->CLASS_PREFIX . "ConnectedLink"; + $this->link = new $classname($this->cms_type); + } + + /** + * get connection status + * + * checks settings + * @access public + * @param string $cms system-type + * @return array messages + */ + public function getConnectionStatus($cms = "") + { + $msg = [ + 'path' => [], + ]; + + if ($this->cms_type == "") { + $this->init($cms); + } + + // check connection to CMS + if (!$this->auth_necessary) { + $msg['auth'] = [ + 'info' => _('Eine Authentifizierung ist für dieses System nicht vorgesehen.') + ]; + } + + // check for SOAP-Interface + if (in_array($this->CLASS_PREFIX, ['Ilias3','Ilias4','Ilias5'])) { + $ch = curl_init($this->ABSOLUTE_PATH_ELEARNINGMODULES . 'login.php'); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_exec($ch); + + if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) { + $msg['path']['error'] = sprintf( + _('Die Verbindung zum System "%s" konnte nicht hergestellt werden. Der Pfad "%s" ist ungültig.'), + $this->name, + $this->ABSOLUTE_PATH_ELEARNINGMODULES + ); + + } else { + $msg['path']['info'] = sprintf( + _('Die %s-Installation wurde gefunden.'), + $this->name + ); + } + + $msg['soap'] = []; + if (!Config::get()->SOAP_ENABLE) { + $msg['soap']['error'] = _('Das Stud.IP-Modul für die SOAP-Schnittstelle ist nicht aktiviert. Ändern Sie den entsprechenden Eintrag in der Konfigurationsdatei "local.inc".'); + } elseif (!is_array($this->soap_data)) { + $msg['soap']['error'] = _('Die SOAP-Verbindungsdaten sind für dieses System nicht gesetzt. Ergänzen Sie die Einstellungen für dieses Systems um den Eintrag "soap_data" in der Konfigurationsdatei "local.inc".'); + } else { + $this->soap_client = new StudipSoapClient($this->ABSOLUTE_PATH_SOAP); + $msg['soap']['info'] = _('Das SOAP-Modul ist aktiv.'); + } + } else { + $file = fopen($this->ABSOLUTE_PATH_ELEARNINGMODULES, 'r'); + if ($file === false) { + $msg['path']['error'] = sprintf( + _('Die Verbindung zum System "%s" konnte nicht hergestellt werden. Der Pfad "%s" ist ungültig.'), + $this->name, + $this->ABSOLUTE_PATH_ELEARNINGMODULES + ); + } else { + fclose($file); + $msg['path']['info'] = sprintf( + _("Die %s-Installation wurde gefunden."), + $this->name + ); + + // check if target-file exists + $msg['auth'] = []; + + $file = fopen($this->ABSOLUTE_PATH_ELEARNINGMODULES . $this->target_file, 'r'); + if ($file === false) { + $msg['auth']['error'] = sprintf( + _('Die Zieldatei "%s" liegt nicht im Hauptverzeichnis der %s-Installation.'), + $this->target_file, + $this->name + ); + } else { + fclose($file); + $msg['auth']['info'] = _('Die Zieldatei ist vorhanden.'); + } + } + } + + $el_path = $GLOBALS['STUDIP_BASE_PATH'] . '/lib/elearning'; + // check if needed classes exist + $files = [ + 'class_link' => "{$el_path}/{$this->CLASS_PREFIX}ConnectedLink.class.php", + 'class_content' => "{$el_path}/{$this->CLASS_PREFIX}ContentModule.class.php", + 'class_cms' => "{$el_path}/{$this->CLASS_PREFIX}ConnectedCMS.class.php", + ]; + + if ($this->auth_necessary) { + $files['class_user'] = "{$el_path}/{$this->CLASS_PREFIX}ConnectedUser.class.php"; + $files['class_perm'] = "{$el_path}/{$this->CLASS_PREFIX}ConnectedPermissions.class.php"; + } + + $errors = 0; + foreach ($files as $index => $file) { + if (!file_exists($file)) { + $msg[$index] = [ + 'error' => sprintf(_('Die Datei "%s" existiert nicht.'), $file), + ]; + $errors += 1; + } + } + + $msg['classes'] = []; + if ($errors === 0) { + require_once $files['class_cms']; + $msg['classes']['info'] = sprintf( + _('Die Klassen der Schnittstelle zum System "%s" wurden geladen.'), + $this->name + ); + } else { + $msg['classes']['error'] = sprintf( + _('Die Klassen der Schnittstelle zum System "%s" wurden nicht geladen.'), + $this->name + ); + } + + return $msg; + } + + /** + * get preferences + * + * shows additional settings. can be overwritten by subclass. + * @access public + */ + public function getPreferences() + { + if ($this->types != "") + { + echo "" . _("Angebundene Lernmodul-Typen: ") . ""; + echo "
\n"; + foreach($this->types as $key => $type) + echo Icon::create($type["icon"], Icon::ROLE_INACTIVE)->asImg() . $type["name"] . " ($key)
\n"; + echo "
\n"; + } + + if ($this->db_classes != "") + { + echo "" . _("Verwendete DB-Zugriffs-Klassen: ") . ""; + echo "
\n"; + foreach($this->db_classes as $key => $type) { + echo $type["file"] . " ($key)
\n"; + } + echo "
\n"; + } + } + + /** + * create new instance of subclass content-module with given values + * + * creates new instance of subclass content-module with given values + * @access public + * @param array $data module-data + * @param boolean $is_connected is module connected to seminar? + */ + public function setContentModule($data, $is_connected = false) + { + global $current_module; + $current_module = $data["ref_id"]; + + require_once($this->CLASS_PREFIX . "ContentModule.class.php"); + $classname = $this->CLASS_PREFIX . "ContentModule"; + + $this->content_module[$current_module] = new $classname("", $data["type"], $this->cms_type); + $this->content_module[$current_module]->setId($data["ref_id"]); + $this->content_module[$current_module]->setTitle($data["title"]); + $this->content_module[$current_module]->setDescription($data["description"]); + $this->content_module[$current_module]->setConnectionType($is_connected); + } + + /** + * create new instance of subclass content-module + * + * creates new instance of subclass content-module + * @access public + * @param string $module_id module-id + * @param string $module_type module-type + * @param boolean $is_connected is module connected to seminar? + */ + public function newContentModule($module_id, $module_type, $is_connected = false) + { + global $current_module; + $current_module = $module_id; + + require_once($this->CLASS_PREFIX . "ContentModule.class.php"); + $classname = $this->CLASS_PREFIX . "ContentModule"; + + if ($is_connected == false) + { + $this->content_module[$module_id] = new $classname("", $module_type, $this->cms_type); + $this->content_module[$module_id]->setId($module_id); + } + else + { + $this->content_module[$module_id] = new $classname($module_id, $module_type, $this->cms_type); + } + + $this->content_module[$module_id]->setConnectionType($is_connected); + } + + /** + * get name of cms + * + * returns name of cms + * @access public + * @return string name + */ + public function getName() + { + return $this->name; + } + + /** + * get type of cms + * + * returns type of cms + * @access public + * @return string type + */ + public function getCMSType() + { + return $this->cms_type; + } + + /** + * get path of cms + * + * returns path of cms + * @access public + * @return string path + */ + public function getAbsolutePath() + { + return $this->ABSOLUTE_PATH_ELEARNINGMODULES; + } + + /** + * get target file of cms + * + * returns target file of cms + * @access public + * @return string target file + */ + public function getTargetFile() + { + return $this->target_file; + } + + /** + * get class prefix + * + * returns class prefix + * @access public + * @return string class prefix + */ + public function getClassPrefix() + { + return $this->CLASS_PREFIX; + } + + /** + * get authentification-setting + * + * returns true, if authentification is necessary + * @access public + * @return boolean authentification-setting + */ + public function isAuthNecessary() + { + return $this->auth_necessary; + } + + /** + * get active-setting + * + * returns true, if cms is active + * @access public + * @return boolean active-setting + function isActive($cms = "") + { + return $this->is_active; + } + */ + + /** + * get user prefix + * + * returns user prefix + * @access public + * @return string user prefix + */ + public function getUserPrefix() + { + return $this->USER_PREFIX; + } + + /** + * get logo-image + * + * returns logo-image + * @access public + * @return string logo-image + */ + public function getLogo() + { + return "logo_file . "\">"; + } + + /** + * get user modules + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + public function getUserContentModules() + { + return false; + } + + /** + * search modules + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + public function searchContentModules($key) + { + return false; + } + + /** + * dummy-method. can be overwritten by subclass. + */ + public function terminate() + { + } + + public function deleteConnectedModules($object_id){ + return ObjectConnections::DeleteAllConnections($object_id, $this->cms_type); + } +} diff --git a/lib/elearning/ConnectedLink.class.php b/lib/elearning/ConnectedLink.class.php deleted file mode 100644 index 6fa1de5..0000000 --- a/lib/elearning/ConnectedLink.class.php +++ /dev/null @@ -1,129 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ConnectedLink -* @package ELearning-Interface -*/ - -use Studip\Button, Studip\LinkButton; - -class ConnectedLink -{ - var $cms_type; - var $cms_link; - /** - * constructor - * - * init class. don't call directly, class is loaded by ConnectedCMS. - * @access public - * @param string $cms system-type - */ - function __construct($cms) - { - global $ELEARNING_INTERFACE_MODULES; - - $this->cms_type = $cms; - $this->cms_link = $ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_ELEARNINGMODULES"] . ($ELEARNING_INTERFACE_MODULES[$cms]["target_file"] ?? null); - } - - /** - * get link to create new account - * - * returns link to create new user-account - * @access public - * @return string html-code - */ - function getNewAccountLink() - { - global $connected_cms, $cms_select, $current_module; - - $output = "\n"; - $output .= CSRFProtection::tokenTag(); - $output .= "\n"; - $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; - $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "cms_type) . "\">\n"; - $output .= "\n"; - $output .= Button::createAccept(_('Starten'), 'start'); - $output .= ""; - return $output; - } - - /** - * get module-links for user - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - function getUserModuleLinks() - { - return false; - } - - /** - * get module-links for admin - * - * returns links to remove or add module to object - * @access public - * @return string html-code - */ - function getAdminModuleLinks() - { - global $connected_cms, $view, $search_key, $cms_select, $current_module; - - $output = "
\n"; - $output .= CSRFProtection::tokenTag(); - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; - $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; - $output .= "cms_type) . "\">\n"; - - if ($connected_cms[$this->cms_type]->content_module[$current_module]->isConnected()) - $output .= " " . Button::create(_('Entfernen'), 'remove'); - else - $output .= " " . Button::create(_('Hinzufügen'), 'add'); - $output .= ""; - - return $output; - } - - /** - * get new module link - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - function getNewModuleLink() - { - return false; - } - - /** - * get start page link - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - function getStartpageLink() - { - return false; - } -} -?> diff --git a/lib/elearning/ConnectedLink.php b/lib/elearning/ConnectedLink.php new file mode 100644 index 0000000..6fa1de5 --- /dev/null +++ b/lib/elearning/ConnectedLink.php @@ -0,0 +1,129 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ConnectedLink +* @package ELearning-Interface +*/ + +use Studip\Button, Studip\LinkButton; + +class ConnectedLink +{ + var $cms_type; + var $cms_link; + /** + * constructor + * + * init class. don't call directly, class is loaded by ConnectedCMS. + * @access public + * @param string $cms system-type + */ + function __construct($cms) + { + global $ELEARNING_INTERFACE_MODULES; + + $this->cms_type = $cms; + $this->cms_link = $ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_ELEARNINGMODULES"] . ($ELEARNING_INTERFACE_MODULES[$cms]["target_file"] ?? null); + } + + /** + * get link to create new account + * + * returns link to create new user-account + * @access public + * @return string html-code + */ + function getNewAccountLink() + { + global $connected_cms, $cms_select, $current_module; + + $output = "
\n"; + $output .= CSRFProtection::tokenTag(); + $output .= "\n"; + $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; + $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; + $output .= "\n"; + $output .= "\n"; + $output .= "cms_type) . "\">\n"; + $output .= "\n"; + $output .= Button::createAccept(_('Starten'), 'start'); + $output .= ""; + return $output; + } + + /** + * get module-links for user + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + function getUserModuleLinks() + { + return false; + } + + /** + * get module-links for admin + * + * returns links to remove or add module to object + * @access public + * @return string html-code + */ + function getAdminModuleLinks() + { + global $connected_cms, $view, $search_key, $cms_select, $current_module; + + $output = "
\n"; + $output .= CSRFProtection::tokenTag(); + $output .= "\n"; + $output .= "\n"; + $output .= "\n"; + $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; + $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; + $output .= "cms_type) . "\">\n"; + + if ($connected_cms[$this->cms_type]->content_module[$current_module]->isConnected()) + $output .= " " . Button::create(_('Entfernen'), 'remove'); + else + $output .= " " . Button::create(_('Hinzufügen'), 'add'); + $output .= ""; + + return $output; + } + + /** + * get new module link + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + function getNewModuleLink() + { + return false; + } + + /** + * get start page link + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + function getStartpageLink() + { + return false; + } +} +?> diff --git a/lib/elearning/ConnectedPermissions.class.php b/lib/elearning/ConnectedPermissions.class.php deleted file mode 100644 index a2216b7..0000000 --- a/lib/elearning/ConnectedPermissions.class.php +++ /dev/null @@ -1,57 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ConnectedPermission -* @package ELearning-Interface -*/ -class ConnectedPermissions -{ - var $cms_type; - - var $db_class; - /** - * constructor - * - * init class. don't call directly, class is loaded by ConnectedCMS. - * @access public - * @param string $cms system-type - */ - function __construct($cms) - { - global $connected_cms, $ELEARNING_INTERFACE_MODULES; - - $this->cms_type = $cms; - if ($ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] != false) - { - require_once('lib/elearning/' . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] - . "/" . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["permissions"]["file"] ); - $classname = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["permissions"]["classname"]; - $this->db_class = new $classname(); - } - - } - - /** - * get module-permissions - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @param string $module_id module-id - * @return boolean returns false - */ - function getContentModulePerms($module_id) - { - return false; - } -} -?> \ No newline at end of file diff --git a/lib/elearning/ConnectedPermissions.php b/lib/elearning/ConnectedPermissions.php new file mode 100644 index 0000000..a2216b7 --- /dev/null +++ b/lib/elearning/ConnectedPermissions.php @@ -0,0 +1,57 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ConnectedPermission +* @package ELearning-Interface +*/ +class ConnectedPermissions +{ + var $cms_type; + + var $db_class; + /** + * constructor + * + * init class. don't call directly, class is loaded by ConnectedCMS. + * @access public + * @param string $cms system-type + */ + function __construct($cms) + { + global $connected_cms, $ELEARNING_INTERFACE_MODULES; + + $this->cms_type = $cms; + if ($ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] != false) + { + require_once('lib/elearning/' . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] + . "/" . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["permissions"]["file"] ); + $classname = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["permissions"]["classname"]; + $this->db_class = new $classname(); + } + + } + + /** + * get module-permissions + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @param string $module_id module-id + * @return boolean returns false + */ + function getContentModulePerms($module_id) + { + return false; + } +} +?> \ No newline at end of file diff --git a/lib/elearning/ConnectedUser.class.php b/lib/elearning/ConnectedUser.class.php deleted file mode 100644 index 3c74a14..0000000 --- a/lib/elearning/ConnectedUser.class.php +++ /dev/null @@ -1,512 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ConnectedUser -* @package ELearning-Interface -*/ -class ConnectedUser -{ - var $cms_type; - var $id; - var $studip_id; - var $studip_login; - var $studip_password; - var $login; - var $external_password; - var $category; - var $gender; - var $title_front; - var $title_rear; - var $title; - var $firstname; - var $lastname; - var $institution; - var $department; - var $street; - var $city; - var $zipcode; - var $country; - var $phone_home; - var $fax; - var $matriculation; - var $email; - var $type; - var $is_connected; - - var $db_class; - /** - * constructor - * - * init class. don't call directly, class is loaded by ConnectedCMS. - * @access public - * @param string $cms system-type - */ - function __construct($cms, $user_id = false) - { - global $auth, $ELEARNING_INTERFACE_MODULES; - - $this->studip_id = $user_id ? $user_id : $auth->auth["uid"]; - $this->cms_type = $cms; - - if ($ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] != false) - { - require_once("lib/elearning/" . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] . "/" . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["user"]["file"] ); - $classname = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["user"]["classname"]; - $this->db_class = new $classname(); - } - $this->readData(); - $this->getStudipUserData(); - } - - /** - * get data - * - * gets data from database - * @access public - * @return boolean returns false, if no data was found - */ - function readData() - { - $query = "SELECT external_user_id, external_user_name, external_user_password, external_user_category, external_user_type - FROM auth_extern - WHERE studip_user_id = ? AND external_user_system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->studip_id, $this->cms_type]); - $data = $statement->fetch(PDO::FETCH_ASSOC); - - if (!$data) { - $this->id = ''; - $this->is_connected = false; - return false; - } - - $this->id = $data['external_user_id']; - $this->login = $data['external_user_name']; - $this->external_password = $data['external_user_password']; - $this->category = $data['external_user_category']; - $this->type = $data['external_user_type']; - $this->is_connected = true; - - return true; - } - - /** - * get stud.ip-user-data - * - * gets stud.ip-user-data from database - * @access public - * @return boolean returns false, if no data was found - */ - function getStudipUserData() - { - global $connected_cms; - - $query = "SELECT username, password, title_front, title_rear, Vorname, - Nachname, Email, privatnr, privadr, geschlecht - FROM auth_user_md5 - LEFT JOIN user_info USING (user_id) - WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->studip_id]); - $data = $statement->fetch(PDO::FETCH_ASSOC); - - if (!$data) { - return false; - } - - $this->studip_login = $data['username']; - if ($this->is_connected == false) { - $this->login = $connected_cms[$this->cms_type]->getUserPrefix() . $this->studip_login; - } - - $this->studip_password = $data['password']; - $this->title_front = $data['title_front']; - $this->title_rear = $data['title_rear']; - $this->firstname = $data['Vorname']; - $this->lastname = $data['Nachname']; - $this->email = $data['Email']; - $this->phone_home = $data['privatnr']; - $this->street = $data['privadr']; - $this->gender = ($data['geschlecht'] == 2 ? 'f' : 'm'); - - if ($this->title_front != '') { - $this->title = $this->title_front; - } - if ($this->title_front != '' && $this->title_rear != '') { - $this->title .= ' '; - } - if ($this->title_rear != '') { - $this->title .= $this->title_rear; - } - return true; - } - - /** - * create new user-account - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - function newUser() - { - return false; - } - - /** - * update user-account - * - * dummy-method. must be overwritten by subclass. - */ - public function updateUser() - { - } - - /** - * delete user-account - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - function deleteUser() - { - return false; - } - - /** - * get login-data of user-account - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - function getLoginData($username) - { - return false; - } - - /** - * get id - * - * returns id - * @access public - * @return string id - */ - function getId() - { - return $this->id; - } - - /** - * get stud.ip user-id - * - * returns id - * @access public - * @return string stud.ip user-id - */ - function getStudipId() - { - return $this->studip_id; - } - - /** - * get username - * - * returns username - * @access public - * @return string username - */ - function getUsername() - { - return $this->login; - } - - /** - * set username - * - * sets username - * @access public - * @param string $user_login username - */ - function setUsername($user_login) - { - $this->login = $user_login; - } - - /** - * get password - * - * returns password - * @access public - * @return string password - */ - function getPassword() - { - return $this->external_password; - } - - /** - * set password - * - * sets password - * @access public - * @param string $user_password password - */ - function setPassword($user_password) - { - $this->external_password = $user_password; - } - - /** - * get user category - * - * returns id - * @access public - * @return string id - */ - function getCategory() - { - return $this->category; - } - - /** - * set user category - * - * sets user category - * @access public - * @param string $user_category category - */ - function setCategory($user_category) - { - $this->category = $user_category; - } - - /** - * get crypted password - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @return boolean returns false - */ - function getCryptedPassword($password) - { - return false; - } - - /** - * verify login data - * - * returns true, if login-data is valid - * @access public - * @param string $username username - * @param string $password password - * @return boolean login-validation - */ - function verifyLogin($username, $password) - { - $this->getLoginData($username); - if (($username == "") OR ($password == "")) - return false; - if ( ($this->login == $username) AND ($this->external_password == $this->getCryptedPassword($password) ) ) - return true; - return false; - } - - /** - * get gender - * - * returns gender-setting - * @access public - * @return string gender-setting - */ - function getGender() - { - return $this->gender; - } - - /** - * set gender - * - * sets gender - * @access public - * @param string $user_gender gender-setting - */ - function setGender($user_gender) - { - $this->gender = $user_gender; - } - - /** - * get full name - * - * returns full name - * @access public - * @return string name - */ - function getName() - { - if ($this->title != "") - return $this->title . ' ' . $this->firstname . ' ' . $this->lastname; - else - return $this->firstname . ' ' . $this->lastname; - } - - /** - * get firstname - * - * returns firstname - * @access public - * @return string firstname - */ - function getFirstname() - { - return $this->firstname; - } - - /** - * set firstname - * - * sets firstname - * @access public - * @param string $user_firstname firstname - */ - function setFirstname($user_firstname) - { - $this->firstname = $user_firstname; - } - - /** - * get lastname - * - * returns lastname - * @access public - * @return string lastname - */ - function getLastname() - { - return $this->lastname; - } - - /** - * set lastname - * - * sets lastname - * @access public - * @param string $user_lastname lastname - */ - function setLastname($user_lastname) - { - $this->lastname = $user_lastname; - } - - /** - * get email-adress - * - * returns email-adress - * @access public - * @return string email-adress - */ - function getEmail() - { - return $this->email; - } - - /** - * set email-adress - * - * sets email-adress - * @access public - * @param string $user_email email-adress - */ - function setEmail($user_email) - { - $this->email = $user_email; - } - - /** - * get user-type - * - * returns user-type - * @access public - * @return string user-type - */ - function getUserType() - { - return $this->type; - } - - /** - * set user-type - * - * sets user-type - * @access public - * @param string $user_type user-type - */ - function setUserType($user_type) - { - $this->type = $user_type; - } - - /** - * save connection for user-account - * - * saves user-connection to database and sets type for actual user - * @param string $user_type user-type - */ - public function setConnection($user_type) - { - $this->setUserType($user_type); - - $query = "INSERT INTO auth_extern (studip_user_id, external_user_id, external_user_name, - external_user_password, external_user_category, - external_user_system_type, external_user_type) - VALUES (?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY - UPDATE external_user_name = VALUES(external_user_name), - external_user_password = VALUES(external_user_password), - external_user_category = VALUES(external_user_category), - external_user_id = VALUES(external_user_id), - external_user_type = VALUES(external_user_type)"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - (string)$this->studip_id, - (string)$this->id, - (string)$this->login, - (string)$this->external_password, - (string)$this->category, - (string)$this->cms_type, - (int)$this->type, - ]); - - $this->is_connected = true; - $this->readData(); - } - - /** - * get connection-status - * - * returns true, if there is a connected user - * @access public - * @return boolean connection-status - */ - function isConnected() - { - return $this->is_connected; - } -} -?> diff --git a/lib/elearning/ConnectedUser.php b/lib/elearning/ConnectedUser.php new file mode 100644 index 0000000..3c74a14 --- /dev/null +++ b/lib/elearning/ConnectedUser.php @@ -0,0 +1,512 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ConnectedUser +* @package ELearning-Interface +*/ +class ConnectedUser +{ + var $cms_type; + var $id; + var $studip_id; + var $studip_login; + var $studip_password; + var $login; + var $external_password; + var $category; + var $gender; + var $title_front; + var $title_rear; + var $title; + var $firstname; + var $lastname; + var $institution; + var $department; + var $street; + var $city; + var $zipcode; + var $country; + var $phone_home; + var $fax; + var $matriculation; + var $email; + var $type; + var $is_connected; + + var $db_class; + /** + * constructor + * + * init class. don't call directly, class is loaded by ConnectedCMS. + * @access public + * @param string $cms system-type + */ + function __construct($cms, $user_id = false) + { + global $auth, $ELEARNING_INTERFACE_MODULES; + + $this->studip_id = $user_id ? $user_id : $auth->auth["uid"]; + $this->cms_type = $cms; + + if ($ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] != false) + { + require_once("lib/elearning/" . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["RELATIVE_PATH_DB_CLASSES"] . "/" . $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["user"]["file"] ); + $classname = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["db_classes"]["user"]["classname"]; + $this->db_class = new $classname(); + } + $this->readData(); + $this->getStudipUserData(); + } + + /** + * get data + * + * gets data from database + * @access public + * @return boolean returns false, if no data was found + */ + function readData() + { + $query = "SELECT external_user_id, external_user_name, external_user_password, external_user_category, external_user_type + FROM auth_extern + WHERE studip_user_id = ? AND external_user_system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->studip_id, $this->cms_type]); + $data = $statement->fetch(PDO::FETCH_ASSOC); + + if (!$data) { + $this->id = ''; + $this->is_connected = false; + return false; + } + + $this->id = $data['external_user_id']; + $this->login = $data['external_user_name']; + $this->external_password = $data['external_user_password']; + $this->category = $data['external_user_category']; + $this->type = $data['external_user_type']; + $this->is_connected = true; + + return true; + } + + /** + * get stud.ip-user-data + * + * gets stud.ip-user-data from database + * @access public + * @return boolean returns false, if no data was found + */ + function getStudipUserData() + { + global $connected_cms; + + $query = "SELECT username, password, title_front, title_rear, Vorname, + Nachname, Email, privatnr, privadr, geschlecht + FROM auth_user_md5 + LEFT JOIN user_info USING (user_id) + WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->studip_id]); + $data = $statement->fetch(PDO::FETCH_ASSOC); + + if (!$data) { + return false; + } + + $this->studip_login = $data['username']; + if ($this->is_connected == false) { + $this->login = $connected_cms[$this->cms_type]->getUserPrefix() . $this->studip_login; + } + + $this->studip_password = $data['password']; + $this->title_front = $data['title_front']; + $this->title_rear = $data['title_rear']; + $this->firstname = $data['Vorname']; + $this->lastname = $data['Nachname']; + $this->email = $data['Email']; + $this->phone_home = $data['privatnr']; + $this->street = $data['privadr']; + $this->gender = ($data['geschlecht'] == 2 ? 'f' : 'm'); + + if ($this->title_front != '') { + $this->title = $this->title_front; + } + if ($this->title_front != '' && $this->title_rear != '') { + $this->title .= ' '; + } + if ($this->title_rear != '') { + $this->title .= $this->title_rear; + } + return true; + } + + /** + * create new user-account + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + function newUser() + { + return false; + } + + /** + * update user-account + * + * dummy-method. must be overwritten by subclass. + */ + public function updateUser() + { + } + + /** + * delete user-account + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + function deleteUser() + { + return false; + } + + /** + * get login-data of user-account + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + function getLoginData($username) + { + return false; + } + + /** + * get id + * + * returns id + * @access public + * @return string id + */ + function getId() + { + return $this->id; + } + + /** + * get stud.ip user-id + * + * returns id + * @access public + * @return string stud.ip user-id + */ + function getStudipId() + { + return $this->studip_id; + } + + /** + * get username + * + * returns username + * @access public + * @return string username + */ + function getUsername() + { + return $this->login; + } + + /** + * set username + * + * sets username + * @access public + * @param string $user_login username + */ + function setUsername($user_login) + { + $this->login = $user_login; + } + + /** + * get password + * + * returns password + * @access public + * @return string password + */ + function getPassword() + { + return $this->external_password; + } + + /** + * set password + * + * sets password + * @access public + * @param string $user_password password + */ + function setPassword($user_password) + { + $this->external_password = $user_password; + } + + /** + * get user category + * + * returns id + * @access public + * @return string id + */ + function getCategory() + { + return $this->category; + } + + /** + * set user category + * + * sets user category + * @access public + * @param string $user_category category + */ + function setCategory($user_category) + { + $this->category = $user_category; + } + + /** + * get crypted password + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @return boolean returns false + */ + function getCryptedPassword($password) + { + return false; + } + + /** + * verify login data + * + * returns true, if login-data is valid + * @access public + * @param string $username username + * @param string $password password + * @return boolean login-validation + */ + function verifyLogin($username, $password) + { + $this->getLoginData($username); + if (($username == "") OR ($password == "")) + return false; + if ( ($this->login == $username) AND ($this->external_password == $this->getCryptedPassword($password) ) ) + return true; + return false; + } + + /** + * get gender + * + * returns gender-setting + * @access public + * @return string gender-setting + */ + function getGender() + { + return $this->gender; + } + + /** + * set gender + * + * sets gender + * @access public + * @param string $user_gender gender-setting + */ + function setGender($user_gender) + { + $this->gender = $user_gender; + } + + /** + * get full name + * + * returns full name + * @access public + * @return string name + */ + function getName() + { + if ($this->title != "") + return $this->title . ' ' . $this->firstname . ' ' . $this->lastname; + else + return $this->firstname . ' ' . $this->lastname; + } + + /** + * get firstname + * + * returns firstname + * @access public + * @return string firstname + */ + function getFirstname() + { + return $this->firstname; + } + + /** + * set firstname + * + * sets firstname + * @access public + * @param string $user_firstname firstname + */ + function setFirstname($user_firstname) + { + $this->firstname = $user_firstname; + } + + /** + * get lastname + * + * returns lastname + * @access public + * @return string lastname + */ + function getLastname() + { + return $this->lastname; + } + + /** + * set lastname + * + * sets lastname + * @access public + * @param string $user_lastname lastname + */ + function setLastname($user_lastname) + { + $this->lastname = $user_lastname; + } + + /** + * get email-adress + * + * returns email-adress + * @access public + * @return string email-adress + */ + function getEmail() + { + return $this->email; + } + + /** + * set email-adress + * + * sets email-adress + * @access public + * @param string $user_email email-adress + */ + function setEmail($user_email) + { + $this->email = $user_email; + } + + /** + * get user-type + * + * returns user-type + * @access public + * @return string user-type + */ + function getUserType() + { + return $this->type; + } + + /** + * set user-type + * + * sets user-type + * @access public + * @param string $user_type user-type + */ + function setUserType($user_type) + { + $this->type = $user_type; + } + + /** + * save connection for user-account + * + * saves user-connection to database and sets type for actual user + * @param string $user_type user-type + */ + public function setConnection($user_type) + { + $this->setUserType($user_type); + + $query = "INSERT INTO auth_extern (studip_user_id, external_user_id, external_user_name, + external_user_password, external_user_category, + external_user_system_type, external_user_type) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY + UPDATE external_user_name = VALUES(external_user_name), + external_user_password = VALUES(external_user_password), + external_user_category = VALUES(external_user_category), + external_user_id = VALUES(external_user_id), + external_user_type = VALUES(external_user_type)"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + (string)$this->studip_id, + (string)$this->id, + (string)$this->login, + (string)$this->external_password, + (string)$this->category, + (string)$this->cms_type, + (int)$this->type, + ]); + + $this->is_connected = true; + $this->readData(); + } + + /** + * get connection-status + * + * returns true, if there is a connected user + * @access public + * @return boolean connection-status + */ + function isConnected() + { + return $this->is_connected; + } +} +?> diff --git a/lib/elearning/ContentModule.class.php b/lib/elearning/ContentModule.class.php deleted file mode 100644 index 1b2921b..0000000 --- a/lib/elearning/ContentModule.class.php +++ /dev/null @@ -1,388 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ContentModule -* @package ELearning-Interface -*/ -abstract class ContentModule -{ - /** - * Fetches data from conencted cms. - */ - abstract function readData(); - - var $id; - var $title; - var $module_type; - var $module_type_name; - var $icon_file; - var $cms_type; - var $cms_name; - var $description; - var $authors; - var $is_connected; - var $is_dummy; - var $allowed_operations; - - var $db_class; - var $view; - /** - * constructor - * - * init class. don't call directly, class is loaded by ConnectedCMS. - * @access public - * @param string $module_id module-id - * @param string $module_type module-type - * @param string $cms_type system-type - */ - function __construct($module_id, $module_type, $cms_type) - { - global $connected_cms; - - $this->is_dummy = false; - $this->setCMSType($cms_type); - $this->setModuleType($module_type); - if ($module_id != "") - { - $this->setId($module_id); - - $this->readData(); - } - $this->view = new ContentModuleView($this->cms_type); - -/**/ } - -/* // Dummy-method. Must be overwritten by subclass. - function readData() - { - return false; - } -*/ - - /** - * set id - * - * sets id - * @access public - * @param string $module_id id - */ - function setId($module_id) - { - $this->id = $module_id; - } - - /** - * get id - * - * returns id - * @access public - * @return string id - */ - function getId() - { - return $this->id; - } - - /** - * set cms-type - * - * sets cms-type - * @access public - * @param string $module_cms_type cms-type - */ - function setCMSType($module_cms_type) - { - global $ELEARNING_INTERFACE_MODULES; - $this->cms_type = $module_cms_type; - $this->cms_name = $ELEARNING_INTERFACE_MODULES[$module_cms_type]["name"]; - } - - /** - * get cms-type - * - * returns cms-type - * @access public - * @return string cms-type - */ - function getCMSType() - { - return $this->cms_type; - } - - /** - * get cms name - * - * returns cms name - * @access public - * @return string cms name - */ - function getCMSName() - { - return $this->cms_name; - } - - /** - * set module-type - * - * sets module-type - * @access public - * @param string $module_type module-type - */ - function setModuleType($module_type) - { - global $ELEARNING_INTERFACE_MODULES; - $this->module_type = $module_type; - $this->module_type_name = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["types"][$module_type]["name"]; - $this->icon_file = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["types"][$module_type]["icon"]; - } - - /** - * get module-type - * - * returns module-type - * @access public - * @return string module-type - */ - function getModuleType() - { - return $this->module_type; - } - - /** - * get module-type name - * - * returns module-type name - * @access public - * @return string module-type name - */ - function getModuleTypeName() - { - return $this->module_type_name; - } - - /** - * set title - * - * sets title - * @access public - * @param string $module_title title - */ - function setTitle($module_title) - { - $this->title = $module_title; - } - - /** - * get title - * - * returns title - * @access public - * @return string title - */ - function getTitle() - { - return $this->title; - } - - /** - * set description - * - * sets description - * @access public - * @param string $module_description description - */ - function setDescription($module_description) - { - $this->description = $module_description; - } - - /** - * get description - * - * returns description - * @access public - * @return string description - */ - function getDescription() - { - return $this->description; - } - - /** - * set authors - * - * sets authors - * @access public - * @param array $module_authors authors - */ - function setAuthors($module_authors) - { - $this->authors = $module_authors; - } - - /** - * get authors - * - * returns authors - * @access public - * @return array authors - */ - function getAuthors() - { - return $this->authors; - } - - /** - * set connection - * - * sets connection with seminar - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function setConnection($seminar_id) - { - $this->is_connected = true; -// echo "$this->id, $this->module_type, $this->cms_type"; - return ObjectConnections::setConnection($seminar_id, $this->id, $this->module_type, $this->cms_type); - } - - /** - * unset connection - * - * unsets connection with seminar - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function unsetConnection($seminar_id) - { - $this->is_connected = false; - return ObjectConnections::unsetConnection($seminar_id, $this->id, $this->module_type, $this->cms_type); - } - - /** - * set connection-status - * - * sets connection-status - * @access public - * @param boolean $is_connected connection-status - */ - function setConnectionType($is_connected) - { - $this->is_connected = $is_connected; - } - - /** - * get connection-status - * - * returns true, if module is connected to seminar - * @access public - * @return boolean connection-status - */ - function isConnected() - { - return $this->is_connected; - } - - /** - * get reference string - * - * returns reference string for content-module - * @access public - * @return string reference string - */ - function getReferenceString() - { - return $this->cms_type."_".$this->module_type."_".$this->id; - } - - /** - * get icon-image - * - * returns icon-image - * @access public - * @return string icon-image - */ - function getIcon() - { - if (!$this->icon_file) { - $this->icon_file = 'learnmodule'; - } - if (mb_strpos('http', $this->icon_file) === 0) { - return "icon_file . "\">"; - } else { - return Icon::create($this->icon_file, 'inactive')->asImg(); - } - } - - /** - * get module-status - * - * returns true, if module is a dummy - * @access public - * @return boolean module-status - */ - function isDummy() - { - return $this->is_dummy; - } - - /** - * create module-dummy - * - * sets title and description of module to display error-message - * @access public - * @param string $error error-type - */ - function createDummyForErrormessage($error = "unknown") - { - global $connected_cms; - - switch($error) - { - case "no permission": - $this->setTitle(_("--- Keine Lese-Berechtigung! ---")); - $this->setDescription(sprintf(_("Sie haben im System \"%s\" keine Lese-Berechtigung für das Lernmodul, das dieser Veranstaltung / Einrichtung an dieser Stelle zugeordnet ist."), $this->getCMSName())); - break; - case "not found": - $this->setTitle(_("--- Dieses Content-Modul existiert nicht mehr im angebundenen System! ---")); - $this->setDescription(sprintf(_("Das Lernmodul, das dieser Veranstaltung / Einrichtung an dieser Stelle zugeordnet war, existiert nicht mehr. Dieser Fehler tritt auf, wenn das angebundene LCMS \"%s\" nicht erreichbar ist oder wenn das Lernmodul innerhalb des angebundenen Systems gelöscht wurde."), $this->getCMSName())); - break; - case "deleted": - $this->setTitle(_("--- Dieses Content-Modul wurde im angebundenen System gelöscht! ---")); - $this->setDescription(sprintf(_("Das Lernmodul, das dieser Veranstaltung / Einrichtung an dieser Stelle zugeordnet war, wurde gelöscht."), $this->getCMSName())); - break; - default: - $this->setTitle(_("--- Es ist ein unbekannter Fehler aufgetreten! ---")); - $this->setDescription(sprintf(_("Unbekannter Fehler beim Lernmodul mit der Referenz-ID \"%s\" im LCMS \"%s\""), $this->getId(), $this->getCMSName())); - } - - $this->is_dummy = true; - } - - /** - * ask for permission for given operation - * - * dummy-method. returns false. must be overwritten by subclass. - * @access public - * @param string $operation operation - * @return boolean returns false - */ - function isAllowed($operation) - { - return false; - } -} -?> diff --git a/lib/elearning/ContentModule.php b/lib/elearning/ContentModule.php new file mode 100644 index 0000000..1b2921b --- /dev/null +++ b/lib/elearning/ContentModule.php @@ -0,0 +1,388 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ContentModule +* @package ELearning-Interface +*/ +abstract class ContentModule +{ + /** + * Fetches data from conencted cms. + */ + abstract function readData(); + + var $id; + var $title; + var $module_type; + var $module_type_name; + var $icon_file; + var $cms_type; + var $cms_name; + var $description; + var $authors; + var $is_connected; + var $is_dummy; + var $allowed_operations; + + var $db_class; + var $view; + /** + * constructor + * + * init class. don't call directly, class is loaded by ConnectedCMS. + * @access public + * @param string $module_id module-id + * @param string $module_type module-type + * @param string $cms_type system-type + */ + function __construct($module_id, $module_type, $cms_type) + { + global $connected_cms; + + $this->is_dummy = false; + $this->setCMSType($cms_type); + $this->setModuleType($module_type); + if ($module_id != "") + { + $this->setId($module_id); + + $this->readData(); + } + $this->view = new ContentModuleView($this->cms_type); + +/**/ } + +/* // Dummy-method. Must be overwritten by subclass. + function readData() + { + return false; + } +*/ + + /** + * set id + * + * sets id + * @access public + * @param string $module_id id + */ + function setId($module_id) + { + $this->id = $module_id; + } + + /** + * get id + * + * returns id + * @access public + * @return string id + */ + function getId() + { + return $this->id; + } + + /** + * set cms-type + * + * sets cms-type + * @access public + * @param string $module_cms_type cms-type + */ + function setCMSType($module_cms_type) + { + global $ELEARNING_INTERFACE_MODULES; + $this->cms_type = $module_cms_type; + $this->cms_name = $ELEARNING_INTERFACE_MODULES[$module_cms_type]["name"]; + } + + /** + * get cms-type + * + * returns cms-type + * @access public + * @return string cms-type + */ + function getCMSType() + { + return $this->cms_type; + } + + /** + * get cms name + * + * returns cms name + * @access public + * @return string cms name + */ + function getCMSName() + { + return $this->cms_name; + } + + /** + * set module-type + * + * sets module-type + * @access public + * @param string $module_type module-type + */ + function setModuleType($module_type) + { + global $ELEARNING_INTERFACE_MODULES; + $this->module_type = $module_type; + $this->module_type_name = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["types"][$module_type]["name"]; + $this->icon_file = $ELEARNING_INTERFACE_MODULES[$this->cms_type]["types"][$module_type]["icon"]; + } + + /** + * get module-type + * + * returns module-type + * @access public + * @return string module-type + */ + function getModuleType() + { + return $this->module_type; + } + + /** + * get module-type name + * + * returns module-type name + * @access public + * @return string module-type name + */ + function getModuleTypeName() + { + return $this->module_type_name; + } + + /** + * set title + * + * sets title + * @access public + * @param string $module_title title + */ + function setTitle($module_title) + { + $this->title = $module_title; + } + + /** + * get title + * + * returns title + * @access public + * @return string title + */ + function getTitle() + { + return $this->title; + } + + /** + * set description + * + * sets description + * @access public + * @param string $module_description description + */ + function setDescription($module_description) + { + $this->description = $module_description; + } + + /** + * get description + * + * returns description + * @access public + * @return string description + */ + function getDescription() + { + return $this->description; + } + + /** + * set authors + * + * sets authors + * @access public + * @param array $module_authors authors + */ + function setAuthors($module_authors) + { + $this->authors = $module_authors; + } + + /** + * get authors + * + * returns authors + * @access public + * @return array authors + */ + function getAuthors() + { + return $this->authors; + } + + /** + * set connection + * + * sets connection with seminar + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function setConnection($seminar_id) + { + $this->is_connected = true; +// echo "$this->id, $this->module_type, $this->cms_type"; + return ObjectConnections::setConnection($seminar_id, $this->id, $this->module_type, $this->cms_type); + } + + /** + * unset connection + * + * unsets connection with seminar + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function unsetConnection($seminar_id) + { + $this->is_connected = false; + return ObjectConnections::unsetConnection($seminar_id, $this->id, $this->module_type, $this->cms_type); + } + + /** + * set connection-status + * + * sets connection-status + * @access public + * @param boolean $is_connected connection-status + */ + function setConnectionType($is_connected) + { + $this->is_connected = $is_connected; + } + + /** + * get connection-status + * + * returns true, if module is connected to seminar + * @access public + * @return boolean connection-status + */ + function isConnected() + { + return $this->is_connected; + } + + /** + * get reference string + * + * returns reference string for content-module + * @access public + * @return string reference string + */ + function getReferenceString() + { + return $this->cms_type."_".$this->module_type."_".$this->id; + } + + /** + * get icon-image + * + * returns icon-image + * @access public + * @return string icon-image + */ + function getIcon() + { + if (!$this->icon_file) { + $this->icon_file = 'learnmodule'; + } + if (mb_strpos('http', $this->icon_file) === 0) { + return "icon_file . "\">"; + } else { + return Icon::create($this->icon_file, 'inactive')->asImg(); + } + } + + /** + * get module-status + * + * returns true, if module is a dummy + * @access public + * @return boolean module-status + */ + function isDummy() + { + return $this->is_dummy; + } + + /** + * create module-dummy + * + * sets title and description of module to display error-message + * @access public + * @param string $error error-type + */ + function createDummyForErrormessage($error = "unknown") + { + global $connected_cms; + + switch($error) + { + case "no permission": + $this->setTitle(_("--- Keine Lese-Berechtigung! ---")); + $this->setDescription(sprintf(_("Sie haben im System \"%s\" keine Lese-Berechtigung für das Lernmodul, das dieser Veranstaltung / Einrichtung an dieser Stelle zugeordnet ist."), $this->getCMSName())); + break; + case "not found": + $this->setTitle(_("--- Dieses Content-Modul existiert nicht mehr im angebundenen System! ---")); + $this->setDescription(sprintf(_("Das Lernmodul, das dieser Veranstaltung / Einrichtung an dieser Stelle zugeordnet war, existiert nicht mehr. Dieser Fehler tritt auf, wenn das angebundene LCMS \"%s\" nicht erreichbar ist oder wenn das Lernmodul innerhalb des angebundenen Systems gelöscht wurde."), $this->getCMSName())); + break; + case "deleted": + $this->setTitle(_("--- Dieses Content-Modul wurde im angebundenen System gelöscht! ---")); + $this->setDescription(sprintf(_("Das Lernmodul, das dieser Veranstaltung / Einrichtung an dieser Stelle zugeordnet war, wurde gelöscht."), $this->getCMSName())); + break; + default: + $this->setTitle(_("--- Es ist ein unbekannter Fehler aufgetreten! ---")); + $this->setDescription(sprintf(_("Unbekannter Fehler beim Lernmodul mit der Referenz-ID \"%s\" im LCMS \"%s\""), $this->getId(), $this->getCMSName())); + } + + $this->is_dummy = true; + } + + /** + * ask for permission for given operation + * + * dummy-method. returns false. must be overwritten by subclass. + * @access public + * @param string $operation operation + * @return boolean returns false + */ + function isAllowed($operation) + { + return false; + } +} +?> diff --git a/lib/elearning/ContentModuleView.class.php b/lib/elearning/ContentModuleView.class.php deleted file mode 100644 index c351c1c..0000000 --- a/lib/elearning/ContentModuleView.class.php +++ /dev/null @@ -1,189 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ContentModuleView -* @package ELearning-Interface -*/ -class ContentModuleView -{ - var $view_mode; - var $change_date; - var $module_new; - var $cms_type; - /** - * constructor - * - * init class. don't call directly, class is loaded by ContentModule. - * @access public - * @param string $cms system-type - */ - function __construct($cms) - { - global $connected_cms; - - $this->change_date = 0; - $this->module_new = false; - $this->cms_type = $cms; - $this->setViewMode("closed"); - } - - /** - * show module-data - * - * show module-data in printhead/printcontent-style. user-mode - * @access public - */ - function show($mode = "") - { - global $connected_cms, $view, $search_key, $cms_select, $current_module, $anker_target; - - $content_module = $connected_cms[$this->cms_type]->content_module[$current_module]; - - if ( (! $content_module->isDummy()) AND ($connected_cms[$this->cms_type]->isAuthNecessary() == true) AND ($connected_cms[$this->cms_type]->user->isConnected() == true)) { - if (! $content_module->isAllowed(OPERATION_VISIBLE)) { - return false; - } - } - - if ($_SESSION['elearning_open_close'][$content_module->getReferenceString()] == true) - $this->setViewMode("open"); - $module_title = $content_module->getTitle(); -/*/ - if ($mode == "searchresult") { - $module_title = $module_title . " (ID " . $content_module->getId() . ", "; - if ($content_module->isAllowed(OPERATION_WRITE)) - $module_title = $module_title . " " . _("Schreibzugriff") . ")"; - else - $module_title = $module_title . " " . _("Lesezugriff") . ")"; - }/**/ - if ($this->isOpen() == true) - $module_link = URLHelper::getLink('?do_close='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); - else - $module_link = URLHelper::getLink('?do_open='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); - if (! $content_module->isDummy()) - $module_buttons = $connected_cms[$this->cms_type]->link->getUserModuleLinks(); - $template = $GLOBALS['template_factory']->open('elearning/_content_module.php'); - $template->set_attribute('module_anker_target', ($anker_target == $content_module->getReferenceString())); - $template->set_attribute('module_link', $module_link); - $template->set_attribute('module_buttons', $module_buttons); - $template->set_attribute('module_authors', $content_module->getAuthors()); - $template->set_attribute('module_title', $module_title); - $template->set_attribute('module_description', $content_module->getDescription()); - $template->set_attribute('module_is_new', $this->module_new); - $template->set_attribute('module_chdate', $this->change_date); - $template->set_attribute('module_reference', $content_module->getReferenceString()); - $template->set_attribute('module_source', $content_module->getCMSName() . " / " . $content_module->getModuleTypeName()); - $template->set_attribute('module_icon', $content_module->getIcon()); - $template->set_attribute('module_is_open', $this->isOpen()); - return $template->render(); - } - - /** - * show module-data to admin - * - * show module-data in printhead/printcontent-style. admin-mode - * @access public - */ - function showAdmin($mode = "") - { - global $connected_cms, $view, $search_key, $cms_select, $current_module, $anker_target; - - $content_module = $connected_cms[$this->cms_type]->content_module[$current_module]; - - if ( (! $content_module->isDummy()) AND ($connected_cms[$this->cms_type]->isAuthNecessary() == true) AND ($connected_cms[$this->cms_type]->user->isConnected() == true)) { - if (! $content_module->isAllowed(OPERATION_VISIBLE)) { - return false; - } - } - - if ($_SESSION['elearning_open_close'][$content_module->getReferenceString()] == true) - $this->setViewMode("open"); - - $module_title = $content_module->getTitle(); - $module_title = $module_title . " (ID " . $content_module->getId(); - if ($content_module->isAllowed(OPERATION_WRITE)) - $module_title = $module_title . ", " . _("Schreibzugriff"); - elseif ($content_module->isAllowed(OPERATION_READ)) - $module_title = $module_title . ", " . _("Lesezugriff"); - else - $module_title = $module_title . ", " . _("kein Lesezugriff"); - $module_title = $module_title . ")"; - - if ($this->isOpen() == true) - $module_link = URLHelper::getLink('?do_close='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); - else - $module_link = URLHelper::getLink('?do_open='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); - - $template = $GLOBALS['template_factory']->open('elearning/_content_module.php'); - $template->set_attribute('module_anker_target', ($anker_target == $content_module->getReferenceString())); - $template->set_attribute('module_link', $module_link); - if ($content_module->isAllowed(OPERATION_READ)) - $module_buttons = $connected_cms[$this->cms_type]->link->getAdminModuleLinks(); - $template->set_attribute('module_buttons', $module_buttons); - $template->set_attribute('module_authors', $content_module->getAuthors()); - $template->set_attribute('module_title', $module_title); - $template->set_attribute('module_description', $content_module->getDescription()); - $template->set_attribute('module_is_new', $this->module_new); - $template->set_attribute('module_chdate', $this->change_date); - $template->set_attribute('module_reference', $content_module->getReferenceString()); - $template->set_attribute('module_source', $content_module->getCMSName() . " / " . $content_module->getModuleTypeName()); - $template->set_attribute('module_icon', $content_module->getIcon()); - $template->set_attribute('module_is_open', $this->isOpen()); - return $template->render(); - } - - /** - * get open-status - * - * returns true, if module is opened - * @access public - * @return boolean open-status - */ - function isOpen() - { - if ($this->view_mode == "open") - return true; - else - return false; - } - - /** - * set view-mode - * - * sets view-mode - * @access public - * @param boolean $module_mode view-mode - */ - function setViewMode($module_mode) - { - $this->view_mode = $module_mode; - } - - /** - * set changedate - * - * sets changedate for view - * @access public - * @param string $module_chdate changedate - */ - function setChangeDate($module_chdate) - { - $this->change_date = $module_chdate; - - if (object_get_visit(Context::getId(), "elearning_interface") < $this->change_date) - $this->module_new = true; - else - $this->module_new = false; - } -} -?> diff --git a/lib/elearning/ContentModuleView.php b/lib/elearning/ContentModuleView.php new file mode 100644 index 0000000..c351c1c --- /dev/null +++ b/lib/elearning/ContentModuleView.php @@ -0,0 +1,189 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ContentModuleView +* @package ELearning-Interface +*/ +class ContentModuleView +{ + var $view_mode; + var $change_date; + var $module_new; + var $cms_type; + /** + * constructor + * + * init class. don't call directly, class is loaded by ContentModule. + * @access public + * @param string $cms system-type + */ + function __construct($cms) + { + global $connected_cms; + + $this->change_date = 0; + $this->module_new = false; + $this->cms_type = $cms; + $this->setViewMode("closed"); + } + + /** + * show module-data + * + * show module-data in printhead/printcontent-style. user-mode + * @access public + */ + function show($mode = "") + { + global $connected_cms, $view, $search_key, $cms_select, $current_module, $anker_target; + + $content_module = $connected_cms[$this->cms_type]->content_module[$current_module]; + + if ( (! $content_module->isDummy()) AND ($connected_cms[$this->cms_type]->isAuthNecessary() == true) AND ($connected_cms[$this->cms_type]->user->isConnected() == true)) { + if (! $content_module->isAllowed(OPERATION_VISIBLE)) { + return false; + } + } + + if ($_SESSION['elearning_open_close'][$content_module->getReferenceString()] == true) + $this->setViewMode("open"); + $module_title = $content_module->getTitle(); +/*/ + if ($mode == "searchresult") { + $module_title = $module_title . " (ID " . $content_module->getId() . ", "; + if ($content_module->isAllowed(OPERATION_WRITE)) + $module_title = $module_title . " " . _("Schreibzugriff") . ")"; + else + $module_title = $module_title . " " . _("Lesezugriff") . ")"; + }/**/ + if ($this->isOpen() == true) + $module_link = URLHelper::getLink('?do_close='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); + else + $module_link = URLHelper::getLink('?do_open='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); + if (! $content_module->isDummy()) + $module_buttons = $connected_cms[$this->cms_type]->link->getUserModuleLinks(); + $template = $GLOBALS['template_factory']->open('elearning/_content_module.php'); + $template->set_attribute('module_anker_target', ($anker_target == $content_module->getReferenceString())); + $template->set_attribute('module_link', $module_link); + $template->set_attribute('module_buttons', $module_buttons); + $template->set_attribute('module_authors', $content_module->getAuthors()); + $template->set_attribute('module_title', $module_title); + $template->set_attribute('module_description', $content_module->getDescription()); + $template->set_attribute('module_is_new', $this->module_new); + $template->set_attribute('module_chdate', $this->change_date); + $template->set_attribute('module_reference', $content_module->getReferenceString()); + $template->set_attribute('module_source', $content_module->getCMSName() . " / " . $content_module->getModuleTypeName()); + $template->set_attribute('module_icon', $content_module->getIcon()); + $template->set_attribute('module_is_open', $this->isOpen()); + return $template->render(); + } + + /** + * show module-data to admin + * + * show module-data in printhead/printcontent-style. admin-mode + * @access public + */ + function showAdmin($mode = "") + { + global $connected_cms, $view, $search_key, $cms_select, $current_module, $anker_target; + + $content_module = $connected_cms[$this->cms_type]->content_module[$current_module]; + + if ( (! $content_module->isDummy()) AND ($connected_cms[$this->cms_type]->isAuthNecessary() == true) AND ($connected_cms[$this->cms_type]->user->isConnected() == true)) { + if (! $content_module->isAllowed(OPERATION_VISIBLE)) { + return false; + } + } + + if ($_SESSION['elearning_open_close'][$content_module->getReferenceString()] == true) + $this->setViewMode("open"); + + $module_title = $content_module->getTitle(); + $module_title = $module_title . " (ID " . $content_module->getId(); + if ($content_module->isAllowed(OPERATION_WRITE)) + $module_title = $module_title . ", " . _("Schreibzugriff"); + elseif ($content_module->isAllowed(OPERATION_READ)) + $module_title = $module_title . ", " . _("Lesezugriff"); + else + $module_title = $module_title . ", " . _("kein Lesezugriff"); + $module_title = $module_title . ")"; + + if ($this->isOpen() == true) + $module_link = URLHelper::getLink('?do_close='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); + else + $module_link = URLHelper::getLink('?do_open='. $content_module->getReferenceString() . '&view='.$view.'&search_key='.$search_key.'&cms_select='.$cms_select.'#anker'); + + $template = $GLOBALS['template_factory']->open('elearning/_content_module.php'); + $template->set_attribute('module_anker_target', ($anker_target == $content_module->getReferenceString())); + $template->set_attribute('module_link', $module_link); + if ($content_module->isAllowed(OPERATION_READ)) + $module_buttons = $connected_cms[$this->cms_type]->link->getAdminModuleLinks(); + $template->set_attribute('module_buttons', $module_buttons); + $template->set_attribute('module_authors', $content_module->getAuthors()); + $template->set_attribute('module_title', $module_title); + $template->set_attribute('module_description', $content_module->getDescription()); + $template->set_attribute('module_is_new', $this->module_new); + $template->set_attribute('module_chdate', $this->change_date); + $template->set_attribute('module_reference', $content_module->getReferenceString()); + $template->set_attribute('module_source', $content_module->getCMSName() . " / " . $content_module->getModuleTypeName()); + $template->set_attribute('module_icon', $content_module->getIcon()); + $template->set_attribute('module_is_open', $this->isOpen()); + return $template->render(); + } + + /** + * get open-status + * + * returns true, if module is opened + * @access public + * @return boolean open-status + */ + function isOpen() + { + if ($this->view_mode == "open") + return true; + else + return false; + } + + /** + * set view-mode + * + * sets view-mode + * @access public + * @param boolean $module_mode view-mode + */ + function setViewMode($module_mode) + { + $this->view_mode = $module_mode; + } + + /** + * set changedate + * + * sets changedate for view + * @access public + * @param string $module_chdate changedate + */ + function setChangeDate($module_chdate) + { + $this->change_date = $module_chdate; + + if (object_get_visit(Context::getId(), "elearning_interface") < $this->change_date) + $this->module_new = true; + else + $this->module_new = false; + } +} +?> diff --git a/lib/elearning/ELearningUtils.class.php b/lib/elearning/ELearningUtils.class.php deleted file mode 100644 index 33fb4a2..0000000 --- a/lib/elearning/ELearningUtils.class.php +++ /dev/null @@ -1,612 +0,0 @@ - -* @package ELearning-Interface -*/ - -use Studip\Button, Studip\LinkButton; - -class ELearningUtils -{ - /** - * loads class ConnectedCMS for given system-type and creates an instance - * - * @param string $cms system-type - */ - public static function loadClass($cms) - { - global $connected_cms, $ELEARNING_INTERFACE_MODULES; - - if (!isset($connected_cms[$cms]) || !is_object($connected_cms[$cms])) { - require_once "lib/elearning/{$ELEARNING_INTERFACE_MODULES[$cms]['CLASS_PREFIX']}ConnectedCMS.class.php"; - $classname = "{$ELEARNING_INTERFACE_MODULES[$cms]['CLASS_PREFIX']}ConnectedCMS"; - $connected_cms[$cms] = new $classname($cms); - $connected_cms[$cms]->initSubclasses(); - } - } - - public static function initElearningInterfaces() - { - global $ELEARNING_INTERFACE_MODULES, $connected_cms; - if (is_array($ELEARNING_INTERFACE_MODULES)) { - foreach (array_keys($ELEARNING_INTERFACE_MODULES) as $cms) { - if (self::isCMSActive($cms)) { - self::loadClass($cms); - } - } - } - return is_array($connected_cms) ? count($connected_cms) : false; - } - - /** - * gets config-value with given name from globals - * - * @param string $name entry-name - * @param string $cms system-type - * @return boolean returns false if no cms is given - */ - public static function getConfigValue($name, $cms) - { - if (!$cms) { - return false; - } - return Config::get()->getValue("ELEARNING_INTERFACE_{$cms}_{$name}"); - } - - /** - * set config-value - * - * writes config-value with given name and value to database - * - * @param string $name entry-name - * @param string $value value - * @param string $cms system-type - */ - public static function setConfigValue($name, $value, $cms) - { - if (!$cms) { - return; - } - - try { - Config::get()->store("ELEARNING_INTERFACE_{$cms}_{$name}", $value); - } catch (InvalidArgumentException $e) { - Config::get()->create("ELEARNING_INTERFACE_{$cms}_{$name}", [ - 'value' => $value, - 'type' => 'string', - ]); - } - } - - /** - * check cms-status - * - * checks if connected content-management-system is activated - * - * @param string $cms system-type - */ - public static function isCMSActive($cms) - { - return self::getConfigValue('ACTIVE', $cms); - } - - /** - * get cms-selectbox - * - * returns a form to select a cms - * - * @param string $message description-text - * @param boolean $check_active show only activated systems - * @return string returns html-code - */ - public static function getCMSSelectbox($message, $check_active = true) - { - global $ELEARNING_INTERFACE_MODULES, $cms_select, $search_key, $view; - if (!is_array($ELEARNING_INTERFACE_MODULES)) { - $msg = sprintf(_("Die ELearning-Schnittstelle ist nicht korrekt konfiguriert. Die Variable \"%s\" " - ."muss in der Konfigurationsdatei von Stud.IP erst mit den Verbindungsdaten angebundener " - ."Learning-Content-Management-Systeme aufgefüllt werden. Solange dies nicht geschehen " - ."ist, setzen Sie die Variable \"%s\" auf FALSE!"), "\$ELEARNING_INTERFACE_MODULES", "\$ELEARNING_INTERFACE_ENABLE"); - PageLayout::postError($msg); - return false; - } - $options = []; - foreach ($ELEARNING_INTERFACE_MODULES as $cms => $cms_preferences) { - if (!$check_active || self::isCMSActive($cms)) { - $options[$cms] = $cms_preferences['name']; - } - } - $template = $GLOBALS['template_factory']->open('elearning/_cms_selectbox.php'); - $template->cms_select = $cms_select; - $template->options = $options; - $template->search_key = $search_key; - $template->view = $view; - $template->message = $message; - return $template->render(); - } - - /** - * get moduletype-selectbox - * - * returns a form to select type for new contentmodule - * - * @param string $cms system-type - * @return string returns html-code - */ - public static function getTypeSelectbox($cms) - { - global $ELEARNING_INTERFACE_MODULES; - $options = []; - foreach ($ELEARNING_INTERFACE_MODULES[$cms]['types'] as $type => $info) { - $options[$type] = $info['name']; - if (Request::get("module_type_{$cms}") === $type) { - $selected = $type; - } - } - $template = $GLOBALS['template_factory']->open('elearning/_type_selectbox.php'); - $template->options = $options; - $template->selected = $selected; - $template->cms = $cms; - return $template->render(); - } - - /** - * get searchfield - * - * returns a form to search for modules - * - * @param string $message description-text - * @return string returns html-code - */ - public static function getSearchfield($message) - { - global $cms_select, $search_key, $view; - $template = $GLOBALS['template_factory']->open('elearning/_searchfield.php'); - $template->cms_select = $cms_select; - $template->search_key = $search_key; - $template->view = $view; - $template->message = $message; - return $template->render(); - } - - /** - * get form for new content-module - * - * returns a form to choose module-type and to create a new content-module - * - * @param string $cms system-type - * @return string returns html-code - */ - public static function getNewModuleForm($cms) - { - global $ELEARNING_INTERFACE_MODULES, $connected_cms; - if (sizeof($ELEARNING_INTERFACE_MODULES[$cms]["types"]) == 1) - foreach($ELEARNING_INTERFACE_MODULES[$cms]["types"] as $type => $info) - Request::set("module_type_" . $cms, $type); - $link = $connected_cms[$cms]->link->getNewModuleLink(); - - if ($link == false) - return false; - $types = []; - $cms_types = []; - foreach ($ELEARNING_INTERFACE_MODULES as $cms_type => $cms_data) - $cms_types["module_type_" . $cms_type] = Request::option("module_type_" . $cms_type); - $template = $GLOBALS['template_factory']->open('elearning/_new_module_form.php'); - $template->set_attribute('link', $link); - $template->set_attribute('cms', $cms); - $template->set_attribute('cms_types', $cms_types); - $template->set_attribute('types', $ELEARNING_INTERFACE_MODULES[$cms]["types"]); - return $template->render(); - } - - /** - * get form for external user-account - * - * returns a form for administration of external user-account - * - * @param string message message-string - * @param string my_account_cms cms-type - * @return string returns html-code - */ - public static function getMyAccountForm($message, $my_account_cms) - { - global $connected_cms; - $template = $GLOBALS['template_factory']->open('elearning/_my_account_form.php'); - if ($connected_cms[$my_account_cms]->user->isConnected()) { - $template->set_attribute('login', $connected_cms[$my_account_cms]->user->getUsername()); - $template->set_attribute('is_connected', 1); - } - $template->set_attribute('my_account_cms', $my_account_cms); - $template->set_attribute('message', $message); - return $template->render(); - } - - /** - * get form for new user - * - * returns a form to add a user-account to connected cms - * - * @param string $new_account_cms system-type - * @return string returns html-code - */ - public static function getNewAccountForm(&$new_account_cms) - { - global $connected_cms, $cms_select, $view, $current_module, $messages, - $ELEARNING_INTERFACE_MODULES; - - $new_account_step = Request::int('new_account_step'); - $ext_password = Request::get('ext_password'); - $ext_password_2 = Request::get('ext_password_2'); - $ext_username = Request::get('ext_username'); - $ref_id = Request::get('ref_id'); - $module_type = Request::get('module_type'); - - self::loadClass($new_account_cms); - - //Password was sent, but is to short - if (isset($ext_password_2) && !Request::submitted('go_back') && Request::submitted('next') && mb_strlen($ext_password_2) < 6) { - PageLayout::postError(_('Das Passwort muss mindestens 6 Zeichen lang sein!')); - $new_account_step--; - } elseif (isset($ext_password_2) && ! Request::submitted('go_back') && Request::submitted('next') && $ext_password != $ext_password_2) { - //Passwords doesn't match password repeat - PageLayout::postError(_('Das Passwort entspricht nicht der Passwort-Wiederholung!')); - $new_account_step--; - } - - // Benutzername was sent - if ($ext_username && !Request::submitted('go_back') && Request::submitted('assign')) { - $caching_status = $connected_cms[$new_account_cms]->soap_client->getCachingStatus(); - $connected_cms[$new_account_cms]->soap_client->setCachingStatus(false); - if ($connected_cms[$new_account_cms]->user->verifyLogin($ext_username, $ext_password)) { - $is_verified = true; - PageLayout::postInfo(_('Der Account wurde zugeordnet.')); - $connected_cms[$new_account_cms]->user->setCategory(""); - $connected_cms[$new_account_cms]->user->setUsername($ext_username); - $connected_cms[$new_account_cms]->user->setPassword($ext_password); - $connected_cms[$new_account_cms]->user->setConnection(USER_TYPE_ORIGINAL); - if ($ref_id) { - $connected_cms[$new_account_cms]->newContentModule($ref_id, $module_type, true); - $module_title = $connected_cms[$new_account_cms]->content_module[$current_module]->getTitle(); - $module_links = $connected_cms[$new_account_cms]->link->getUserModuleLinks(); - } - } else { - $new_account_step = 1; - PageLayout::postError(_('Die eingegebenen Login-Daten sind nicht korrekt.')); - } - $connected_cms[$new_account_cms]->soap_client->setCachingStatus($caching_status); - } - - if (Request::submitted('start')) { - $new_account_step = 1; - } - if (Request::submitted('go_back')) { - $new_account_step--; - if ($new_account_step < 1) { - $new_account_cms = ''; - return false; - } - } elseif (Request::submitted('next') || Request::submitted('assign')) { - $new_account_step++; - } - - if ($new_account_step == 2 && Request::submitted('assign')) { - // Assign existing Account - $step = 'assign'; - } elseif ($new_account_step == 2 && Request::submitted('next')) { - // Create new Account: ask for new password - $step = 'new_account'; - } elseif ($new_account_step == 3 && Request::submitted('next')) { - // Create new Account - $connected_cms[$new_account_cms]->user->setPassword($ext_password); - if ($connected_cms[$new_account_cms]->user->newUser()) { - $is_verified = true; - PageLayout::postInfo(sprintf(_("Der Account wurde erzeugt und zugeordnet. Ihr Loginname ist %s."), "" . htmlReady($connected_cms[$new_account_cms]->user->getUsername()) . "")); - if ($ref_id) { - $connected_cms[$new_account_cms]->newContentModule($ref_id, $module_type, true); - $module_title = $connected_cms[$new_account_cms]->content_module[$current_module]->getTitle(); - $module_links = $connected_cms[$new_account_cms]->link->getUserModuleLinks(); - } - } - } elseif (!$is_verified) { - if (Request::submitted('start')) { - $messages["info"] = sprintf(_("Sie versuchen zum erstem Mal ein Lernmodul des angebundenen Systems %s zu starten. Bevor Sie das Modul nutzen können, muss Ihrem Stud.IP-Benutzeraccount ein Account im angebundenen System zugeordnet werden."), htmlReady($connected_cms[$new_account_cms]->getName())) . "

\n\n"; - } - } - $template = $GLOBALS['template_factory']->open('elearning/_new_account_form.php'); - $template->cms_title = htmlReady($connected_cms[$new_account_cms]->getName()); - $template->cms_select = $cms_select; - $template->module_title = $module_title; - $template->module_links = $module_links; - $template->module_type = $module_type; - $template->ref_id = $ref_id; - $template->ext_username = $ext_username; - $template->new_account_cms = $new_account_cms; - $template->new_account_step = $new_account_step; - $template->is_connected = $connected_cms[$new_account_cms]->user->isConnected(); - $template->is_verified = $is_verified; - $template->step = $step; - - // TODO: Should this really be below the assignment? - if ($is_verified) { - $new_account_cms = ""; - } - - return $template->render(); - } - - /** - * get table-header for connected cms - * - * returns a table-header for connected cms - * - * @param string $title table-title - * @return string returns html-code - */ - public static function getCMSHeader($title) - { - $template = $GLOBALS['template_factory']->open('elearning/_cms_header.php'); - $template->title = $title; - return $template->render(); - } - - /** - * get table-footer for connected cms - * - * returns a table-footer for connected cms - * - * @param string $logo system-logo - * @return string returns html-code - */ - public static function getCMSFooter($logo) - { - $template = $GLOBALS['template_factory']->open('elearning/_cms_footer.php'); - $template->logo = $logo; - return $template->render(); - } - - /** - * get headline for modules - * - * returns a table with a headline - * - * @param string $title headline - * @return string returns html-code - */ - public static function getModuleHeader($title) - { - global $view, $cms_select, $search_key; - $template = $GLOBALS['template_factory']->open('elearning/_module_header.php'); - $template->title = $title; - $template->view = $view; - $template->cms_select = $cms_select; - $template->search_key = $search_key; - $template->all_open = $_SESSION['elearning_open_close']['all open']; - return $template->render(); - } - - /** - * get Headline - * - * returns a table with a headline - * - * @param string $title headline - * @return string returns html-code - */ - public static function getHeader($title) - { - $template = $GLOBALS['template_factory']->open('elearning/_header.php'); - $template->title = $title; - return $template->render(); - } - - /** - * save timestamp - * - * saves a timestamp for debugging and performance-check - * - * @param string $stri description - */ - public static function bench($stri) - { - $GLOBALS['timearray'][] = [ - 'zeit' => microtime(true), - 'name' => $stri, - ]; - } - - /** - * show benchmark - * - * shows saved timestamps with descriptions - * - * @param string $stri description - */ - public static function showbench() - { - global $timearray; - echo "
"; - for ($i = 0; $i < count($timearray); $i++) { - echo ""; - } - echo "
Zeit (".$timearray[0]["name"].")
".$timearray[$i]["name"].": " . number_format(($timearray[$i]["zeit"]-$timearray[$i-1]["zeit"])*1000,2) . " msek
Gesamtzeit: " . number_format(($timearray[$i-1]["zeit"]-$timearray[0]["zeit"])*1000,2)." msek
"; - } - - /** - * delete cms-data - * - * deletes all data belonging to the specified cms from stud.ip database - */ - public static function deleteCMSData($cms_type) - { - $db = DBManager::get(); - $db->execute("DELETE FROM auth_extern WHERE external_user_system_type = ?", [$cms_type]); - $db->execute("DELETE FROM object_contentmodules WHERE system_type = ?", [$cms_type]); - - $config = Config::get(); - foreach ($config->getFields('global' ,null , "ELEARNING_INTERFACE_{$cms_type}") as $key) { - $config->delete($key); - } - } - - /** - * get ilias courses - * - * creates output of ilias courses linked to the chosen seminar. also updates object-connections. - * - * @return array - */ - public static function getIliasCourses($sem_id) - { - global $connected_cms, $messages, $view, $cms_select; - $db = DBManager::get(); - - $rs = $db->query("SELECT DISTINCT system_type, module_id - FROM object_contentmodules - WHERE module_type = 'crs' AND object_id = " . $db->quote($sem_id)) - ->fetchAll(PDO::FETCH_ASSOC); - $courses = []; - foreach ($rs as $row) { - $courses[$row['system_type']] = $row['module_id']; - } - - $connected_courses = [ - 'courses' => [], - ]; - if (is_array($courses)) { - foreach ($courses as $system_type => $crs_id) { - if (self::isCMSActive($system_type)) { - self::loadClass($system_type); - $connected_courses['courses'][$system_type] = [ - 'url' => URLHelper::getLink($connected_cms[$system_type]->link->cms_link . '?client_id=' . $connected_cms[$system_type]->getClientId() . '&cms_select=' . $system_type . '&ref_id=' . $crs_id . '&type=crs&target=start'), - 'cms_name' => $connected_cms[$system_type]->getName() - ]; - // gegebenenfalls zugeordnete Module aktualisieren - if (Request::option('update')) { - if ((method_exists($connected_cms[$system_type], "updateConnections"))) { - $connected_cms[$system_type]->updateConnections($crs_id); - } - } - if (method_exists($connected_cms[$system_type]->permissions, 'CheckUserPermissions')) { - $connected_cms[$system_type]->permissions->CheckUserPermissions($crs_id); - } - } - } - } - - if ($connected_courses['courses']) { - if (count($connected_courses['courses']) > 1) { - $connected_courses['text'] = _("Diese Veranstaltung ist mit folgenden Ilias-Kursen verknüpft. Hier gelangen Sie direkt in den jeweiligen Kurs: "); - } else { - $connected_courses['text'] = _("Diese Veranstaltung ist mit einem Ilias-Kurs verknüpft. Hier gelangen Sie direkt in den Kurs: "); - } - } - - return $connected_courses; - } - - /** - * check db-integrity - * - * checks if there are broken links in the database - * - * @return boolean successful - */ - public static function checkIntegrity() - { - global $ELEARNING_INTERFACE_MODULES, $messages; - $db = DBManager::get(); - - foreach ($ELEARNING_INTERFACE_MODULES as $cms_type =>$data) $cmsystems[$cms_type] = []; - - $config = Config::get(); - foreach ($config->getFields('global' ,null , 'ELEARNING_INTERFACE_') as $key) { - $parts = explode("_", $key); - $cmsystems[$parts[2]]["config"]++; - } - - $rs = $db->query("SELECT external_user_system_type, COUNT(*) as c FROM auth_extern GROUP BY external_user_system_type"); - while ($row = $rs->fetch()) - $cmsystems[$row["external_user_system_type"]]["accounts"] = $row['c']; - $rs = $db->query("SELECT system_type, COUNT(*) FROM object_contentmodules GROUP BY system_type"); - while ($row = $rs->fetch()) - $cmsystems[$row["system_type"]]["modules"] = $row['c']; - - if (Request::submitted('delete')) { - $messages["info"] .= "
"; - $messages["info"] .= CSRFProtection::tokenTag(); - $messages["info"] .= ""; - $messages["info"] .= ""; - $messages["info"] .= ""; - $messages["info"] .= "'; - $messages["info"] .= ""; - $messages["info"] .= "
 
" . sprintf(_("Durch das Löschen der Daten zum System mit dem Index \"%s\" werden %s Konfigurationseinträge und Verknüpfungen von Stud.IP-Veranstaltungen und -User-Accounts unwiederbringlich aus der Stud.IP-Datenbank entfernt. Wollen Sie diese Daten jetzt löschen?"), Request::quoted('delete_cms'), $cmsystems[Request::quoted('delete_cms')]["accounts"]+$cmsystems[Request::quoted('delete_cms')]["modules"]+$cmsystems[Request::quoted('delete_cms')]["config"] ) . "
"; - $messages["info"] .= '
' . Button::create(_('Alle löschen'), 'confirm_delete') . Button::createCancel(_('Abbrechen'), 'abbruch') . '
"; - $messages["info"] .= "
"; - } - - if (Request::submitted('confirm_delete')) { - unset($cmsystems[Request::quoted('delete_cms')]); -// deleteCMSData(Request::quoted('delete_cms')); - $messages["info"] .= _("Daten wurden gelöscht."); - } - - foreach ($cmsystems as $cms_type =>$data) { - if ($ELEARNING_INTERFACE_MODULES[$cms_type]) { - $output .= self::getCMSHeader($ELEARNING_INTERFACE_MODULES[$cms_type]["name"]); - $output .= ""; - $output .= ""; - if (self::isCMSActive($cms_type)) { - $output .= ""; - $output .= ""; - } - elseif ($data["config"] < 1) - $output .= ""; - elseif ($data["config"] < 1) - $output .= ""; - - if ($data["accounts"]) - $output .= ""; - if ($data["modules"]) - $output .= ""; - if ($data["config"]) - $output .= ""; - $output .= ""; - $output .= "
 
" . Icon::create('checkbox-checked', 'clickable')->asImg(['class' => 'text-top']) . "". sprintf(_("Die Schnittstelle zum System %s ist aktiv."), $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
 
" . Icon::create('checkbox-unchecked', 'clickable')->asImg(['class' => 'text-top']) . "". sprintf(_("Die Schnittstelle für das System %s wurde noch nicht eingerichtet."), $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
" . Icon::create('checkbox-unchecked', 'clickable')->asImg(['class' => 'text-top']) . "". sprintf(_("Die Schnittstelle wurde noch nicht aktiviert."), $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
". sprintf(_("%s Stud.IP-User-Accounts sind mit Accounts im System %s verknüpft."), $data["accounts"], $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
". sprintf(_("%s Objekte sind Stud.IP-Veranstaltungen oder -Einrichtungen zugeordnet."), $data["modules"]) . "
". sprintf(_("%s Einträge in der config-Tabelle der Stud.IP-Datenbank."), $data["config"]) . "
 
"; - $output .= self::getCMSFooter(($ELEARNING_INTERFACE_MODULES[$cms_type]["logo_file"] ? "" : $cms_type)); - } - else { - $output .= self::getCMSHeader(" Unbekanntes System: " . $cms_type . ""); - $output .= "
"; - $output .= CSRFProtection::tokenTag(); - $output .= ""; - $output .= ""; - $output .= ""; - $output .= ""; - $output .= ""; - if ($data["accounts"]) - $output .= ""; - if ($data["modules"]) - $output .= ""; - if ($data["config"]) - $output .= ""; - $output .= ""; - $output .= ""; - $output .= ""; - $output .= "
 
" . Icon::create('decline', 'attention')->asImg(['class' => 'text-top']) . "".sprintf(_("Für das System mit dem Index \"%s\" existieren keine Voreinstellungen in den Konfigurationsdateien mehr."), $cms_type) . "
 
". _("In der Stud.IP-Datenbank sind noch folgende Informationen zu diesem System gespeichert:") . "
". sprintf(_("%s Stud.IP-User-Accounts sind mit externen Accounts mit dem Index \"%s\" verknüpft."), $data["accounts"], $cms_type) . "
". sprintf(_("%s Objekte sind Stud.IP-Veranstaltungen oder -Einrichtungen zugeordnet."), $data["modules"]) . "
". sprintf(_("%s Einträge in der config-Tabelle der Stud.IP-Datenbank."), $data["config"]) . "
 
" . Button::create(_('Löschen'), 'delete') . "
 
"; - $output .= "
"; - $output .= self::getCMSFooter(''); - } - $output .= "
"; - } - - return $output; - } -} diff --git a/lib/elearning/ELearningUtils.php b/lib/elearning/ELearningUtils.php new file mode 100644 index 0000000..33fb4a2 --- /dev/null +++ b/lib/elearning/ELearningUtils.php @@ -0,0 +1,612 @@ + +* @package ELearning-Interface +*/ + +use Studip\Button, Studip\LinkButton; + +class ELearningUtils +{ + /** + * loads class ConnectedCMS for given system-type and creates an instance + * + * @param string $cms system-type + */ + public static function loadClass($cms) + { + global $connected_cms, $ELEARNING_INTERFACE_MODULES; + + if (!isset($connected_cms[$cms]) || !is_object($connected_cms[$cms])) { + require_once "lib/elearning/{$ELEARNING_INTERFACE_MODULES[$cms]['CLASS_PREFIX']}ConnectedCMS.class.php"; + $classname = "{$ELEARNING_INTERFACE_MODULES[$cms]['CLASS_PREFIX']}ConnectedCMS"; + $connected_cms[$cms] = new $classname($cms); + $connected_cms[$cms]->initSubclasses(); + } + } + + public static function initElearningInterfaces() + { + global $ELEARNING_INTERFACE_MODULES, $connected_cms; + if (is_array($ELEARNING_INTERFACE_MODULES)) { + foreach (array_keys($ELEARNING_INTERFACE_MODULES) as $cms) { + if (self::isCMSActive($cms)) { + self::loadClass($cms); + } + } + } + return is_array($connected_cms) ? count($connected_cms) : false; + } + + /** + * gets config-value with given name from globals + * + * @param string $name entry-name + * @param string $cms system-type + * @return boolean returns false if no cms is given + */ + public static function getConfigValue($name, $cms) + { + if (!$cms) { + return false; + } + return Config::get()->getValue("ELEARNING_INTERFACE_{$cms}_{$name}"); + } + + /** + * set config-value + * + * writes config-value with given name and value to database + * + * @param string $name entry-name + * @param string $value value + * @param string $cms system-type + */ + public static function setConfigValue($name, $value, $cms) + { + if (!$cms) { + return; + } + + try { + Config::get()->store("ELEARNING_INTERFACE_{$cms}_{$name}", $value); + } catch (InvalidArgumentException $e) { + Config::get()->create("ELEARNING_INTERFACE_{$cms}_{$name}", [ + 'value' => $value, + 'type' => 'string', + ]); + } + } + + /** + * check cms-status + * + * checks if connected content-management-system is activated + * + * @param string $cms system-type + */ + public static function isCMSActive($cms) + { + return self::getConfigValue('ACTIVE', $cms); + } + + /** + * get cms-selectbox + * + * returns a form to select a cms + * + * @param string $message description-text + * @param boolean $check_active show only activated systems + * @return string returns html-code + */ + public static function getCMSSelectbox($message, $check_active = true) + { + global $ELEARNING_INTERFACE_MODULES, $cms_select, $search_key, $view; + if (!is_array($ELEARNING_INTERFACE_MODULES)) { + $msg = sprintf(_("Die ELearning-Schnittstelle ist nicht korrekt konfiguriert. Die Variable \"%s\" " + ."muss in der Konfigurationsdatei von Stud.IP erst mit den Verbindungsdaten angebundener " + ."Learning-Content-Management-Systeme aufgefüllt werden. Solange dies nicht geschehen " + ."ist, setzen Sie die Variable \"%s\" auf FALSE!"), "\$ELEARNING_INTERFACE_MODULES", "\$ELEARNING_INTERFACE_ENABLE"); + PageLayout::postError($msg); + return false; + } + $options = []; + foreach ($ELEARNING_INTERFACE_MODULES as $cms => $cms_preferences) { + if (!$check_active || self::isCMSActive($cms)) { + $options[$cms] = $cms_preferences['name']; + } + } + $template = $GLOBALS['template_factory']->open('elearning/_cms_selectbox.php'); + $template->cms_select = $cms_select; + $template->options = $options; + $template->search_key = $search_key; + $template->view = $view; + $template->message = $message; + return $template->render(); + } + + /** + * get moduletype-selectbox + * + * returns a form to select type for new contentmodule + * + * @param string $cms system-type + * @return string returns html-code + */ + public static function getTypeSelectbox($cms) + { + global $ELEARNING_INTERFACE_MODULES; + $options = []; + foreach ($ELEARNING_INTERFACE_MODULES[$cms]['types'] as $type => $info) { + $options[$type] = $info['name']; + if (Request::get("module_type_{$cms}") === $type) { + $selected = $type; + } + } + $template = $GLOBALS['template_factory']->open('elearning/_type_selectbox.php'); + $template->options = $options; + $template->selected = $selected; + $template->cms = $cms; + return $template->render(); + } + + /** + * get searchfield + * + * returns a form to search for modules + * + * @param string $message description-text + * @return string returns html-code + */ + public static function getSearchfield($message) + { + global $cms_select, $search_key, $view; + $template = $GLOBALS['template_factory']->open('elearning/_searchfield.php'); + $template->cms_select = $cms_select; + $template->search_key = $search_key; + $template->view = $view; + $template->message = $message; + return $template->render(); + } + + /** + * get form for new content-module + * + * returns a form to choose module-type and to create a new content-module + * + * @param string $cms system-type + * @return string returns html-code + */ + public static function getNewModuleForm($cms) + { + global $ELEARNING_INTERFACE_MODULES, $connected_cms; + if (sizeof($ELEARNING_INTERFACE_MODULES[$cms]["types"]) == 1) + foreach($ELEARNING_INTERFACE_MODULES[$cms]["types"] as $type => $info) + Request::set("module_type_" . $cms, $type); + $link = $connected_cms[$cms]->link->getNewModuleLink(); + + if ($link == false) + return false; + $types = []; + $cms_types = []; + foreach ($ELEARNING_INTERFACE_MODULES as $cms_type => $cms_data) + $cms_types["module_type_" . $cms_type] = Request::option("module_type_" . $cms_type); + $template = $GLOBALS['template_factory']->open('elearning/_new_module_form.php'); + $template->set_attribute('link', $link); + $template->set_attribute('cms', $cms); + $template->set_attribute('cms_types', $cms_types); + $template->set_attribute('types', $ELEARNING_INTERFACE_MODULES[$cms]["types"]); + return $template->render(); + } + + /** + * get form for external user-account + * + * returns a form for administration of external user-account + * + * @param string message message-string + * @param string my_account_cms cms-type + * @return string returns html-code + */ + public static function getMyAccountForm($message, $my_account_cms) + { + global $connected_cms; + $template = $GLOBALS['template_factory']->open('elearning/_my_account_form.php'); + if ($connected_cms[$my_account_cms]->user->isConnected()) { + $template->set_attribute('login', $connected_cms[$my_account_cms]->user->getUsername()); + $template->set_attribute('is_connected', 1); + } + $template->set_attribute('my_account_cms', $my_account_cms); + $template->set_attribute('message', $message); + return $template->render(); + } + + /** + * get form for new user + * + * returns a form to add a user-account to connected cms + * + * @param string $new_account_cms system-type + * @return string returns html-code + */ + public static function getNewAccountForm(&$new_account_cms) + { + global $connected_cms, $cms_select, $view, $current_module, $messages, + $ELEARNING_INTERFACE_MODULES; + + $new_account_step = Request::int('new_account_step'); + $ext_password = Request::get('ext_password'); + $ext_password_2 = Request::get('ext_password_2'); + $ext_username = Request::get('ext_username'); + $ref_id = Request::get('ref_id'); + $module_type = Request::get('module_type'); + + self::loadClass($new_account_cms); + + //Password was sent, but is to short + if (isset($ext_password_2) && !Request::submitted('go_back') && Request::submitted('next') && mb_strlen($ext_password_2) < 6) { + PageLayout::postError(_('Das Passwort muss mindestens 6 Zeichen lang sein!')); + $new_account_step--; + } elseif (isset($ext_password_2) && ! Request::submitted('go_back') && Request::submitted('next') && $ext_password != $ext_password_2) { + //Passwords doesn't match password repeat + PageLayout::postError(_('Das Passwort entspricht nicht der Passwort-Wiederholung!')); + $new_account_step--; + } + + // Benutzername was sent + if ($ext_username && !Request::submitted('go_back') && Request::submitted('assign')) { + $caching_status = $connected_cms[$new_account_cms]->soap_client->getCachingStatus(); + $connected_cms[$new_account_cms]->soap_client->setCachingStatus(false); + if ($connected_cms[$new_account_cms]->user->verifyLogin($ext_username, $ext_password)) { + $is_verified = true; + PageLayout::postInfo(_('Der Account wurde zugeordnet.')); + $connected_cms[$new_account_cms]->user->setCategory(""); + $connected_cms[$new_account_cms]->user->setUsername($ext_username); + $connected_cms[$new_account_cms]->user->setPassword($ext_password); + $connected_cms[$new_account_cms]->user->setConnection(USER_TYPE_ORIGINAL); + if ($ref_id) { + $connected_cms[$new_account_cms]->newContentModule($ref_id, $module_type, true); + $module_title = $connected_cms[$new_account_cms]->content_module[$current_module]->getTitle(); + $module_links = $connected_cms[$new_account_cms]->link->getUserModuleLinks(); + } + } else { + $new_account_step = 1; + PageLayout::postError(_('Die eingegebenen Login-Daten sind nicht korrekt.')); + } + $connected_cms[$new_account_cms]->soap_client->setCachingStatus($caching_status); + } + + if (Request::submitted('start')) { + $new_account_step = 1; + } + if (Request::submitted('go_back')) { + $new_account_step--; + if ($new_account_step < 1) { + $new_account_cms = ''; + return false; + } + } elseif (Request::submitted('next') || Request::submitted('assign')) { + $new_account_step++; + } + + if ($new_account_step == 2 && Request::submitted('assign')) { + // Assign existing Account + $step = 'assign'; + } elseif ($new_account_step == 2 && Request::submitted('next')) { + // Create new Account: ask for new password + $step = 'new_account'; + } elseif ($new_account_step == 3 && Request::submitted('next')) { + // Create new Account + $connected_cms[$new_account_cms]->user->setPassword($ext_password); + if ($connected_cms[$new_account_cms]->user->newUser()) { + $is_verified = true; + PageLayout::postInfo(sprintf(_("Der Account wurde erzeugt und zugeordnet. Ihr Loginname ist %s."), "" . htmlReady($connected_cms[$new_account_cms]->user->getUsername()) . "")); + if ($ref_id) { + $connected_cms[$new_account_cms]->newContentModule($ref_id, $module_type, true); + $module_title = $connected_cms[$new_account_cms]->content_module[$current_module]->getTitle(); + $module_links = $connected_cms[$new_account_cms]->link->getUserModuleLinks(); + } + } + } elseif (!$is_verified) { + if (Request::submitted('start')) { + $messages["info"] = sprintf(_("Sie versuchen zum erstem Mal ein Lernmodul des angebundenen Systems %s zu starten. Bevor Sie das Modul nutzen können, muss Ihrem Stud.IP-Benutzeraccount ein Account im angebundenen System zugeordnet werden."), htmlReady($connected_cms[$new_account_cms]->getName())) . "

\n\n"; + } + } + $template = $GLOBALS['template_factory']->open('elearning/_new_account_form.php'); + $template->cms_title = htmlReady($connected_cms[$new_account_cms]->getName()); + $template->cms_select = $cms_select; + $template->module_title = $module_title; + $template->module_links = $module_links; + $template->module_type = $module_type; + $template->ref_id = $ref_id; + $template->ext_username = $ext_username; + $template->new_account_cms = $new_account_cms; + $template->new_account_step = $new_account_step; + $template->is_connected = $connected_cms[$new_account_cms]->user->isConnected(); + $template->is_verified = $is_verified; + $template->step = $step; + + // TODO: Should this really be below the assignment? + if ($is_verified) { + $new_account_cms = ""; + } + + return $template->render(); + } + + /** + * get table-header for connected cms + * + * returns a table-header for connected cms + * + * @param string $title table-title + * @return string returns html-code + */ + public static function getCMSHeader($title) + { + $template = $GLOBALS['template_factory']->open('elearning/_cms_header.php'); + $template->title = $title; + return $template->render(); + } + + /** + * get table-footer for connected cms + * + * returns a table-footer for connected cms + * + * @param string $logo system-logo + * @return string returns html-code + */ + public static function getCMSFooter($logo) + { + $template = $GLOBALS['template_factory']->open('elearning/_cms_footer.php'); + $template->logo = $logo; + return $template->render(); + } + + /** + * get headline for modules + * + * returns a table with a headline + * + * @param string $title headline + * @return string returns html-code + */ + public static function getModuleHeader($title) + { + global $view, $cms_select, $search_key; + $template = $GLOBALS['template_factory']->open('elearning/_module_header.php'); + $template->title = $title; + $template->view = $view; + $template->cms_select = $cms_select; + $template->search_key = $search_key; + $template->all_open = $_SESSION['elearning_open_close']['all open']; + return $template->render(); + } + + /** + * get Headline + * + * returns a table with a headline + * + * @param string $title headline + * @return string returns html-code + */ + public static function getHeader($title) + { + $template = $GLOBALS['template_factory']->open('elearning/_header.php'); + $template->title = $title; + return $template->render(); + } + + /** + * save timestamp + * + * saves a timestamp for debugging and performance-check + * + * @param string $stri description + */ + public static function bench($stri) + { + $GLOBALS['timearray'][] = [ + 'zeit' => microtime(true), + 'name' => $stri, + ]; + } + + /** + * show benchmark + * + * shows saved timestamps with descriptions + * + * @param string $stri description + */ + public static function showbench() + { + global $timearray; + echo ""; + for ($i = 0; $i < count($timearray); $i++) { + echo ""; + } + echo "
Zeit (".$timearray[0]["name"].")
".$timearray[$i]["name"].": " . number_format(($timearray[$i]["zeit"]-$timearray[$i-1]["zeit"])*1000,2) . " msek
Gesamtzeit: " . number_format(($timearray[$i-1]["zeit"]-$timearray[0]["zeit"])*1000,2)." msek
"; + } + + /** + * delete cms-data + * + * deletes all data belonging to the specified cms from stud.ip database + */ + public static function deleteCMSData($cms_type) + { + $db = DBManager::get(); + $db->execute("DELETE FROM auth_extern WHERE external_user_system_type = ?", [$cms_type]); + $db->execute("DELETE FROM object_contentmodules WHERE system_type = ?", [$cms_type]); + + $config = Config::get(); + foreach ($config->getFields('global' ,null , "ELEARNING_INTERFACE_{$cms_type}") as $key) { + $config->delete($key); + } + } + + /** + * get ilias courses + * + * creates output of ilias courses linked to the chosen seminar. also updates object-connections. + * + * @return array + */ + public static function getIliasCourses($sem_id) + { + global $connected_cms, $messages, $view, $cms_select; + $db = DBManager::get(); + + $rs = $db->query("SELECT DISTINCT system_type, module_id + FROM object_contentmodules + WHERE module_type = 'crs' AND object_id = " . $db->quote($sem_id)) + ->fetchAll(PDO::FETCH_ASSOC); + $courses = []; + foreach ($rs as $row) { + $courses[$row['system_type']] = $row['module_id']; + } + + $connected_courses = [ + 'courses' => [], + ]; + if (is_array($courses)) { + foreach ($courses as $system_type => $crs_id) { + if (self::isCMSActive($system_type)) { + self::loadClass($system_type); + $connected_courses['courses'][$system_type] = [ + 'url' => URLHelper::getLink($connected_cms[$system_type]->link->cms_link . '?client_id=' . $connected_cms[$system_type]->getClientId() . '&cms_select=' . $system_type . '&ref_id=' . $crs_id . '&type=crs&target=start'), + 'cms_name' => $connected_cms[$system_type]->getName() + ]; + // gegebenenfalls zugeordnete Module aktualisieren + if (Request::option('update')) { + if ((method_exists($connected_cms[$system_type], "updateConnections"))) { + $connected_cms[$system_type]->updateConnections($crs_id); + } + } + if (method_exists($connected_cms[$system_type]->permissions, 'CheckUserPermissions')) { + $connected_cms[$system_type]->permissions->CheckUserPermissions($crs_id); + } + } + } + } + + if ($connected_courses['courses']) { + if (count($connected_courses['courses']) > 1) { + $connected_courses['text'] = _("Diese Veranstaltung ist mit folgenden Ilias-Kursen verknüpft. Hier gelangen Sie direkt in den jeweiligen Kurs: "); + } else { + $connected_courses['text'] = _("Diese Veranstaltung ist mit einem Ilias-Kurs verknüpft. Hier gelangen Sie direkt in den Kurs: "); + } + } + + return $connected_courses; + } + + /** + * check db-integrity + * + * checks if there are broken links in the database + * + * @return boolean successful + */ + public static function checkIntegrity() + { + global $ELEARNING_INTERFACE_MODULES, $messages; + $db = DBManager::get(); + + foreach ($ELEARNING_INTERFACE_MODULES as $cms_type =>$data) $cmsystems[$cms_type] = []; + + $config = Config::get(); + foreach ($config->getFields('global' ,null , 'ELEARNING_INTERFACE_') as $key) { + $parts = explode("_", $key); + $cmsystems[$parts[2]]["config"]++; + } + + $rs = $db->query("SELECT external_user_system_type, COUNT(*) as c FROM auth_extern GROUP BY external_user_system_type"); + while ($row = $rs->fetch()) + $cmsystems[$row["external_user_system_type"]]["accounts"] = $row['c']; + $rs = $db->query("SELECT system_type, COUNT(*) FROM object_contentmodules GROUP BY system_type"); + while ($row = $rs->fetch()) + $cmsystems[$row["system_type"]]["modules"] = $row['c']; + + if (Request::submitted('delete')) { + $messages["info"] .= "
"; + $messages["info"] .= CSRFProtection::tokenTag(); + $messages["info"] .= ""; + $messages["info"] .= ""; + $messages["info"] .= ""; + $messages["info"] .= "'; + $messages["info"] .= ""; + $messages["info"] .= "
 
" . sprintf(_("Durch das Löschen der Daten zum System mit dem Index \"%s\" werden %s Konfigurationseinträge und Verknüpfungen von Stud.IP-Veranstaltungen und -User-Accounts unwiederbringlich aus der Stud.IP-Datenbank entfernt. Wollen Sie diese Daten jetzt löschen?"), Request::quoted('delete_cms'), $cmsystems[Request::quoted('delete_cms')]["accounts"]+$cmsystems[Request::quoted('delete_cms')]["modules"]+$cmsystems[Request::quoted('delete_cms')]["config"] ) . "
"; + $messages["info"] .= '
' . Button::create(_('Alle löschen'), 'confirm_delete') . Button::createCancel(_('Abbrechen'), 'abbruch') . '
"; + $messages["info"] .= "
"; + } + + if (Request::submitted('confirm_delete')) { + unset($cmsystems[Request::quoted('delete_cms')]); +// deleteCMSData(Request::quoted('delete_cms')); + $messages["info"] .= _("Daten wurden gelöscht."); + } + + foreach ($cmsystems as $cms_type =>$data) { + if ($ELEARNING_INTERFACE_MODULES[$cms_type]) { + $output .= self::getCMSHeader($ELEARNING_INTERFACE_MODULES[$cms_type]["name"]); + $output .= ""; + $output .= ""; + if (self::isCMSActive($cms_type)) { + $output .= ""; + $output .= ""; + } + elseif ($data["config"] < 1) + $output .= ""; + elseif ($data["config"] < 1) + $output .= ""; + + if ($data["accounts"]) + $output .= ""; + if ($data["modules"]) + $output .= ""; + if ($data["config"]) + $output .= ""; + $output .= ""; + $output .= "
 
" . Icon::create('checkbox-checked', 'clickable')->asImg(['class' => 'text-top']) . "". sprintf(_("Die Schnittstelle zum System %s ist aktiv."), $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
 
" . Icon::create('checkbox-unchecked', 'clickable')->asImg(['class' => 'text-top']) . "". sprintf(_("Die Schnittstelle für das System %s wurde noch nicht eingerichtet."), $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
" . Icon::create('checkbox-unchecked', 'clickable')->asImg(['class' => 'text-top']) . "". sprintf(_("Die Schnittstelle wurde noch nicht aktiviert."), $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
". sprintf(_("%s Stud.IP-User-Accounts sind mit Accounts im System %s verknüpft."), $data["accounts"], $ELEARNING_INTERFACE_MODULES[$cms_type]["name"]) . "
". sprintf(_("%s Objekte sind Stud.IP-Veranstaltungen oder -Einrichtungen zugeordnet."), $data["modules"]) . "
". sprintf(_("%s Einträge in der config-Tabelle der Stud.IP-Datenbank."), $data["config"]) . "
 
"; + $output .= self::getCMSFooter(($ELEARNING_INTERFACE_MODULES[$cms_type]["logo_file"] ? "" : $cms_type)); + } + else { + $output .= self::getCMSHeader(" Unbekanntes System: " . $cms_type . ""); + $output .= "
"; + $output .= CSRFProtection::tokenTag(); + $output .= ""; + $output .= ""; + $output .= ""; + $output .= ""; + $output .= ""; + if ($data["accounts"]) + $output .= ""; + if ($data["modules"]) + $output .= ""; + if ($data["config"]) + $output .= ""; + $output .= ""; + $output .= ""; + $output .= ""; + $output .= "
 
" . Icon::create('decline', 'attention')->asImg(['class' => 'text-top']) . "".sprintf(_("Für das System mit dem Index \"%s\" existieren keine Voreinstellungen in den Konfigurationsdateien mehr."), $cms_type) . "
 
". _("In der Stud.IP-Datenbank sind noch folgende Informationen zu diesem System gespeichert:") . "
". sprintf(_("%s Stud.IP-User-Accounts sind mit externen Accounts mit dem Index \"%s\" verknüpft."), $data["accounts"], $cms_type) . "
". sprintf(_("%s Objekte sind Stud.IP-Veranstaltungen oder -Einrichtungen zugeordnet."), $data["modules"]) . "
". sprintf(_("%s Einträge in der config-Tabelle der Stud.IP-Datenbank."), $data["config"]) . "
 
" . Button::create(_('Löschen'), 'delete') . "
 
"; + $output .= "
"; + $output .= self::getCMSFooter(''); + } + $output .= "
"; + } + + return $output; + } +} diff --git a/lib/elearning/Ilias3ConnectedCMS.class.php b/lib/elearning/Ilias3ConnectedCMS.class.php deleted file mode 100644 index 3e428ff..0000000 --- a/lib/elearning/Ilias3ConnectedCMS.class.php +++ /dev/null @@ -1,350 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module Ilias3ConnectedCMS -* @package ELearning-Interface -*/ -class Ilias3ConnectedCMS extends ConnectedCMS -{ - var $client_id; - var $root_user_sid; - var $main_category_node_id; - var $user_role_template_id; - var $user_skin; - var $user_style; - var $crs_roles; - var $global_roles; - var $soap_client; - var $encrypt_passwords; - var $is_first_call = true; - - /** - * constructor - * - * init class. - * @access public - * @param string $cms system-type - */ - function __construct($cms) - { - global $ELEARNING_INTERFACE_MODULES; - - parent::__construct($cms); - - require_once($this->CLASS_PREFIX . "Soap.class.php"); - $classname = $this->CLASS_PREFIX . "Soap"; - $this->soap_client = new $classname($this->cms_type); - $this->soap_client->setCachingStatus(true); - - $this->main_category_node_id = ELearningUtils::getConfigValue("category_id", $cms); - - if ((ELearningUtils::getConfigValue("user_role_template_id", $cms) == "") AND ($GLOBALS["role_template_name"] == "")) - $GLOBALS["role_template_name"] = "Author"; - $this->user_role_template_id = ELearningUtils::getConfigValue("user_role_template_id", $cms); - $this->user_skin = ELearningUtils::getConfigValue("user_skin", $cms); - $this->user_style = ELearningUtils::getConfigValue("user_style", $cms); - $this->encrypt_passwords = ELearningUtils::getConfigValue("encrypt_passwords", $cms); - - $this->crs_roles = $ELEARNING_INTERFACE_MODULES[$cms]["crs_roles"]; - $this->client_id = $ELEARNING_INTERFACE_MODULES[$cms]["soap_data"]["client"]; - $this->global_roles = $ELEARNING_INTERFACE_MODULES[$cms]["global_roles"]; - } - - /** - * get preferences - * - * shows additional settings. - */ - public function getPreferences() - { - $role_template_name = Request::get('role_template_name'); - $cat_name = Request::get('cat_name'); - $style_setting = Request::option('style_setting'); - $encrypt_passwords = Request::option('encrypt_passwords'); - - $this->soap_client->setCachingStatus(false); - - $messages = ['error' => '']; - - if ($cat_name) { - $cat = $this->soap_client->getReferenceByTitle( trim( $cat_name ), "cat"); - if (!$cat) { - $messages["error"] .= sprintf(_("Das Objekt mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($cat_name), htmlReady($this->getName())) . "
\n"; - } else { - ELearningUtils::setConfigValue("category_id", $cat, $this->cms_type); - $this->main_category_node_id = $cat; - } - } - - if ($role_template_name) { - $role_template = $this->soap_client->getObjectByTitle( trim( $role_template_name ), "rolt" ); - if (!$role_template) { - $messages["error"] .= sprintf(_("Das Rollen-Template mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($role_template_name), htmlReady($this->getName())) . "
\n"; - } elseif (is_array($role_template)) { - ELearningUtils::setConfigValue("user_role_template_id", $role_template["obj_id"], $this->cms_type); - ELearningUtils::setConfigValue("user_role_template_name", $role_template["title"], $this->cms_type); - $this->user_role_template_id = $role_template["obj_id"]; - } - } - - if (Request::submitted('submit')) { - ELearningUtils::setConfigValue("user_style", $style_setting, $this->cms_type); - ELearningUtils::setConfigValue("user_skin", $style_setting, $this->cms_type); - ELearningUtils::setConfigValue("encrypt_passwords", $encrypt_passwords, $this->cms_type); - } else { - if (ELearningUtils::getConfigValue("user_style", $this->cms_type)) { - $style_setting = ELearningUtils::getConfigValue("user_style", $this->cms_type); - } - if (ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type)) { - $encrypt_passwords = ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type); - } - } - - - if ($messages['error']) { - echo "" . Icon::create('decline', 'attention')->asImg(['class' => 'text-top', 'title' => _('Fehler')]) . " " . $messages["error"] . "

"; - } - - echo ""; - echo ""; - echo "
"; - echo "" . _("SOAP-Verbindung: ") . ""; - echo ""; - $error = $this->soap_client->getError(); - if ($error != false) - echo sprintf(_("Beim Herstellen der SOAP-Verbindung trat folgender Fehler auf:")) . "

" . $error; - else - echo sprintf(_("Die SOAP-Verbindung zum Klienten \"%s\" wurde hergestellt, der Name des Administrator-Accounts ist \"%s\"."), htmlReady($this->soap_data["client"]), htmlReady($this->soap_data["username"])); - echo "
\n"; - echo "
\n"; - echo "
"; - - $cat = $this->soap_client->getObjectByReference( $this->main_category_node_id ); - echo '' . _('Kategorie') . ':'; - echo ""; - echo " "; - echo Icon::create('info-circle', 'inactive', ['title' => _('Geben Sie hier den Namen einer bestehenden ILIAS 3 - Kategorie ein, in der die Lernmodule und User-Kategorien abgelegt werden sollen.')])->asImg(); - echo "
"; - echo " (ID " . $this->main_category_node_id; - if ($cat["description"] != "") - echo ", " . _("Beschreibung: ") . htmlReady($cat["description"]); - echo ")"; - echo "
\n"; - echo "
\n"; - echo "
"; - - - echo "" . _("Rollen-Template für die persönliche Kategorie: ") . ""; - echo ""; - echo "cms_type) . "\" name=\"role_template_name\"> "; - echo Icon::create('info-circle', 'inactive', ['title' => _('Geben Sie den Namen des Rollen-Templates ein, das für die persönliche Kategorie von Lehrenden verwendet werden soll (z.B. \"Author\").')])->asImg(); - echo "
"; - echo " (ID " . $this->user_role_template_id; - echo ")"; - echo "
\n"; - echo "
\n"; - echo "
"; - - echo "" . _("Passwörter: ") . ""; - echo ""; - echo " " . _("ILIAS-Passwörter verschlüsselt speichern."); - echo Icon::create('info-circle', 'inactive', ['title' => _('Wählen Sie diese Option, wenn die ILIAS-Passwörter der zugeordneten Accounts verschlüsselt in der Stud.IP-Datenbank abgelegt werden sollen.')])->asImg(); - echo "
"; - echo "
\n"; - echo "
\n"; - echo "
"; - - echo "" . _("Style / Skin: ") . ""; - echo ""; - echo " " . _("Stud.IP-Style für neue Nutzer-Accounts voreinstellen."); - echo Icon::create('info-circle', 'inactive', ['title' => _('Wählen Sie diese Option, wenn für alle von Stud.IP angelegten ILIAS-Accounts das Stud.IP-Layout als System-Style eingetragen werden soll. ILIAS-seitig angelegte Accounts erhalten weiterhin den Standard-Style.')])->asImg(); - echo "
"; - echo "
\n"; - echo "
\n"; - - - - - echo "
"; - echo "
" . Button::create(_('übernehmen'), 'submit') . "

"; - echo "
\n"; - - parent::getPreferences(); - - echo "
\n"; - } - - function setContentModule($data, $is_connected = false) - { - parent::setContentModule($data, $is_connected); - - if ($data["owner"] != "") - { - $user_data = $this->soap_client->getUser($data["owner"]); - $user_name = trim($user_data["title"] . " " . $user_data["firstname"] . " " . $user_data["lastname"]); - $this->content_module[$data["ref_id"]]->setAuthors($user_name); - } - $this->content_module[$data["ref_id"]]->setPermissions($data["accessInfo"], $data["operations"]); - } - - /** - * create new instance of subclass content-module - * - * creates new instance of subclass content-module and gets permissions - * @access public - * @param string $module_id module-id - * @param string $module_type module-type - * @param string $is_connected is module connected to seminar? - */ - function newContentModule($module_id, $module_type, $is_connected = false) - { - global $seminar_id, $current_module, $caching_active; - - $current_module = $module_id; -// echo "call module $module_id"; - - if ($this->is_first_call AND ($seminar_id != "") AND ($is_connected == true)) - { - $id = ObjectConnections::getConnectionModuleId( $seminar_id, "crs", $this->cms_type ); - if ($id != false) - { - if ($this->user->isConnected()) - $this->permissions->checkUserPermissions($id); - $this->is_first_call = false; - } -// echo "first call, ref_id $id"; - } - - parent::newContentModule($module_id, $module_type, $is_connected); - } - - /** - * get user modules - * - * returns user content modules - * @access public - * @return array list of content modules - */ - function getUserContentModules() - { - global $connected_cms; - - $types = []; - foreach ($this->types as $type => $name) - { - $types[] = $type; - } - if ($this->user->getCategory() == false) - return false; - $result = $this->soap_client->getTreeChilds($this->user->getCategory(), $types, $connected_cms[$this->cms_type]->user->getId()); - $obj_ids = []; - if (is_array($result)) - foreach($result as $key => $object_data){ - if (is_array($object_data["operations"])){ - if ((!in_array($object_data["obj_id"], $obj_ids) && in_array(OPERATION_READ, $object_data["operations"])) - || in_array(OPERATION_WRITE, $object_data["operations"])) - { - if (is_array($user_modules[$object_data["obj_id"]]["operations"])){ - if (in_array(OPERATION_WRITE, $user_modules[$object_data["obj_id"]]["operations"])){ - continue; - } - } - $user_modules[$object_data["obj_id"]] = $object_data; - $obj_ids[] = $result[$key]["obj_id"]; - } - } - } - return $user_modules; - } - - /** - * search for content modules - * - * returns found content modules - * @access public - * @param string $key keyword - * @return array list of content modules - */ - function searchContentModules($key) - { - global $connected_cms; - - $types = []; - foreach ($this->types as $type => $name) - { - $types[] = $type; - } - - $result = $this->soap_client->searchObjects($types, $key,"and", $connected_cms[$this->cms_type]->user->getId()); - return $result; - } - - - /** - * get client-id - * - * returns client-id - * @access public - * @return string client-id - */ - function getClientId() - { - return $this->client_id; - } - - /** - * get session-id - * - * returns soap-session-id - * @access public - * @return string session-id - */ - function getSID() - { - return $this->root_user_sid; - } - - /** - * terminate - * - * terminates connection. - */ - public function terminate() - { -// $this->soap_client->logout(); - $this->soap_client->saveCacheData(); - } - - //we have to delete the course only - function deleteConnectedModules($object_id){ - global $connected_cms; - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - $connected_cms[$this->cms_type]->soap_client->clearCache(); - $connected_cms[$this->cms_type]->soap_client->user_type == "admin"; - $crs_id = ObjectConnections::getConnectionModuleId($object_id, "crs", $this->cms_type); - if($crs_id && $connected_cms[$this->cms_type]->soap_client->checkReferenceById($crs_id)){ - $connected_cms[$this->cms_type]->soap_client->deleteObject($crs_id); - } - return parent::deleteConnectedModules($object_id); - } -} -?> diff --git a/lib/elearning/Ilias3ConnectedCMS.php b/lib/elearning/Ilias3ConnectedCMS.php new file mode 100644 index 0000000..3e428ff --- /dev/null +++ b/lib/elearning/Ilias3ConnectedCMS.php @@ -0,0 +1,350 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module Ilias3ConnectedCMS +* @package ELearning-Interface +*/ +class Ilias3ConnectedCMS extends ConnectedCMS +{ + var $client_id; + var $root_user_sid; + var $main_category_node_id; + var $user_role_template_id; + var $user_skin; + var $user_style; + var $crs_roles; + var $global_roles; + var $soap_client; + var $encrypt_passwords; + var $is_first_call = true; + + /** + * constructor + * + * init class. + * @access public + * @param string $cms system-type + */ + function __construct($cms) + { + global $ELEARNING_INTERFACE_MODULES; + + parent::__construct($cms); + + require_once($this->CLASS_PREFIX . "Soap.class.php"); + $classname = $this->CLASS_PREFIX . "Soap"; + $this->soap_client = new $classname($this->cms_type); + $this->soap_client->setCachingStatus(true); + + $this->main_category_node_id = ELearningUtils::getConfigValue("category_id", $cms); + + if ((ELearningUtils::getConfigValue("user_role_template_id", $cms) == "") AND ($GLOBALS["role_template_name"] == "")) + $GLOBALS["role_template_name"] = "Author"; + $this->user_role_template_id = ELearningUtils::getConfigValue("user_role_template_id", $cms); + $this->user_skin = ELearningUtils::getConfigValue("user_skin", $cms); + $this->user_style = ELearningUtils::getConfigValue("user_style", $cms); + $this->encrypt_passwords = ELearningUtils::getConfigValue("encrypt_passwords", $cms); + + $this->crs_roles = $ELEARNING_INTERFACE_MODULES[$cms]["crs_roles"]; + $this->client_id = $ELEARNING_INTERFACE_MODULES[$cms]["soap_data"]["client"]; + $this->global_roles = $ELEARNING_INTERFACE_MODULES[$cms]["global_roles"]; + } + + /** + * get preferences + * + * shows additional settings. + */ + public function getPreferences() + { + $role_template_name = Request::get('role_template_name'); + $cat_name = Request::get('cat_name'); + $style_setting = Request::option('style_setting'); + $encrypt_passwords = Request::option('encrypt_passwords'); + + $this->soap_client->setCachingStatus(false); + + $messages = ['error' => '']; + + if ($cat_name) { + $cat = $this->soap_client->getReferenceByTitle( trim( $cat_name ), "cat"); + if (!$cat) { + $messages["error"] .= sprintf(_("Das Objekt mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($cat_name), htmlReady($this->getName())) . "
\n"; + } else { + ELearningUtils::setConfigValue("category_id", $cat, $this->cms_type); + $this->main_category_node_id = $cat; + } + } + + if ($role_template_name) { + $role_template = $this->soap_client->getObjectByTitle( trim( $role_template_name ), "rolt" ); + if (!$role_template) { + $messages["error"] .= sprintf(_("Das Rollen-Template mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($role_template_name), htmlReady($this->getName())) . "
\n"; + } elseif (is_array($role_template)) { + ELearningUtils::setConfigValue("user_role_template_id", $role_template["obj_id"], $this->cms_type); + ELearningUtils::setConfigValue("user_role_template_name", $role_template["title"], $this->cms_type); + $this->user_role_template_id = $role_template["obj_id"]; + } + } + + if (Request::submitted('submit')) { + ELearningUtils::setConfigValue("user_style", $style_setting, $this->cms_type); + ELearningUtils::setConfigValue("user_skin", $style_setting, $this->cms_type); + ELearningUtils::setConfigValue("encrypt_passwords", $encrypt_passwords, $this->cms_type); + } else { + if (ELearningUtils::getConfigValue("user_style", $this->cms_type)) { + $style_setting = ELearningUtils::getConfigValue("user_style", $this->cms_type); + } + if (ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type)) { + $encrypt_passwords = ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type); + } + } + + + if ($messages['error']) { + echo "" . Icon::create('decline', 'attention')->asImg(['class' => 'text-top', 'title' => _('Fehler')]) . " " . $messages["error"] . "

"; + } + + echo ""; + echo ""; + echo "
"; + echo "" . _("SOAP-Verbindung: ") . ""; + echo ""; + $error = $this->soap_client->getError(); + if ($error != false) + echo sprintf(_("Beim Herstellen der SOAP-Verbindung trat folgender Fehler auf:")) . "

" . $error; + else + echo sprintf(_("Die SOAP-Verbindung zum Klienten \"%s\" wurde hergestellt, der Name des Administrator-Accounts ist \"%s\"."), htmlReady($this->soap_data["client"]), htmlReady($this->soap_data["username"])); + echo "
\n"; + echo "
\n"; + echo "
"; + + $cat = $this->soap_client->getObjectByReference( $this->main_category_node_id ); + echo '' . _('Kategorie') . ':'; + echo ""; + echo " "; + echo Icon::create('info-circle', 'inactive', ['title' => _('Geben Sie hier den Namen einer bestehenden ILIAS 3 - Kategorie ein, in der die Lernmodule und User-Kategorien abgelegt werden sollen.')])->asImg(); + echo "
"; + echo " (ID " . $this->main_category_node_id; + if ($cat["description"] != "") + echo ", " . _("Beschreibung: ") . htmlReady($cat["description"]); + echo ")"; + echo "
\n"; + echo "
\n"; + echo "
"; + + + echo "" . _("Rollen-Template für die persönliche Kategorie: ") . ""; + echo ""; + echo "cms_type) . "\" name=\"role_template_name\"> "; + echo Icon::create('info-circle', 'inactive', ['title' => _('Geben Sie den Namen des Rollen-Templates ein, das für die persönliche Kategorie von Lehrenden verwendet werden soll (z.B. \"Author\").')])->asImg(); + echo "
"; + echo " (ID " . $this->user_role_template_id; + echo ")"; + echo "
\n"; + echo "
\n"; + echo "
"; + + echo "" . _("Passwörter: ") . ""; + echo ""; + echo " " . _("ILIAS-Passwörter verschlüsselt speichern."); + echo Icon::create('info-circle', 'inactive', ['title' => _('Wählen Sie diese Option, wenn die ILIAS-Passwörter der zugeordneten Accounts verschlüsselt in der Stud.IP-Datenbank abgelegt werden sollen.')])->asImg(); + echo "
"; + echo "
\n"; + echo "
\n"; + echo "
"; + + echo "" . _("Style / Skin: ") . ""; + echo ""; + echo " " . _("Stud.IP-Style für neue Nutzer-Accounts voreinstellen."); + echo Icon::create('info-circle', 'inactive', ['title' => _('Wählen Sie diese Option, wenn für alle von Stud.IP angelegten ILIAS-Accounts das Stud.IP-Layout als System-Style eingetragen werden soll. ILIAS-seitig angelegte Accounts erhalten weiterhin den Standard-Style.')])->asImg(); + echo "
"; + echo "
\n"; + echo "
\n"; + + + + + echo "
"; + echo "
" . Button::create(_('übernehmen'), 'submit') . "

"; + echo "
\n"; + + parent::getPreferences(); + + echo "
\n"; + } + + function setContentModule($data, $is_connected = false) + { + parent::setContentModule($data, $is_connected); + + if ($data["owner"] != "") + { + $user_data = $this->soap_client->getUser($data["owner"]); + $user_name = trim($user_data["title"] . " " . $user_data["firstname"] . " " . $user_data["lastname"]); + $this->content_module[$data["ref_id"]]->setAuthors($user_name); + } + $this->content_module[$data["ref_id"]]->setPermissions($data["accessInfo"], $data["operations"]); + } + + /** + * create new instance of subclass content-module + * + * creates new instance of subclass content-module and gets permissions + * @access public + * @param string $module_id module-id + * @param string $module_type module-type + * @param string $is_connected is module connected to seminar? + */ + function newContentModule($module_id, $module_type, $is_connected = false) + { + global $seminar_id, $current_module, $caching_active; + + $current_module = $module_id; +// echo "call module $module_id"; + + if ($this->is_first_call AND ($seminar_id != "") AND ($is_connected == true)) + { + $id = ObjectConnections::getConnectionModuleId( $seminar_id, "crs", $this->cms_type ); + if ($id != false) + { + if ($this->user->isConnected()) + $this->permissions->checkUserPermissions($id); + $this->is_first_call = false; + } +// echo "first call, ref_id $id"; + } + + parent::newContentModule($module_id, $module_type, $is_connected); + } + + /** + * get user modules + * + * returns user content modules + * @access public + * @return array list of content modules + */ + function getUserContentModules() + { + global $connected_cms; + + $types = []; + foreach ($this->types as $type => $name) + { + $types[] = $type; + } + if ($this->user->getCategory() == false) + return false; + $result = $this->soap_client->getTreeChilds($this->user->getCategory(), $types, $connected_cms[$this->cms_type]->user->getId()); + $obj_ids = []; + if (is_array($result)) + foreach($result as $key => $object_data){ + if (is_array($object_data["operations"])){ + if ((!in_array($object_data["obj_id"], $obj_ids) && in_array(OPERATION_READ, $object_data["operations"])) + || in_array(OPERATION_WRITE, $object_data["operations"])) + { + if (is_array($user_modules[$object_data["obj_id"]]["operations"])){ + if (in_array(OPERATION_WRITE, $user_modules[$object_data["obj_id"]]["operations"])){ + continue; + } + } + $user_modules[$object_data["obj_id"]] = $object_data; + $obj_ids[] = $result[$key]["obj_id"]; + } + } + } + return $user_modules; + } + + /** + * search for content modules + * + * returns found content modules + * @access public + * @param string $key keyword + * @return array list of content modules + */ + function searchContentModules($key) + { + global $connected_cms; + + $types = []; + foreach ($this->types as $type => $name) + { + $types[] = $type; + } + + $result = $this->soap_client->searchObjects($types, $key,"and", $connected_cms[$this->cms_type]->user->getId()); + return $result; + } + + + /** + * get client-id + * + * returns client-id + * @access public + * @return string client-id + */ + function getClientId() + { + return $this->client_id; + } + + /** + * get session-id + * + * returns soap-session-id + * @access public + * @return string session-id + */ + function getSID() + { + return $this->root_user_sid; + } + + /** + * terminate + * + * terminates connection. + */ + public function terminate() + { +// $this->soap_client->logout(); + $this->soap_client->saveCacheData(); + } + + //we have to delete the course only + function deleteConnectedModules($object_id){ + global $connected_cms; + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + $connected_cms[$this->cms_type]->soap_client->clearCache(); + $connected_cms[$this->cms_type]->soap_client->user_type == "admin"; + $crs_id = ObjectConnections::getConnectionModuleId($object_id, "crs", $this->cms_type); + if($crs_id && $connected_cms[$this->cms_type]->soap_client->checkReferenceById($crs_id)){ + $connected_cms[$this->cms_type]->soap_client->deleteObject($crs_id); + } + return parent::deleteConnectedModules($object_id); + } +} +?> diff --git a/lib/elearning/Ilias3ConnectedLink.class.php b/lib/elearning/Ilias3ConnectedLink.class.php deleted file mode 100644 index f50fd53..0000000 --- a/lib/elearning/Ilias3ConnectedLink.class.php +++ /dev/null @@ -1,188 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module Ilias3ConnectedLink -* @package ELearning-Interface -*/ -class Ilias3ConnectedLink extends ConnectedLink -{ - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms) - { - parent::__construct($cms); - $this->cms_link = "ilias3_referrer.php"; - } - - /** - * get user module links - * - * returns content module links for user - * @return string html-code - */ - public function getUserModuleLinks() - { - global $connected_cms, $current_module; - - $output = ''; - - if ($connected_cms[$this->cms_type]->isAuthNecessary() && !$connected_cms[$this->cms_type]->user->isConnected()) { - $output .= $this->getNewAccountLink(); - } elseif (!$connected_cms[$this->cms_type]->content_module[$current_module]->isDummy()) { - if ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_READ)) { - $output .= LinkButton::create( - _('Starten'), - URLHelper::getURL($this->cms_link, [ - 'client_id' => $connected_cms[$this->cms_type]->getClientId(), - 'cms_select' => $this->cms_type, - // 'sess_id' => $connected_cms[$this->cms_type]->user->getSessionId(), - 'ref_id' => $connected_cms[$this->cms_type]->content_module[$current_module]->getId(), - 'type' => $connected_cms[$this->cms_type]->content_module[$current_module]->getModuleType(), - 'target' => 'start', - ]), - [ - 'target' => '_blank', - 'rel' => 'noopener noreferrer', - ] - ); - $output .= " "; - } - if ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_WRITE)) { - $output .= LinkButton::create( - _('Bearbeiten'), - URLHelper::getURL($this->cms_link, [ - 'client_id' => $connected_cms[$this->cms_type]->getClientId(), - 'cms_select' => $this->cms_type, - // 'sess_id' => $connected_cms[$this->cms_type]->user->getSessionId(), - 'ref_id' => $connected_cms[$this->cms_type]->content_module[$current_module]->getId(), - 'type' => $connected_cms[$this->cms_type]->content_module[$current_module]->getModuleType(), - 'target' => 'edit', - ]), - [ - 'target' => '_blank', - 'rel' => 'noopener noreferrer', - ] - ); - $output .= " "; - } - } - - return $output; - } - - /** - * get admin module links - * - * returns links add or remove a module from course - * @return string returns html-code - */ - public function getAdminModuleLinks() - { - global $connected_cms, $view, $search_key, $cms_select, $current_module; - - $output = "
\n"; - $output .= CSRFProtection::tokenTag(); - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; - $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; - $output .= "cms_type) . "\">\n"; - - if ($connected_cms[$this->cms_type]->content_module[$current_module]->isConnected()) { - $output .= " " . Button::create(_('Entfernen'), 'remove'); - } elseif ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_WRITE)) { - $output .= "
"; - $output .= _("Mit Schreibrechten für alle Lehrenden/Tutoren und Tutorinnen dieser Veranstaltung") . "
"; - $output .= ""; - $output .= _("Mit Schreibrechten für alle Teilnehmenden dieser Veranstaltung") . "
"; - $output .= Button::create(_('Hinzufügen'), 'add') . "
"; - } else { - $output .= " " . Button::create(_('Hinzufügen'), 'add'); - } - $output .= "
"; - - return $output; - } - - /** - * get new module link - * - * returns link to create a new module if allowed - * @return string|false returns html-code or false - */ - public function getNewModuleLink() - { - global $connected_cms, $auth; - $output = "\n"; - if (Request::get("module_type_" . $this->cms_type)) { - if (!$connected_cms[$this->cms_type]->user->category) { - $connected_cms[$this->cms_type]->user->newUserCategory(); - if ($connected_cms[$this->cms_type]->user->category == false) { - return $output; - } - } - $output = " "; - $output .= LinkButton::create( - _('Neu anlegen'), - URLHelper::getURL($this->cms_link, [ - 'client_id' => $connected_cms[$this->cms_type]->getClientId(), - 'cms_select' => $this->cms_type, -// 'sess_id' => $connected_cms[$this->cms_type]->user->getSessionId(), - 'ref_id' => $connected_cms[$this->cms_type]->user->category, - 'type' => Request::option("module_type_" . $this->cms_type), - 'target' => 'new', - ]), - [ - 'target' => '_blank', - 'rel' => 'noopener noreferrer', - ] - ); - } - $user_crs_role = $connected_cms[$this->cms_type]->crs_roles[$auth->auth['perm']]; - if ($user_crs_role === 'admin') { - return $output; - } - - return false; - } - - /** - * get start page link - * - * returns link to ilias start-page - * @access public - * @return string returns url or false - */ - function getStartpageLink() - { - global $connected_cms; - - if ($connected_cms[$this->cms_type]->user->isConnected()) { - $output = $this->cms_link . "?" - . "client_id=" . $connected_cms[$this->cms_type]->getClientId() - . "&cms_select=" . $this->cms_type - . "&target=login"; - } - return $output; - } -} -?> diff --git a/lib/elearning/Ilias3ConnectedLink.php b/lib/elearning/Ilias3ConnectedLink.php new file mode 100644 index 0000000..f50fd53 --- /dev/null +++ b/lib/elearning/Ilias3ConnectedLink.php @@ -0,0 +1,188 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module Ilias3ConnectedLink +* @package ELearning-Interface +*/ +class Ilias3ConnectedLink extends ConnectedLink +{ + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms) + { + parent::__construct($cms); + $this->cms_link = "ilias3_referrer.php"; + } + + /** + * get user module links + * + * returns content module links for user + * @return string html-code + */ + public function getUserModuleLinks() + { + global $connected_cms, $current_module; + + $output = ''; + + if ($connected_cms[$this->cms_type]->isAuthNecessary() && !$connected_cms[$this->cms_type]->user->isConnected()) { + $output .= $this->getNewAccountLink(); + } elseif (!$connected_cms[$this->cms_type]->content_module[$current_module]->isDummy()) { + if ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_READ)) { + $output .= LinkButton::create( + _('Starten'), + URLHelper::getURL($this->cms_link, [ + 'client_id' => $connected_cms[$this->cms_type]->getClientId(), + 'cms_select' => $this->cms_type, + // 'sess_id' => $connected_cms[$this->cms_type]->user->getSessionId(), + 'ref_id' => $connected_cms[$this->cms_type]->content_module[$current_module]->getId(), + 'type' => $connected_cms[$this->cms_type]->content_module[$current_module]->getModuleType(), + 'target' => 'start', + ]), + [ + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + ] + ); + $output .= " "; + } + if ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_WRITE)) { + $output .= LinkButton::create( + _('Bearbeiten'), + URLHelper::getURL($this->cms_link, [ + 'client_id' => $connected_cms[$this->cms_type]->getClientId(), + 'cms_select' => $this->cms_type, + // 'sess_id' => $connected_cms[$this->cms_type]->user->getSessionId(), + 'ref_id' => $connected_cms[$this->cms_type]->content_module[$current_module]->getId(), + 'type' => $connected_cms[$this->cms_type]->content_module[$current_module]->getModuleType(), + 'target' => 'edit', + ]), + [ + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + ] + ); + $output .= " "; + } + } + + return $output; + } + + /** + * get admin module links + * + * returns links add or remove a module from course + * @return string returns html-code + */ + public function getAdminModuleLinks() + { + global $connected_cms, $view, $search_key, $cms_select, $current_module; + + $output = "
\n"; + $output .= CSRFProtection::tokenTag(); + $output .= "\n"; + $output .= "\n"; + $output .= "\n"; + $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; + $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; + $output .= "cms_type) . "\">\n"; + + if ($connected_cms[$this->cms_type]->content_module[$current_module]->isConnected()) { + $output .= " " . Button::create(_('Entfernen'), 'remove'); + } elseif ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_WRITE)) { + $output .= "
"; + $output .= _("Mit Schreibrechten für alle Lehrenden/Tutoren und Tutorinnen dieser Veranstaltung") . "
"; + $output .= ""; + $output .= _("Mit Schreibrechten für alle Teilnehmenden dieser Veranstaltung") . "
"; + $output .= Button::create(_('Hinzufügen'), 'add') . "
"; + } else { + $output .= " " . Button::create(_('Hinzufügen'), 'add'); + } + $output .= "
"; + + return $output; + } + + /** + * get new module link + * + * returns link to create a new module if allowed + * @return string|false returns html-code or false + */ + public function getNewModuleLink() + { + global $connected_cms, $auth; + $output = "\n"; + if (Request::get("module_type_" . $this->cms_type)) { + if (!$connected_cms[$this->cms_type]->user->category) { + $connected_cms[$this->cms_type]->user->newUserCategory(); + if ($connected_cms[$this->cms_type]->user->category == false) { + return $output; + } + } + $output = " "; + $output .= LinkButton::create( + _('Neu anlegen'), + URLHelper::getURL($this->cms_link, [ + 'client_id' => $connected_cms[$this->cms_type]->getClientId(), + 'cms_select' => $this->cms_type, +// 'sess_id' => $connected_cms[$this->cms_type]->user->getSessionId(), + 'ref_id' => $connected_cms[$this->cms_type]->user->category, + 'type' => Request::option("module_type_" . $this->cms_type), + 'target' => 'new', + ]), + [ + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + ] + ); + } + $user_crs_role = $connected_cms[$this->cms_type]->crs_roles[$auth->auth['perm']]; + if ($user_crs_role === 'admin') { + return $output; + } + + return false; + } + + /** + * get start page link + * + * returns link to ilias start-page + * @access public + * @return string returns url or false + */ + function getStartpageLink() + { + global $connected_cms; + + if ($connected_cms[$this->cms_type]->user->isConnected()) { + $output = $this->cms_link . "?" + . "client_id=" . $connected_cms[$this->cms_type]->getClientId() + . "&cms_select=" . $this->cms_type + . "&target=login"; + } + return $output; + } +} +?> diff --git a/lib/elearning/Ilias3ConnectedPermissions.class.php b/lib/elearning/Ilias3ConnectedPermissions.class.php deleted file mode 100644 index 5ac5f59..0000000 --- a/lib/elearning/Ilias3ConnectedPermissions.class.php +++ /dev/null @@ -1,256 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module Ilias3ConnectedPermission -* @package ELearning-Interface -*/ -class Ilias3ConnectedPermissions extends ConnectedPermissions -{ - var $operations; - var $allowed_operations; - var $tree_allowed_operations; - - var $USER_OPERATIONS; - var $AUTHOR_OPERATIONS; - - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms) - { - global $connected_cms; - - parent::__construct($cms); - $this->readData(); - - if ($connected_cms[$this->cms_type]->user->isConnected()) - { - $roles = $this->getUserRoles(); - $connected_cms[$this->cms_type]->user->setRoles( $roles ); - } - $this->USER_OPERATIONS = [OPERATION_VISIBLE, OPERATION_READ]; -// $this->AUTHOR_OPERATIONS = array(OPERATION_VISIBLE, OPERATION_READ, OPERATION_CREATE_LM, OPERATION_CREATE_TEST, OPERATION_CREATE_QUESTIONS, OPERATION_CREATE_FILE); - } - - /** - * read data - * - * reads acces control data from database - * @access public - */ - function readData() - { - global $connected_cms; - - $this->operations = $connected_cms[$this->cms_type]->soap_client->getOperations(); - } - - /** - * check user permissions - * - * checks user permissions for connected course and changes setting if necessary - * @access public - * @param string $course_id course-id - * @return boolean returns false on error - */ - function checkUserPermissions($course_id) - { - global $connected_cms, $messages; - - if (!$course_id) { - return false; - } - if (!$connected_cms[$this->cms_type]->user->getId()) { - return false; - } - - // get course role folder and local roles - $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($course_id); - $active_role = ""; - $proper_role = ""; - $user_crs_role = $connected_cms[$this->cms_type]->crs_roles[$GLOBALS['perm']->get_studip_perm(Context::getId())]; - if (is_array($local_roles)) { - foreach ($local_roles as $key => $role_data) { // check only if local role is il_crs_member, -tutor or -admin - if (mb_strpos($role_data["title"], "_crs_") !== false) { - if (in_array($role_data["obj_id"], $connected_cms[$this->cms_type]->user->getRoles())) { - $active_role = $role_data["obj_id"]; - } - if (mb_strpos($role_data["title"], $user_crs_role) > 0) { - $proper_role = $role_data["obj_id"]; - } - } - } - } - - // is user already course-member? otherwise add member with proper role - $is_member = $connected_cms[$this->cms_type]->soap_client->isMember( $connected_cms[$this->cms_type]->user->getId(), $course_id); - if (!$is_member) { - $member_data["usr_id"] = $connected_cms[$this->cms_type]->user->getId(); - $member_data["ref_id"] = $course_id; - $member_data["status"] = CRS_NO_NOTIFICATION; - $type = ""; - switch ($user_crs_role) { - case "admin": - $member_data["role"] = CRS_ADMIN_ROLE; - $type = "Admin"; - break; - case "tutor": - $member_data["role"] = CRS_TUTOR_ROLE; - $type = "Tutor"; - break; - case "member": - $member_data["role"] = CRS_MEMBER_ROLE; - $type = "Member"; - break; - default: - } - $member_data["passed"] = CRS_PASSED_VALUE; - if ($type != "") - { - $connected_cms[$this->cms_type]->soap_client->addMember( $connected_cms[$this->cms_type]->user->getId(), $type, $course_id ); - if ($GLOBALS["debug"] == true) - echo "addMember"; - } - } - - // check if user has proper local role - // if not, change it - if ($active_role != $proper_role) - { - if ($active_role != "") - { - $connected_cms[$this->cms_type]->soap_client->deleteUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $active_role); - if ($GLOBALS["debug"] == true) - echo "Role $active_role deleted."; - } - - if ($proper_role != "") - { - $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $proper_role); - if ($GLOBALS["debug"] == true) - echo "Role $proper_role added."; - } - - } - if (!$this->getContentModulePerms($course_id)) { - $messages["info"] .= _("Für den zugeordneten ILIAS-Kurs konnten keine Berechtigungen ermittelt werden.") . "
"; - } - - return true; - } - - /** - * get user roles - * - * returns roles for current user - * @access public - * @return array role-ids - */ - function getUserRoles() - { - global $connected_cms; - - return $connected_cms[$this->cms_type]->soap_client->getUserRoles($connected_cms[$this->cms_type]->user->getId()); - } - - /** - * get permissions for content module - * - * returns allowed operations for given user and module - * @access public - * @param string $module_id module-id - * @return boolean returns false on error - */ - function getContentModulePerms($module_id) - { - global $connected_cms, $current_module; - - if (is_array($connected_cms[$this->cms_type]->content_module[$current_module]->allowed_operations)) - return true; - $this->allowed_operations = []; - $this->tree_allowed_operations = $connected_cms[$this->cms_type]->soap_client->getObjectTreeOperations( - $module_id, - $connected_cms[$this->cms_type]->user->getId() - ); - if (!is_array($this->tree_allowed_operations)) { - return false; - } - - $no_permission = false; - if (isset($current_module)) { //TODO: fixes Warning:Creating default object from empty value - possible side effects - if ((! in_array($this->operations[OPERATION_READ], $this->tree_allowed_operations)) OR (! in_array($this->operations[OPERATION_VISIBLE], $this->tree_allowed_operations))) - $no_permission = true; - - if ($no_permission == false) - $connected_cms[$this->cms_type]->content_module[$current_module]->allowed_operations = $this->tree_allowed_operations; - else - $connected_cms[$this->cms_type]->content_module[$current_module]->allowed_operations = false; - } - return true; - } - - /** - * get operation - * - * returns id for given operation-string - * @access public - * @param string $operation operation - * @return integer operation-id - */ - function getOperation($operation) - { - return $this->operations[$operation]; - } - - /** - * get operation-ids - * - * returns an array of operation-ids - * @param array $operation operation - * @return array|false operation-ids - */ - public function getOperationArray($operation) - { - if (!is_array($operation)) { - return false; - } - - return array_map( - function ($operation_name) { - return $this->operations[$operation_name]; - }, - $operation - ); - } -} diff --git a/lib/elearning/Ilias3ConnectedPermissions.php b/lib/elearning/Ilias3ConnectedPermissions.php new file mode 100644 index 0000000..5ac5f59 --- /dev/null +++ b/lib/elearning/Ilias3ConnectedPermissions.php @@ -0,0 +1,256 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module Ilias3ConnectedPermission +* @package ELearning-Interface +*/ +class Ilias3ConnectedPermissions extends ConnectedPermissions +{ + var $operations; + var $allowed_operations; + var $tree_allowed_operations; + + var $USER_OPERATIONS; + var $AUTHOR_OPERATIONS; + + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms) + { + global $connected_cms; + + parent::__construct($cms); + $this->readData(); + + if ($connected_cms[$this->cms_type]->user->isConnected()) + { + $roles = $this->getUserRoles(); + $connected_cms[$this->cms_type]->user->setRoles( $roles ); + } + $this->USER_OPERATIONS = [OPERATION_VISIBLE, OPERATION_READ]; +// $this->AUTHOR_OPERATIONS = array(OPERATION_VISIBLE, OPERATION_READ, OPERATION_CREATE_LM, OPERATION_CREATE_TEST, OPERATION_CREATE_QUESTIONS, OPERATION_CREATE_FILE); + } + + /** + * read data + * + * reads acces control data from database + * @access public + */ + function readData() + { + global $connected_cms; + + $this->operations = $connected_cms[$this->cms_type]->soap_client->getOperations(); + } + + /** + * check user permissions + * + * checks user permissions for connected course and changes setting if necessary + * @access public + * @param string $course_id course-id + * @return boolean returns false on error + */ + function checkUserPermissions($course_id) + { + global $connected_cms, $messages; + + if (!$course_id) { + return false; + } + if (!$connected_cms[$this->cms_type]->user->getId()) { + return false; + } + + // get course role folder and local roles + $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($course_id); + $active_role = ""; + $proper_role = ""; + $user_crs_role = $connected_cms[$this->cms_type]->crs_roles[$GLOBALS['perm']->get_studip_perm(Context::getId())]; + if (is_array($local_roles)) { + foreach ($local_roles as $key => $role_data) { // check only if local role is il_crs_member, -tutor or -admin + if (mb_strpos($role_data["title"], "_crs_") !== false) { + if (in_array($role_data["obj_id"], $connected_cms[$this->cms_type]->user->getRoles())) { + $active_role = $role_data["obj_id"]; + } + if (mb_strpos($role_data["title"], $user_crs_role) > 0) { + $proper_role = $role_data["obj_id"]; + } + } + } + } + + // is user already course-member? otherwise add member with proper role + $is_member = $connected_cms[$this->cms_type]->soap_client->isMember( $connected_cms[$this->cms_type]->user->getId(), $course_id); + if (!$is_member) { + $member_data["usr_id"] = $connected_cms[$this->cms_type]->user->getId(); + $member_data["ref_id"] = $course_id; + $member_data["status"] = CRS_NO_NOTIFICATION; + $type = ""; + switch ($user_crs_role) { + case "admin": + $member_data["role"] = CRS_ADMIN_ROLE; + $type = "Admin"; + break; + case "tutor": + $member_data["role"] = CRS_TUTOR_ROLE; + $type = "Tutor"; + break; + case "member": + $member_data["role"] = CRS_MEMBER_ROLE; + $type = "Member"; + break; + default: + } + $member_data["passed"] = CRS_PASSED_VALUE; + if ($type != "") + { + $connected_cms[$this->cms_type]->soap_client->addMember( $connected_cms[$this->cms_type]->user->getId(), $type, $course_id ); + if ($GLOBALS["debug"] == true) + echo "addMember"; + } + } + + // check if user has proper local role + // if not, change it + if ($active_role != $proper_role) + { + if ($active_role != "") + { + $connected_cms[$this->cms_type]->soap_client->deleteUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $active_role); + if ($GLOBALS["debug"] == true) + echo "Role $active_role deleted."; + } + + if ($proper_role != "") + { + $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $proper_role); + if ($GLOBALS["debug"] == true) + echo "Role $proper_role added."; + } + + } + if (!$this->getContentModulePerms($course_id)) { + $messages["info"] .= _("Für den zugeordneten ILIAS-Kurs konnten keine Berechtigungen ermittelt werden.") . "
"; + } + + return true; + } + + /** + * get user roles + * + * returns roles for current user + * @access public + * @return array role-ids + */ + function getUserRoles() + { + global $connected_cms; + + return $connected_cms[$this->cms_type]->soap_client->getUserRoles($connected_cms[$this->cms_type]->user->getId()); + } + + /** + * get permissions for content module + * + * returns allowed operations for given user and module + * @access public + * @param string $module_id module-id + * @return boolean returns false on error + */ + function getContentModulePerms($module_id) + { + global $connected_cms, $current_module; + + if (is_array($connected_cms[$this->cms_type]->content_module[$current_module]->allowed_operations)) + return true; + $this->allowed_operations = []; + $this->tree_allowed_operations = $connected_cms[$this->cms_type]->soap_client->getObjectTreeOperations( + $module_id, + $connected_cms[$this->cms_type]->user->getId() + ); + if (!is_array($this->tree_allowed_operations)) { + return false; + } + + $no_permission = false; + if (isset($current_module)) { //TODO: fixes Warning:Creating default object from empty value - possible side effects + if ((! in_array($this->operations[OPERATION_READ], $this->tree_allowed_operations)) OR (! in_array($this->operations[OPERATION_VISIBLE], $this->tree_allowed_operations))) + $no_permission = true; + + if ($no_permission == false) + $connected_cms[$this->cms_type]->content_module[$current_module]->allowed_operations = $this->tree_allowed_operations; + else + $connected_cms[$this->cms_type]->content_module[$current_module]->allowed_operations = false; + } + return true; + } + + /** + * get operation + * + * returns id for given operation-string + * @access public + * @param string $operation operation + * @return integer operation-id + */ + function getOperation($operation) + { + return $this->operations[$operation]; + } + + /** + * get operation-ids + * + * returns an array of operation-ids + * @param array $operation operation + * @return array|false operation-ids + */ + public function getOperationArray($operation) + { + if (!is_array($operation)) { + return false; + } + + return array_map( + function ($operation_name) { + return $this->operations[$operation_name]; + }, + $operation + ); + } +} diff --git a/lib/elearning/Ilias3ConnectedUser.class.php b/lib/elearning/Ilias3ConnectedUser.class.php deleted file mode 100644 index ef98529..0000000 --- a/lib/elearning/Ilias3ConnectedUser.class.php +++ /dev/null @@ -1,345 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module Ilias3ConnectedUser -* @package ELearning-Interface -*/ -class Ilias3ConnectedUser extends ConnectedUser -{ - var $roles; - var $user_sid; - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms, $user_id = false) - { - global $connected_cms, $perm; - - parent::__construct($cms, $user_id); - // create account automatically if it doesn't exist - if (! $this->isConnected() AND ($connected_cms[$this->cms_type]->USER_AUTO_CREATE == true)) - { - $this->setPassword(md5(uniqid("4dfmjsnll"))); - $this->newUser(true); - $this->readData(); - } - $this->roles = [$connected_cms[$cms]->roles[$perm->get_perm($this->studip_id)]]; - } - - function readData() - { - global $connected_cms; - parent::readData(); - if($this->is_connected){ - $user_id = $connected_cms[$this->cms_type]->soap_client->lookupUser($this->login); - if (!$user_id) { - //do not delete in case of error - if($user_id !== false) { - $query = "DELETE FROM auth_extern WHERE studip_user_id = ? LIMIT 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->studip_id]); - } - $this->id = ''; - $this->login = ''; - $this->external_password = ''; - $this->category = ''; - $this->type = ''; - $this->is_connected = false; - } elseif($this->category != ''){ - $cat = $connected_cms[$this->cms_type]->soap_client->checkReferenceById($this->category); - if(!$cat){ - $query = "UPDATE auth_extern SET external_user_category = '' WHERE studip_user_id = ? LIMIT 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->studip_id]); - - $this->category = ''; - } - } - } - return $this->is_connected; - } - - /** - * get login-data - * - * gets login-data from database - * @access public - * @param string $username username - * @return boolean returns false, if no data was found - */ - function getLoginData($username) - { - global $connected_cms; - - if (!$username) { - return false; - } - $user_id = $connected_cms[$this->cms_type]->soap_client->lookupUser($username); - - if ($user_id == false) - return false; - - $user_data = $connected_cms[$this->cms_type]->soap_client->getUser($user_id); - - if ($user_data == false) - return false; - - $this->id = $user_data["usr_id"]; - $this->login = $user_data["login"]; - $this->external_password = $user_data["passwd"]; - return true; - } - - /** - * get crypted password - * - * returns ILIAS 3 password - * @access public - * @param string $password password - * @return string password - */ - function getCryptedPassword($password) - { - return md5($password); - } - - /** - * set roles - * - * sets roles - * @access public - * @param array $role_array role-array - */ - function setRoles($role_array) - { - $this->roles = $role_array; - } - - /** - * get roles - * - * returns roles - * @access public - * @return array roles - */ - function getRoles() - { - return $this->roles; - } - - /** - * create new user category - * - * create new user category - * @access public - * @return boolean returns false on error - */ - function newUserCategory() - { - global $connected_cms, $messages; - - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - - // data for user-category in ILIAS 3 - $object_data["title"] = sprintf(_("Eigene Daten von %s (%s)."), $this->getName(), $this->getId()); - $object_data["description"] = sprintf(_("Hier befinden sich die persönlichen Lernmodule des Benutzers %s."), $this->getName()); - $object_data["type"] = "cat"; - $object_data["owner"] = $this->getId(); - - $cat = $connected_cms[$this->cms_type]->soap_client->getReferenceByTitle($object_data["title"]); - if ($cat != false && $connected_cms[$this->cms_type]->soap_client->checkReferenceById($cat) ) - { - $messages["info"] .= sprintf(_("Ihre persönliche Kategorie wurde bereits angelegt."), $this->login) . "
\n"; - $this->category = $cat; - } - else - { - $this->category = $connected_cms[$this->cms_type]->soap_client->addObject($object_data, $connected_cms[$this->cms_type]->main_category_node_id); - } - if ($this->category != false) - parent::setConnection( $this->getUserType() ); - else - { - echo "CATEGORY_ERROR".$connected_cms[$this->cms_type]->main_category_node_id ."-"; - return false; - } - // data for personal user-role in ILIAS 3 - $role_data["title"] = "studip_usr" . $this->getId() . "_cat" . $this->category; - $role_data["description"] = sprintf(_("User-Rolle von %s. Diese Rolle wurde von Stud.IP generiert."), $this->getName()); - $role_id = $connected_cms[$this->cms_type]->soap_client->getObjectByTitle($role_data["title"], "role"); - if ($role_id != false) - $messages["info"] .= sprintf(_("Ihre persönliche Userrolle wurde bereits angelegt."), $this->login) . "
\n"; - else - $role_id = $connected_cms[$this->cms_type]->soap_client->addRoleFromTemplate($role_data, $this->category, $connected_cms[$this->cms_type]->user_role_template_id); - $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry($this->getId(), $role_id); - // delete permissions for all global roles for this category - foreach ($connected_cms[$this->cms_type]->global_roles as $key => $role) - $connected_cms[$this->cms_type]->soap_client->revokePermissions($role, $this->category); - return true; - } - - /** - * new user - * - * save new user - * @access public - * @return boolean returns false on error - */ - function newUser($ignore_encrypt_passwords = false) - { - global $connected_cms, $auth, $messages; - - if ($this->getLoginData($this->login)) - { - $messages["error"] .= sprintf(_("Es existiert bereits ein Account mit dem Benutzernamen \"%s\"."), $this->login) . "
\n"; - return false; - } - - // data for user-account in ILIAS 3 - $user_data["login"] = $this->login; - $user_data["passwd"] = $this->external_password; - $user_data["firstname"] = $this->firstname; - $user_data["lastname"] = $this->lastname; - $user_data["title"] = $this->title; - $user_data["gender"] = $this->gender; - $user_data["email"] = $this->email; - $user_data["street"] = $this->street; - $user_data["phone_home"] = $this->phone_home; - $user_data["time_limit_unlimited"] = 1; - $user_data["active"] = 1; - $user_data["approve_date"] = date('Y-m-d H:i:s'); - $user_data["accepted_agreement"] = true; - - if ($connected_cms[$this->cms_type]->user_style != "") - $user_data["user_style"] = $connected_cms[$this->cms_type]->user_style; - if ($connected_cms[$this->cms_type]->user_skin != "") - $user_data["user_skin"] = $connected_cms[$this->cms_type]->user_skin; - - $role_id = $connected_cms[$this->cms_type]->roles[$auth->auth["perm"]]; - - $user_id = $connected_cms[$this->cms_type]->soap_client->addUser($user_data, $role_id); - - if ($user_id != false) - { - $this->id = $user_id; - -// $connected_cms[$this->cms_type]->soap_client->updatePassword($user_id, $user_data["passwd"]); - -// $this->newUserCategory(); - - $this->setConnection(USER_TYPE_CREATED, $ignore_encrypt_passwords); - return true; - } - echo $connected_cms[$this->cms_type]->soap_client->getError(); - return false; - } - - /** - * update user-account - */ - public function updateUser() - { - } - - /** - * delete user - * - * delete user-account - * @access public - * @return boolean returns false on error - */ - function deleteUser() - { - global $connected_cms; - $ret = null; - $connected_cms[$this->cms_type]->soap_client->user_type == "admin"; - $connected_cms[$this->cms_type]->soap_client->caching_active = false; - if($this->category){ - $ret['cat_deleted'] = $connected_cms[$this->cms_type]->soap_client->deleteObject($this->category); - } - if($this->type == 0 && $this->id){ - $ret['iliasuser_deleted'] = $connected_cms[$this->cms_type]->soap_client->deleteUser($this->id); - } - $query = "DELETE FROM auth_extern WHERE studip_user_id = ? LIMIT 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->studip_id]); - - $ret['auth_extern_deleted'] = $statement->rowCount(); - return $ret; - } - - /** - * set connection - * - * set user connection - * @access public - * @param string user_type user-type - */ - public function setConnection($user_type, $ignore_encrypt_passwords = false) - { - global $connected_cms; - - if (!$ignore_encrypt_passwords && $connected_cms[$this->cms_type]->encrypt_passwords === "md5") - { -// echo "PASSWORD-ENCRYPTION"; - $this->external_password = $this->getCryptedPassword( $this->external_password ); - } - - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - parent::setConnection($user_type); - } - - /** - * get sid - * - * returns soap-sid - * @access public - * @return string soap-sid - */ - function getSID() - { - global $connected_cms; - - $caching_status = $connected_cms[$this->cms_type]->soap_client->getCachingStatus(); - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - - $connected_cms[$this->cms_type]->soap_client->setUserType("user"); - $this->user_sid = $connected_cms[$this->cms_type]->soap_client->login(); - - $connected_cms[$this->cms_type]->soap_client->setCachingStatus($caching_status); - $connected_cms[$this->cms_type]->soap_client->setUserType("admin"); - return $this->user_sid; - } - - /** - * get session-id - * - * returns soap-session-id - * @access public - * @return string soap-session-id - */ - function getSessionId() - { - $sid = $this->getSID(); - if ($sid == false) - return false; - $arr = explode("::", $sid); - return $arr[0]; - } -} -?> diff --git a/lib/elearning/Ilias3ConnectedUser.php b/lib/elearning/Ilias3ConnectedUser.php new file mode 100644 index 0000000..ef98529 --- /dev/null +++ b/lib/elearning/Ilias3ConnectedUser.php @@ -0,0 +1,345 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module Ilias3ConnectedUser +* @package ELearning-Interface +*/ +class Ilias3ConnectedUser extends ConnectedUser +{ + var $roles; + var $user_sid; + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms, $user_id = false) + { + global $connected_cms, $perm; + + parent::__construct($cms, $user_id); + // create account automatically if it doesn't exist + if (! $this->isConnected() AND ($connected_cms[$this->cms_type]->USER_AUTO_CREATE == true)) + { + $this->setPassword(md5(uniqid("4dfmjsnll"))); + $this->newUser(true); + $this->readData(); + } + $this->roles = [$connected_cms[$cms]->roles[$perm->get_perm($this->studip_id)]]; + } + + function readData() + { + global $connected_cms; + parent::readData(); + if($this->is_connected){ + $user_id = $connected_cms[$this->cms_type]->soap_client->lookupUser($this->login); + if (!$user_id) { + //do not delete in case of error + if($user_id !== false) { + $query = "DELETE FROM auth_extern WHERE studip_user_id = ? LIMIT 1"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->studip_id]); + } + $this->id = ''; + $this->login = ''; + $this->external_password = ''; + $this->category = ''; + $this->type = ''; + $this->is_connected = false; + } elseif($this->category != ''){ + $cat = $connected_cms[$this->cms_type]->soap_client->checkReferenceById($this->category); + if(!$cat){ + $query = "UPDATE auth_extern SET external_user_category = '' WHERE studip_user_id = ? LIMIT 1"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->studip_id]); + + $this->category = ''; + } + } + } + return $this->is_connected; + } + + /** + * get login-data + * + * gets login-data from database + * @access public + * @param string $username username + * @return boolean returns false, if no data was found + */ + function getLoginData($username) + { + global $connected_cms; + + if (!$username) { + return false; + } + $user_id = $connected_cms[$this->cms_type]->soap_client->lookupUser($username); + + if ($user_id == false) + return false; + + $user_data = $connected_cms[$this->cms_type]->soap_client->getUser($user_id); + + if ($user_data == false) + return false; + + $this->id = $user_data["usr_id"]; + $this->login = $user_data["login"]; + $this->external_password = $user_data["passwd"]; + return true; + } + + /** + * get crypted password + * + * returns ILIAS 3 password + * @access public + * @param string $password password + * @return string password + */ + function getCryptedPassword($password) + { + return md5($password); + } + + /** + * set roles + * + * sets roles + * @access public + * @param array $role_array role-array + */ + function setRoles($role_array) + { + $this->roles = $role_array; + } + + /** + * get roles + * + * returns roles + * @access public + * @return array roles + */ + function getRoles() + { + return $this->roles; + } + + /** + * create new user category + * + * create new user category + * @access public + * @return boolean returns false on error + */ + function newUserCategory() + { + global $connected_cms, $messages; + + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + + // data for user-category in ILIAS 3 + $object_data["title"] = sprintf(_("Eigene Daten von %s (%s)."), $this->getName(), $this->getId()); + $object_data["description"] = sprintf(_("Hier befinden sich die persönlichen Lernmodule des Benutzers %s."), $this->getName()); + $object_data["type"] = "cat"; + $object_data["owner"] = $this->getId(); + + $cat = $connected_cms[$this->cms_type]->soap_client->getReferenceByTitle($object_data["title"]); + if ($cat != false && $connected_cms[$this->cms_type]->soap_client->checkReferenceById($cat) ) + { + $messages["info"] .= sprintf(_("Ihre persönliche Kategorie wurde bereits angelegt."), $this->login) . "
\n"; + $this->category = $cat; + } + else + { + $this->category = $connected_cms[$this->cms_type]->soap_client->addObject($object_data, $connected_cms[$this->cms_type]->main_category_node_id); + } + if ($this->category != false) + parent::setConnection( $this->getUserType() ); + else + { + echo "CATEGORY_ERROR".$connected_cms[$this->cms_type]->main_category_node_id ."-"; + return false; + } + // data for personal user-role in ILIAS 3 + $role_data["title"] = "studip_usr" . $this->getId() . "_cat" . $this->category; + $role_data["description"] = sprintf(_("User-Rolle von %s. Diese Rolle wurde von Stud.IP generiert."), $this->getName()); + $role_id = $connected_cms[$this->cms_type]->soap_client->getObjectByTitle($role_data["title"], "role"); + if ($role_id != false) + $messages["info"] .= sprintf(_("Ihre persönliche Userrolle wurde bereits angelegt."), $this->login) . "
\n"; + else + $role_id = $connected_cms[$this->cms_type]->soap_client->addRoleFromTemplate($role_data, $this->category, $connected_cms[$this->cms_type]->user_role_template_id); + $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry($this->getId(), $role_id); + // delete permissions for all global roles for this category + foreach ($connected_cms[$this->cms_type]->global_roles as $key => $role) + $connected_cms[$this->cms_type]->soap_client->revokePermissions($role, $this->category); + return true; + } + + /** + * new user + * + * save new user + * @access public + * @return boolean returns false on error + */ + function newUser($ignore_encrypt_passwords = false) + { + global $connected_cms, $auth, $messages; + + if ($this->getLoginData($this->login)) + { + $messages["error"] .= sprintf(_("Es existiert bereits ein Account mit dem Benutzernamen \"%s\"."), $this->login) . "
\n"; + return false; + } + + // data for user-account in ILIAS 3 + $user_data["login"] = $this->login; + $user_data["passwd"] = $this->external_password; + $user_data["firstname"] = $this->firstname; + $user_data["lastname"] = $this->lastname; + $user_data["title"] = $this->title; + $user_data["gender"] = $this->gender; + $user_data["email"] = $this->email; + $user_data["street"] = $this->street; + $user_data["phone_home"] = $this->phone_home; + $user_data["time_limit_unlimited"] = 1; + $user_data["active"] = 1; + $user_data["approve_date"] = date('Y-m-d H:i:s'); + $user_data["accepted_agreement"] = true; + + if ($connected_cms[$this->cms_type]->user_style != "") + $user_data["user_style"] = $connected_cms[$this->cms_type]->user_style; + if ($connected_cms[$this->cms_type]->user_skin != "") + $user_data["user_skin"] = $connected_cms[$this->cms_type]->user_skin; + + $role_id = $connected_cms[$this->cms_type]->roles[$auth->auth["perm"]]; + + $user_id = $connected_cms[$this->cms_type]->soap_client->addUser($user_data, $role_id); + + if ($user_id != false) + { + $this->id = $user_id; + +// $connected_cms[$this->cms_type]->soap_client->updatePassword($user_id, $user_data["passwd"]); + +// $this->newUserCategory(); + + $this->setConnection(USER_TYPE_CREATED, $ignore_encrypt_passwords); + return true; + } + echo $connected_cms[$this->cms_type]->soap_client->getError(); + return false; + } + + /** + * update user-account + */ + public function updateUser() + { + } + + /** + * delete user + * + * delete user-account + * @access public + * @return boolean returns false on error + */ + function deleteUser() + { + global $connected_cms; + $ret = null; + $connected_cms[$this->cms_type]->soap_client->user_type == "admin"; + $connected_cms[$this->cms_type]->soap_client->caching_active = false; + if($this->category){ + $ret['cat_deleted'] = $connected_cms[$this->cms_type]->soap_client->deleteObject($this->category); + } + if($this->type == 0 && $this->id){ + $ret['iliasuser_deleted'] = $connected_cms[$this->cms_type]->soap_client->deleteUser($this->id); + } + $query = "DELETE FROM auth_extern WHERE studip_user_id = ? LIMIT 1"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->studip_id]); + + $ret['auth_extern_deleted'] = $statement->rowCount(); + return $ret; + } + + /** + * set connection + * + * set user connection + * @access public + * @param string user_type user-type + */ + public function setConnection($user_type, $ignore_encrypt_passwords = false) + { + global $connected_cms; + + if (!$ignore_encrypt_passwords && $connected_cms[$this->cms_type]->encrypt_passwords === "md5") + { +// echo "PASSWORD-ENCRYPTION"; + $this->external_password = $this->getCryptedPassword( $this->external_password ); + } + + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + parent::setConnection($user_type); + } + + /** + * get sid + * + * returns soap-sid + * @access public + * @return string soap-sid + */ + function getSID() + { + global $connected_cms; + + $caching_status = $connected_cms[$this->cms_type]->soap_client->getCachingStatus(); + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + + $connected_cms[$this->cms_type]->soap_client->setUserType("user"); + $this->user_sid = $connected_cms[$this->cms_type]->soap_client->login(); + + $connected_cms[$this->cms_type]->soap_client->setCachingStatus($caching_status); + $connected_cms[$this->cms_type]->soap_client->setUserType("admin"); + return $this->user_sid; + } + + /** + * get session-id + * + * returns soap-session-id + * @access public + * @return string soap-session-id + */ + function getSessionId() + { + $sid = $this->getSID(); + if ($sid == false) + return false; + $arr = explode("::", $sid); + return $arr[0]; + } +} +?> diff --git a/lib/elearning/Ilias3ContentModule.class.php b/lib/elearning/Ilias3ContentModule.class.php deleted file mode 100644 index 3067575..0000000 --- a/lib/elearning/Ilias3ContentModule.class.php +++ /dev/null @@ -1,292 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module Ilias3ContentModule -* @package ELearning-Interface -*/ -class Ilias3ContentModule extends ContentModule -{ - var $object_id; - - /** - * constructor - * - * init class. - * @access public - * @param string $module_id module-id - * @param string $module_type module-type - * @param string $cms_type system-type - */ - function __construct($module_id, $module_type, $cms_type) - { - parent::__construct($module_id, $module_type, $cms_type); - if ($module_id != "") - $this->readData(); - } - - /** - * read data - * - * get module data from database. - * @access public - */ - function readData() - { - global $connected_cms; - - $object_data = $connected_cms[$this->cms_type]->soap_client->getObjectByReference($this->id, $connected_cms[$this->cms_type]->user->getId()); - if ( (! ($object_data == false)) AND ($connected_cms[$this->cms_type]->types[$object_data["type"]] != "") ) - { - // If User has no external Account, show module and link to user-assignment - if (! $connected_cms[$this->cms_type]->user->isConnected()) - $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ] ); - - //set module data - $this->setObjectId($object_data["obj_id"]); - $this->setTitle($object_data["title"]); - $this->setDescription($object_data["description"]); - if ($object_data["owner"] != "") - { - $user_data = $connected_cms[$this->cms_type]->soap_client->getUser($object_data["owner"]); - $user_name = trim($user_data["title"] . " " . $user_data["firstname"] . " " . $user_data["lastname"]); - $this->setAuthors($user_name); - } - $this->setPermissions($object_data["accessInfo"], $object_data["operations"]); - } - else - { - // If module doesn't exist, show errormessage - $this->createDummyForErrormessage("not found"); - $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_DELETE] ); - } - } - - /** - * set permissions - * - * sets permissions for content-module - * @access public - * @param string $acces_info access-status - * @param array $operations array of operations - * @return boolean successful - */ - function setPermissions($access_info, $operations) - { - global $connected_cms; - - switch ($access_info) { - case "granted": - $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations); - break; - case "no_permission": - $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations); - $this->setDescription("" . _("Sie haben keine Leseberechtigung für dieses Modul.") . ""); - return false; - case "missing_precondition": - $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations ); - $this->setDescription("" . _("Sie haben zur Zeit noch keinen Zugriff auf deses Modul (fehlende Vorbedingungen).") . ""); - break; - case "no_object_access": - $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations ); - $this->setDescription("" . _("Dieses Modul ist momentan offline oder durch Payment-Regeln gesperrt.") . ""); - break; - case "no_parent_access": - $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations ); - $this->setDescription("" . _("Sie haben keinen Zugriff auf die übergeordneten Objekte dieses Moduls.") . ""); - return false; - case "object_deleted": - $this->createDummyForErrormessage("deleted"); - return false; - } - if ($connected_cms[$this->cms_type]->isAuthNecessary() && $connected_cms[$this->cms_type]->user->isConnected()) { - // If User has no permission, don't show module data - if (!$this->isAllowed(OPERATION_VISIBLE) && !$this->isDummy() && $connected_cms[$this->cms_type]->user->isConnected()) { - $this->createDummyForErrormessage("no permission"); - } - } - - return true; - } - - /** - * set connection - * - * sets connection with seminar - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function setConnection($seminar_id) - { - global $connected_cms, $messages; - - $write_permission = Request::option("write_permission"); - $write_permission_autor = Request::option("write_permission_autor"); - - $crs_id = ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); -// echo "SET?".$this->cms_type; - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - $connected_cms[$this->cms_type]->soap_client->clearCache(); - - // Check, ob Kurs in ILIAS gelöscht wurde - if (($crs_id != false) AND ($connected_cms[$this->cms_type]->soap_client->getObjectByReference($crs_id) == false)) - { - ObjectConnections::unsetConnection($seminar_id, $crs_id, "crs", $this->cms_type); -// echo "deleted: ".ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); -// echo "Der zugeordnete ILIAS-Kurs (ID $crs_id) existiert nicht mehr. Ein neuer Kurs wird angelegt."; - $messages["info"] .= _("Der zugeordnete ILIAS-Kurs (ID $crs_id) existiert nicht mehr. Ein neuer Kurs wird angelegt.") . "
"; - $crs_id = false; - } - - if ($crs_id == false) - { - - $lang_array = explode("_",Config::get()->DEFAULT_LANGUAGE); - $course_data["language"] = $lang_array[0]; - $course_data["title"] = "Stud.IP-Kurs " . Context::get()->Name; - $course_data["description"] = ""; - $ref_id = $connected_cms[$this->cms_type]->main_category_node_id; - $crs_id = $connected_cms[$this->cms_type]->soap_client->addCourse($course_data, $ref_id); - - if ($crs_id == false) - { - $messages["error"] .= _("Zuordnungs-Fehler: Kurs konnte nicht angelegt werden."); - return false; - } - ObjectConnections::setConnection($seminar_id, $crs_id, "crs", $this->cms_type); - - // Rollen zuordnen - $connected_cms[$this->cms_type]->permissions->CheckUserPermissions($crs_id); -// $messages["info"] .= "Neue Kurs-ID: $crs_id.
"; - } - - - $ref_id = $this->getId(); - $ref_id = $connected_cms[$this->cms_type]->soap_client->addReference($this->id, $crs_id); - $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($crs_id); - $member_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ]); - $admin_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_WRITE]); - foreach ($local_roles as $key => $role_data){ - // check only if local role is il_crs_member, -tutor or -admin - if (mb_strpos($role_data["title"], "il_crs_") === 0) { - if(mb_strpos($role_data["title"], 'il_crs_member') === 0){ - $operations = $write_permission_autor ? $admin_operations : $member_operations; - } else if(mb_strpos($role_data["title"], 'il_crs_tutor') === 0){ - $operations = $write_permission_autor || $write_permission ? $admin_operations : $member_operations; - } else { - continue; - } - $connected_cms[$this->cms_type]->soap_client->revokePermissions($role_data["obj_id"], $ref_id); - $connected_cms[$this->cms_type]->soap_client->grantPermissions($operations, $role_data["obj_id"], $ref_id); - } - } - if ($ref_id) - { - $this->setId($ref_id); - return parent::setConnection($seminar_id); - } - else - $messages["error"] .= _("Die Zuordnung konnte nicht gespeichert werden."); - return false; - } - - /** - * unset connection - * - * unsets connection with seminar - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function unsetConnection($seminar_id) - { - global $connected_cms, $messages; - - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - { - if ( $this->getObjectId() != false) - $connected_cms[$this->cms_type]->soap_client->deleteObject($this->getId()); - return parent::unsetConnection($seminar_id); - } - $messages["error"] .= _("Die Zuordnung konnte nicht entfernt werden."); - return false; - } - - /** - * set object id - * - * sets object id - * @access public - * @param string $module_object_id object id - */ - function setObjectId($module_object_id) - { - $this->object_id = $module_object_id; - } - - /** - * get object id - * - * returns object id - * @access public - * @return string object id - */ - function getObjectId() - { - return $this->object_id; - } - - /** - * set allowed operations - * - * sets allowed operations - * @access public - * @param array $operation_array operation-ids - */ - function setAllowedOperations( $operation_array ) - { - global $connected_cms; - - $this->allowed_operations = []; - foreach($operation_array as $key => $operation) - { -// echo "O$operation = I".$connected_cms[$this->cms_type]->permissions->getOperation[$operation]."
"; - $this->allowed_operations[] = $connected_cms[$this->cms_type]->permissions->getOperation[$operation]; - } - } - - /** - * get permission-status - * - * returns true, if operation is allowed - * @access public - * @param string $operation operation - * @return boolean allowed - */ - function isAllowed($operation) - { - global $connected_cms; - - if (is_array($this->allowed_operations)) - { - if (in_array($connected_cms[$this->cms_type]->permissions->getOperation($operation), $this->allowed_operations)) - return true; - else - return false; - } - else - return false; - } -} -?> diff --git a/lib/elearning/Ilias3ContentModule.php b/lib/elearning/Ilias3ContentModule.php new file mode 100644 index 0000000..3067575 --- /dev/null +++ b/lib/elearning/Ilias3ContentModule.php @@ -0,0 +1,292 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module Ilias3ContentModule +* @package ELearning-Interface +*/ +class Ilias3ContentModule extends ContentModule +{ + var $object_id; + + /** + * constructor + * + * init class. + * @access public + * @param string $module_id module-id + * @param string $module_type module-type + * @param string $cms_type system-type + */ + function __construct($module_id, $module_type, $cms_type) + { + parent::__construct($module_id, $module_type, $cms_type); + if ($module_id != "") + $this->readData(); + } + + /** + * read data + * + * get module data from database. + * @access public + */ + function readData() + { + global $connected_cms; + + $object_data = $connected_cms[$this->cms_type]->soap_client->getObjectByReference($this->id, $connected_cms[$this->cms_type]->user->getId()); + if ( (! ($object_data == false)) AND ($connected_cms[$this->cms_type]->types[$object_data["type"]] != "") ) + { + // If User has no external Account, show module and link to user-assignment + if (! $connected_cms[$this->cms_type]->user->isConnected()) + $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ] ); + + //set module data + $this->setObjectId($object_data["obj_id"]); + $this->setTitle($object_data["title"]); + $this->setDescription($object_data["description"]); + if ($object_data["owner"] != "") + { + $user_data = $connected_cms[$this->cms_type]->soap_client->getUser($object_data["owner"]); + $user_name = trim($user_data["title"] . " " . $user_data["firstname"] . " " . $user_data["lastname"]); + $this->setAuthors($user_name); + } + $this->setPermissions($object_data["accessInfo"], $object_data["operations"]); + } + else + { + // If module doesn't exist, show errormessage + $this->createDummyForErrormessage("not found"); + $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_DELETE] ); + } + } + + /** + * set permissions + * + * sets permissions for content-module + * @access public + * @param string $acces_info access-status + * @param array $operations array of operations + * @return boolean successful + */ + function setPermissions($access_info, $operations) + { + global $connected_cms; + + switch ($access_info) { + case "granted": + $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations); + break; + case "no_permission": + $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations); + $this->setDescription("" . _("Sie haben keine Leseberechtigung für dieses Modul.") . ""); + return false; + case "missing_precondition": + $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations ); + $this->setDescription("" . _("Sie haben zur Zeit noch keinen Zugriff auf deses Modul (fehlende Vorbedingungen).") . ""); + break; + case "no_object_access": + $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations ); + $this->setDescription("" . _("Dieses Modul ist momentan offline oder durch Payment-Regeln gesperrt.") . ""); + break; + case "no_parent_access": + $this->allowed_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray($operations ); + $this->setDescription("" . _("Sie haben keinen Zugriff auf die übergeordneten Objekte dieses Moduls.") . ""); + return false; + case "object_deleted": + $this->createDummyForErrormessage("deleted"); + return false; + } + if ($connected_cms[$this->cms_type]->isAuthNecessary() && $connected_cms[$this->cms_type]->user->isConnected()) { + // If User has no permission, don't show module data + if (!$this->isAllowed(OPERATION_VISIBLE) && !$this->isDummy() && $connected_cms[$this->cms_type]->user->isConnected()) { + $this->createDummyForErrormessage("no permission"); + } + } + + return true; + } + + /** + * set connection + * + * sets connection with seminar + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function setConnection($seminar_id) + { + global $connected_cms, $messages; + + $write_permission = Request::option("write_permission"); + $write_permission_autor = Request::option("write_permission_autor"); + + $crs_id = ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); +// echo "SET?".$this->cms_type; + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + $connected_cms[$this->cms_type]->soap_client->clearCache(); + + // Check, ob Kurs in ILIAS gelöscht wurde + if (($crs_id != false) AND ($connected_cms[$this->cms_type]->soap_client->getObjectByReference($crs_id) == false)) + { + ObjectConnections::unsetConnection($seminar_id, $crs_id, "crs", $this->cms_type); +// echo "deleted: ".ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); +// echo "Der zugeordnete ILIAS-Kurs (ID $crs_id) existiert nicht mehr. Ein neuer Kurs wird angelegt."; + $messages["info"] .= _("Der zugeordnete ILIAS-Kurs (ID $crs_id) existiert nicht mehr. Ein neuer Kurs wird angelegt.") . "
"; + $crs_id = false; + } + + if ($crs_id == false) + { + + $lang_array = explode("_",Config::get()->DEFAULT_LANGUAGE); + $course_data["language"] = $lang_array[0]; + $course_data["title"] = "Stud.IP-Kurs " . Context::get()->Name; + $course_data["description"] = ""; + $ref_id = $connected_cms[$this->cms_type]->main_category_node_id; + $crs_id = $connected_cms[$this->cms_type]->soap_client->addCourse($course_data, $ref_id); + + if ($crs_id == false) + { + $messages["error"] .= _("Zuordnungs-Fehler: Kurs konnte nicht angelegt werden."); + return false; + } + ObjectConnections::setConnection($seminar_id, $crs_id, "crs", $this->cms_type); + + // Rollen zuordnen + $connected_cms[$this->cms_type]->permissions->CheckUserPermissions($crs_id); +// $messages["info"] .= "Neue Kurs-ID: $crs_id.
"; + } + + + $ref_id = $this->getId(); + $ref_id = $connected_cms[$this->cms_type]->soap_client->addReference($this->id, $crs_id); + $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($crs_id); + $member_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ]); + $admin_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_WRITE]); + foreach ($local_roles as $key => $role_data){ + // check only if local role is il_crs_member, -tutor or -admin + if (mb_strpos($role_data["title"], "il_crs_") === 0) { + if(mb_strpos($role_data["title"], 'il_crs_member') === 0){ + $operations = $write_permission_autor ? $admin_operations : $member_operations; + } else if(mb_strpos($role_data["title"], 'il_crs_tutor') === 0){ + $operations = $write_permission_autor || $write_permission ? $admin_operations : $member_operations; + } else { + continue; + } + $connected_cms[$this->cms_type]->soap_client->revokePermissions($role_data["obj_id"], $ref_id); + $connected_cms[$this->cms_type]->soap_client->grantPermissions($operations, $role_data["obj_id"], $ref_id); + } + } + if ($ref_id) + { + $this->setId($ref_id); + return parent::setConnection($seminar_id); + } + else + $messages["error"] .= _("Die Zuordnung konnte nicht gespeichert werden."); + return false; + } + + /** + * unset connection + * + * unsets connection with seminar + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function unsetConnection($seminar_id) + { + global $connected_cms, $messages; + + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + { + if ( $this->getObjectId() != false) + $connected_cms[$this->cms_type]->soap_client->deleteObject($this->getId()); + return parent::unsetConnection($seminar_id); + } + $messages["error"] .= _("Die Zuordnung konnte nicht entfernt werden."); + return false; + } + + /** + * set object id + * + * sets object id + * @access public + * @param string $module_object_id object id + */ + function setObjectId($module_object_id) + { + $this->object_id = $module_object_id; + } + + /** + * get object id + * + * returns object id + * @access public + * @return string object id + */ + function getObjectId() + { + return $this->object_id; + } + + /** + * set allowed operations + * + * sets allowed operations + * @access public + * @param array $operation_array operation-ids + */ + function setAllowedOperations( $operation_array ) + { + global $connected_cms; + + $this->allowed_operations = []; + foreach($operation_array as $key => $operation) + { +// echo "O$operation = I".$connected_cms[$this->cms_type]->permissions->getOperation[$operation]."
"; + $this->allowed_operations[] = $connected_cms[$this->cms_type]->permissions->getOperation[$operation]; + } + } + + /** + * get permission-status + * + * returns true, if operation is allowed + * @access public + * @param string $operation operation + * @return boolean allowed + */ + function isAllowed($operation) + { + global $connected_cms; + + if (is_array($this->allowed_operations)) + { + if (in_array($connected_cms[$this->cms_type]->permissions->getOperation($operation), $this->allowed_operations)) + return true; + else + return false; + } + else + return false; + } +} +?> diff --git a/lib/elearning/Ilias3ObjectXMLParser.class.php b/lib/elearning/Ilias3ObjectXMLParser.class.php deleted file mode 100644 index 172ca2a..0000000 --- a/lib/elearning/Ilias3ObjectXMLParser.class.php +++ /dev/null @@ -1,236 +0,0 @@ - -* -* @extends ilSaxParser -* @package common -*/ - -class Ilias3ObjectXMLParser extends Ilias3SaxParser -{ - var $object_data = []; - var $curr_obj; - var $reference_count; - var $cdata = ''; - - /** - * Constructor - * - * @param object $a_content_object must be of type ilObjContentObject - * ilObjTest or ilObjQuestionPool - * @param string $a_xml_file xml data - * @param string $a_subdir subdirectory in import directory - * - * @access public - */ - function __construct($a_xml_data = '') - { - parent::__construct(); - $this->setXMLContent($a_xml_data); - } - - function getObjectData() - { - return $this->object_data ? $this->object_data : []; - } - - /** - * set event handlers - * - * @param resource reference to the xml parser - * - * @access private - */ - function setHandlers($a_xml_parser) - { - xml_set_object($a_xml_parser, $this); - xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); - xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); - } - - - /** - * handler for begin of element - * - * @param resource $a_xml_parser xml parser - * @param string $a_name element name - * @param array $a_attribs element attributes array - */ - function handlerBeginTag($a_xml_parser, $a_name, $a_attribs) - { - - switch ($a_name) { - case 'Objects': - $this->curr_obj = -1; - break; - - case 'Object': - ++$this->curr_obj; - $this->reference_count = -1; - - $this->addProperty__('type', $a_attribs['type']); - $this->addProperty__('obj_id', $a_attribs['obj_id']); - break; - - case 'Title': - break; - - case 'Description': - break; - - case 'Owner': - break; - - case 'CreateDate': - break; - - case 'LastUpdate': - break; - - case 'ImportId': - break; - - case 'References': - ++$this->reference_count; - $this->addReference__($a_attribs['ref_id'], $a_attribs['accessInfo']); - break; - - case 'Operation': - break; - } - } - - - /** - * handler for end of element - * - * @param resource $a_xml_parser xml parser - * @param string $a_name element name - */ - function handlerEndTag($a_xml_parser, $a_name) - { - switch ($a_name) { - case 'Objects': - break; - - case 'Object': - break; - - case 'Title': - $this->addProperty__('title', trim($this->cdata)); - break; - - case 'Description': - $this->addProperty__('description', trim($this->cdata)); - break; - - case 'Owner': - $this->addProperty__('owner', trim($this->cdata)); - break; - - case 'CreateDate': - $this->addProperty__('create_date', trim($this->cdata)); - break; - - case 'LastUpdate': - $this->addProperty__('last_update', trim($this->cdata)); - break; - - case 'ImportId': - $this->addProperty__('import_id', trim($this->cdata)); - break; - - case 'References': - $this->addReference__(trim($this->cdata)); - break; - - case 'Operation': - $this->addOperation__(trim($this->cdata)); - break; - } - - $this->cdata = ''; - - return; - } - - /** - * handler for character data - * - * @param resource $a_xml_parser xml parser - * @param string $a_data character data - */ - function handlerCharacterData($a_xml_parser, $a_data) - { - if ($a_data != "\n") { - // Replace multiple tabs with one space - $a_data = preg_replace("/\t+/", " ", $a_data); - - $this->cdata .= $a_data; - } - - - } - - // PRIVATE - function addProperty__($a_name, $a_value) - { - $this->object_data[$this->curr_obj][$a_name] = $a_value; - /*/ - if (is_array($this->object_data[$this->curr_obj][$a_name])) - $this->object_data[$this->curr_obj][$a_name][] = $a_value; - elseif ($this->object_data[$this->curr_obj][$a_name] != "") - { - $old_value = $this->object_data[$this->curr_obj][$a_name]; - $this->object_data[$this->curr_obj][$a_name] = array($old_value); - $this->object_data[$this->curr_obj][$a_name][] = $a_value; - } - else - $this->object_data[$this->curr_obj][$a_name] = $a_value; - /**/ - } - - function addReference__($a_value, $a_accessinfo = "") - { - if ($a_value) { - $this->object_data[$this->curr_obj]['references'][$this->reference_count]["ref_id"] = $a_value; - $this->object_data[$this->curr_obj]['references'][$this->reference_count]["accessInfo"] = $a_accessinfo; - } - } - - function addOperation__($a_value) - { - if ($a_value) { - $this->object_data[$this->curr_obj]['references'][$this->reference_count]["operations"][] = $a_value; - } - } -} diff --git a/lib/elearning/Ilias3ObjectXMLParser.php b/lib/elearning/Ilias3ObjectXMLParser.php new file mode 100644 index 0000000..172ca2a --- /dev/null +++ b/lib/elearning/Ilias3ObjectXMLParser.php @@ -0,0 +1,236 @@ + +* +* @extends ilSaxParser +* @package common +*/ + +class Ilias3ObjectXMLParser extends Ilias3SaxParser +{ + var $object_data = []; + var $curr_obj; + var $reference_count; + var $cdata = ''; + + /** + * Constructor + * + * @param object $a_content_object must be of type ilObjContentObject + * ilObjTest or ilObjQuestionPool + * @param string $a_xml_file xml data + * @param string $a_subdir subdirectory in import directory + * + * @access public + */ + function __construct($a_xml_data = '') + { + parent::__construct(); + $this->setXMLContent($a_xml_data); + } + + function getObjectData() + { + return $this->object_data ? $this->object_data : []; + } + + /** + * set event handlers + * + * @param resource reference to the xml parser + * + * @access private + */ + function setHandlers($a_xml_parser) + { + xml_set_object($a_xml_parser, $this); + xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag'); + xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData'); + } + + + /** + * handler for begin of element + * + * @param resource $a_xml_parser xml parser + * @param string $a_name element name + * @param array $a_attribs element attributes array + */ + function handlerBeginTag($a_xml_parser, $a_name, $a_attribs) + { + + switch ($a_name) { + case 'Objects': + $this->curr_obj = -1; + break; + + case 'Object': + ++$this->curr_obj; + $this->reference_count = -1; + + $this->addProperty__('type', $a_attribs['type']); + $this->addProperty__('obj_id', $a_attribs['obj_id']); + break; + + case 'Title': + break; + + case 'Description': + break; + + case 'Owner': + break; + + case 'CreateDate': + break; + + case 'LastUpdate': + break; + + case 'ImportId': + break; + + case 'References': + ++$this->reference_count; + $this->addReference__($a_attribs['ref_id'], $a_attribs['accessInfo']); + break; + + case 'Operation': + break; + } + } + + + /** + * handler for end of element + * + * @param resource $a_xml_parser xml parser + * @param string $a_name element name + */ + function handlerEndTag($a_xml_parser, $a_name) + { + switch ($a_name) { + case 'Objects': + break; + + case 'Object': + break; + + case 'Title': + $this->addProperty__('title', trim($this->cdata)); + break; + + case 'Description': + $this->addProperty__('description', trim($this->cdata)); + break; + + case 'Owner': + $this->addProperty__('owner', trim($this->cdata)); + break; + + case 'CreateDate': + $this->addProperty__('create_date', trim($this->cdata)); + break; + + case 'LastUpdate': + $this->addProperty__('last_update', trim($this->cdata)); + break; + + case 'ImportId': + $this->addProperty__('import_id', trim($this->cdata)); + break; + + case 'References': + $this->addReference__(trim($this->cdata)); + break; + + case 'Operation': + $this->addOperation__(trim($this->cdata)); + break; + } + + $this->cdata = ''; + + return; + } + + /** + * handler for character data + * + * @param resource $a_xml_parser xml parser + * @param string $a_data character data + */ + function handlerCharacterData($a_xml_parser, $a_data) + { + if ($a_data != "\n") { + // Replace multiple tabs with one space + $a_data = preg_replace("/\t+/", " ", $a_data); + + $this->cdata .= $a_data; + } + + + } + + // PRIVATE + function addProperty__($a_name, $a_value) + { + $this->object_data[$this->curr_obj][$a_name] = $a_value; + /*/ + if (is_array($this->object_data[$this->curr_obj][$a_name])) + $this->object_data[$this->curr_obj][$a_name][] = $a_value; + elseif ($this->object_data[$this->curr_obj][$a_name] != "") + { + $old_value = $this->object_data[$this->curr_obj][$a_name]; + $this->object_data[$this->curr_obj][$a_name] = array($old_value); + $this->object_data[$this->curr_obj][$a_name][] = $a_value; + } + else + $this->object_data[$this->curr_obj][$a_name] = $a_value; + /**/ + } + + function addReference__($a_value, $a_accessinfo = "") + { + if ($a_value) { + $this->object_data[$this->curr_obj]['references'][$this->reference_count]["ref_id"] = $a_value; + $this->object_data[$this->curr_obj]['references'][$this->reference_count]["accessInfo"] = $a_accessinfo; + } + } + + function addOperation__($a_value) + { + if ($a_value) { + $this->object_data[$this->curr_obj]['references'][$this->reference_count]["operations"][] = $a_value; + } + } +} diff --git a/lib/elearning/Ilias3SaxParser.class.php b/lib/elearning/Ilias3SaxParser.class.php deleted file mode 100644 index 92d247d..0000000 --- a/lib/elearning/Ilias3SaxParser.class.php +++ /dev/null @@ -1,230 +0,0 @@ - -* -* @package ilias-core -*/ -class Ilias3SaxParser -{ - /** - * XML-Content type 'file' or 'string' - * If you choose file set the filename in constructor - * If you choose 'String' call the constructor with no argument and use setXMLContent() - * @var string - * @access private - */ - var $input_type = null; - - /** - * XML-Content in case of content type 'string' - - * @var string - * @access private - */ - var $xml_content = ''; - - /** - * ilias object - * @var object ilias - * @access private - */ - var $ilias; - - /** - * language object - * @var object language - * @access private - */ - var $lng; - - /** - * xml filename - * @var filename - * @access private - */ - var $xml_file; - - /** - * Constructor - * setup ILIAS global object - * @access public - */ - function __construct($a_xml_file = '') - { - global $ilias, $lng; - - if($a_xml_file) - { - $this->xml_file = $a_xml_file; - $this->input_type = 'file'; - } - - $this->ilias = &$ilias; - $this->lng = &$lng; - } - - function setXMLContent($a_xml_content) - { - $this->xml_content = $a_xml_content; - $this->input_type = 'string'; - } - - function getXMLContent() - { - return $this->xml_content; - } - - function getInputType() - { - return $this->input_type; - } - - /** - * stores xml data in array - * - * @access private - */ - function startParsing() - { - $xml_parser = $this->createParser(); - $this->setOptions($xml_parser); - $this->setHandlers($xml_parser); - - switch($this->getInputType()) - { - case 'file': - $fp = $this->openXMLFile(); - $this->parse($xml_parser,$fp); - break; - - case 'string': - $this->parse($xml_parser); - break; - - default: - echo "No input type given. Set filename in constructor or choose setXMLContent()"; - break; - } - $this->freeParser($xml_parser); - } - /** - * create parser - * - * @access private - */ - function createParser() - { - $xml_parser = xml_parser_create("UTF-8"); - - if($xml_parser == false) - { - echo "Cannot create an XML parser handle"; - } - return $xml_parser; - } - /** - * set parser options - * - * @access private - */ - function setOptions($a_xml_parser) - { - xml_parser_set_option($a_xml_parser,XML_OPTION_CASE_FOLDING,false); - } - /** - * set event handler - * should be overwritten by inherited class - * @access private - */ - function setHandlers($a_xml_parser) - { - echo 'ilSaxParser::setHandlers() must be overwritten'; - } - /** - * open xml file - * - * @access private - */ - function openXMLFile() - { - if(!($fp = fopen($this->xml_file,'r'))) - { - echo "Cannot open xml file"; - } - return $fp; - } - /** - * parse xml file - * - * @access private - */ - function parse($a_xml_parser,$a_fp = null) - { - switch($this->getInputType()) - { - case 'file': - - while($data = fread($a_fp,4096)) - { - $parseOk = xml_parse($a_xml_parser,$data,feof($a_fp)); - } - break; - - case 'string': - $parseOk = xml_parse($a_xml_parser,$this->getXMLContent()); - break; - } - if(!$parseOk - && (xml_get_error_code($a_xml_parser) != XML_ERROR_NONE)) - { - echo $this->getXMLContent(); - echo "XML Parse Error: ".xml_get_error_code($a_xml_parser); - } - return true; - - } - /** - * free xml parser handle - * - * @access private - */ - function freeParser($a_xml_parser) - { - if(!xml_parser_free($a_xml_parser)) - { - echo "Error freeing xml parser handle "; - } - } -} -?> diff --git a/lib/elearning/Ilias3SaxParser.php b/lib/elearning/Ilias3SaxParser.php new file mode 100644 index 0000000..92d247d --- /dev/null +++ b/lib/elearning/Ilias3SaxParser.php @@ -0,0 +1,230 @@ + +* +* @package ilias-core +*/ +class Ilias3SaxParser +{ + /** + * XML-Content type 'file' or 'string' + * If you choose file set the filename in constructor + * If you choose 'String' call the constructor with no argument and use setXMLContent() + * @var string + * @access private + */ + var $input_type = null; + + /** + * XML-Content in case of content type 'string' + + * @var string + * @access private + */ + var $xml_content = ''; + + /** + * ilias object + * @var object ilias + * @access private + */ + var $ilias; + + /** + * language object + * @var object language + * @access private + */ + var $lng; + + /** + * xml filename + * @var filename + * @access private + */ + var $xml_file; + + /** + * Constructor + * setup ILIAS global object + * @access public + */ + function __construct($a_xml_file = '') + { + global $ilias, $lng; + + if($a_xml_file) + { + $this->xml_file = $a_xml_file; + $this->input_type = 'file'; + } + + $this->ilias = &$ilias; + $this->lng = &$lng; + } + + function setXMLContent($a_xml_content) + { + $this->xml_content = $a_xml_content; + $this->input_type = 'string'; + } + + function getXMLContent() + { + return $this->xml_content; + } + + function getInputType() + { + return $this->input_type; + } + + /** + * stores xml data in array + * + * @access private + */ + function startParsing() + { + $xml_parser = $this->createParser(); + $this->setOptions($xml_parser); + $this->setHandlers($xml_parser); + + switch($this->getInputType()) + { + case 'file': + $fp = $this->openXMLFile(); + $this->parse($xml_parser,$fp); + break; + + case 'string': + $this->parse($xml_parser); + break; + + default: + echo "No input type given. Set filename in constructor or choose setXMLContent()"; + break; + } + $this->freeParser($xml_parser); + } + /** + * create parser + * + * @access private + */ + function createParser() + { + $xml_parser = xml_parser_create("UTF-8"); + + if($xml_parser == false) + { + echo "Cannot create an XML parser handle"; + } + return $xml_parser; + } + /** + * set parser options + * + * @access private + */ + function setOptions($a_xml_parser) + { + xml_parser_set_option($a_xml_parser,XML_OPTION_CASE_FOLDING,false); + } + /** + * set event handler + * should be overwritten by inherited class + * @access private + */ + function setHandlers($a_xml_parser) + { + echo 'ilSaxParser::setHandlers() must be overwritten'; + } + /** + * open xml file + * + * @access private + */ + function openXMLFile() + { + if(!($fp = fopen($this->xml_file,'r'))) + { + echo "Cannot open xml file"; + } + return $fp; + } + /** + * parse xml file + * + * @access private + */ + function parse($a_xml_parser,$a_fp = null) + { + switch($this->getInputType()) + { + case 'file': + + while($data = fread($a_fp,4096)) + { + $parseOk = xml_parse($a_xml_parser,$data,feof($a_fp)); + } + break; + + case 'string': + $parseOk = xml_parse($a_xml_parser,$this->getXMLContent()); + break; + } + if(!$parseOk + && (xml_get_error_code($a_xml_parser) != XML_ERROR_NONE)) + { + echo $this->getXMLContent(); + echo "XML Parse Error: ".xml_get_error_code($a_xml_parser); + } + return true; + + } + /** + * free xml parser handle + * + * @access private + */ + function freeParser($a_xml_parser) + { + if(!xml_parser_free($a_xml_parser)) + { + echo "Error freeing xml parser handle "; + } + } +} +?> diff --git a/lib/elearning/Ilias3Soap.class.php b/lib/elearning/Ilias3Soap.class.php deleted file mode 100644 index 4fc5a65..0000000 --- a/lib/elearning/Ilias3Soap.class.php +++ /dev/null @@ -1,1069 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module Ilias3Soap -* @package ELearning-Interface -*/ -class Ilias3Soap extends StudipSoapClient -{ - var $cms_type; - var $admin_sid; - var $user_sid; - var $user_type; - var $soap_cache; - var $caching_active = false; - - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms) - { - global $ELEARNING_INTERFACE_MODULES, $connected_cms; - $this->cms_type = $cms; - - parent::__construct($ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_SOAP"]); - $this->user_type = "admin"; - - $this->loadCacheData($cms); - } - - - - /** - * set usertype - * - * sets usertype fpr soap-calls - * @access public - * @param string user_type usertype (admin or user) - */ - function setUserType($user_type) - { - $this->user_type = $user_type; - } - - /** - * get sid - * - * returns soap-session-id - * @access public - * @return string session-id - */ - function getSID() - { - if ($this->user_type == "admin") - { - if ($this->admin_sid == false) - $this->login(); -// echo "a"; - return $this->admin_sid; - } - if ($this->user_type == "user") - { - if ($this->user_sid == false) - $this->login(); -// echo "u"; - return $this->user_sid; - } - return false; - } - - /** - * call soap-function - * - * calls soap-function with given parameters - * @access public - * @param string method method-name - * @param string params parameters - * @return mixed result - */ - function call($method, $params) - { - $index = md5($method . ":" . implode('-', $params)); - // return false if no session_id is given - if (($method != "login") AND ($params["sid"] == "")) - return false; -// echo $this->caching_active; - if (($this->caching_active == true) AND (isset($this->soap_cache[$index]))) - { -// echo $index; -// echo " from Cache
"; - $result = $this->soap_cache[$index]; - } - else - { - $result = $this->_call($method, $params); - // if Session is expired, re-login and try again - if (($method != "login") AND $this->soap_client->fault AND in_array(mb_strtolower($this->faultstring), ["session not valid","session invalid", "session idled"]) ) - { -// echo "LOGIN AGAIN."; - $caching_status = $this->caching_active; - $this->caching_active = false; - $params["sid"] = $this->login(); - $result = $this->_call($method, $params); - $this->caching_active = $caching_status; - } - elseif (! $this->soap_client->fault) - $this->soap_cache[$index] = $result; - } - return $result; - } - - /** - * load cache - * - * load soap-cache - * @access public - * @param string cms cms-type - */ - function loadCacheData($cms) - { - $this->soap_cache = (array)$_SESSION["cache_data"][$cms]; - } - - /** - * get caching status - * - * gets caching-status - * @access public - * @return boolean status - */ - function getCachingStatus() - { - return $this->caching_active; - } - - /** - * set caching status - * - * sets caching-status - * @access public - * @param boolean bool_value status - */ - function setCachingStatus($bool_value) - { - $this->caching_active = $bool_value; -// echo "SET:".$this->caching_active."
"; - } - - /** - * clear cache - * - * clears cache - * @access public - */ - function clearCache() - { - $this->soap_cache = []; - $_SESSION["cache_data"][$this->cms_type] = []; - - } - - /** - * save cache - * - * saves soap-cache in session-variable - * @access public - */ - function saveCacheData() - { - $_SESSION["cache_data"][$this->cms_type] = $this->soap_cache; - - } - - /** - * parse xml - * - * use xml-parser - * @access public - * @param string data xml-data - * @return array object - */ - function ParseXML($data) - { - $xml_parser = new Ilias3ObjectXMLParser($data); - $xml_parser->startParsing(); - return $xml_parser->getObjectData(); - } - - /** - * login - * - * login to soap-webservice - * @access public - * @return string result - */ - function login() - { - global $ELEARNING_INTERFACE_MODULES, $connected_cms; - if ($this->user_type == "admin") - $param = [ - 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], - 'username' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["username"], - 'password' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["password"] - ]; - elseif ($this->user_type == "user") - $param = [ - 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], - 'username' => $connected_cms[$this->cms_type]->user->getUsername(), - 'password' => $connected_cms[$this->cms_type]->user->getPassword() - ]; - $result = $this->call('login', $param); - if ($this->user_type == "admin") - $this->admin_sid = $result; - if ($this->user_type == "user") - $this->user_sid = $result; -// if ($this->user_type == "user") echo "SID".$this->call('login', $param).$param["username"]; - return $result; - } - - /** - * logout - * - * logout from soap-webservice - * @access public - * @return boolean result - */ - function logout() - { - $param = [ - 'sid' => $this->getSID() - ]; - return $this->call('logout', $param); - } - - -/////////////////////////// -// OBJECT-FUNCTIONS // -////////////////////////// - - /** - * search objects - * - * search for ilias-objects - * @access public - * @param array types types - * @param string key keyword - * @param string combination search-combination - * @param string user_id ilias-user-id - * @return array objects - */ - function searchObjects($types, $key, $combination, $user_id = "") - { - $param = [ - 'sid' => $this->getSID(), - 'types' => $types, - 'key' => $key, - 'combination' => $combination - ]; - if ($user_id != "") - $param["user_id"] = $user_id; - $result = $this->call('searchObjects', $param); - if ($result != false) - { - $objects = $this->parseXML($result); - $all_objects = []; - foreach($objects as $count => $object_data){ - if (is_array($object_data["references"])) - { - foreach($object_data["references"] as $ref_data) - if ($ref_data["accessInfo"] == "granted" - && (count($all_objects[$object_data["obj_id"]]["operations"]) < count($ref_data["operations"]))) - { - $all_objects[$object_data["obj_id"]] = $object_data; - unset($all_objects[$object_data["obj_id"]]["references"]); - $all_objects[$object_data["obj_id"]]["ref_id"] = $ref_data["ref_id"]; - $all_objects[$object_data["obj_id"]]["accessInfo"] = $ref_data["accessInfo"]; - $all_objects[$object_data["obj_id"]]["operations"] = $ref_data["operations"]; - } - } - } - if (count($all_objects)){ - foreach($all_objects as $one_object){ - $ret[$one_object['ref_id']] = $one_object; - } - return $ret; - } - } - return false; - - } - - /** - * get object by reference - * - * gets object by reference-id - * @access public - * @param ref reference_id - * @param string user_id ilias-user-id - * @return array object - */ - function getObjectByReference($ref, $user_id = "") - { - $param = [ - 'sid' => $this->getSID(), - 'reference_id' => $ref - ]; - if ($user_id != "") - $param["user_id"] = $user_id; - $result = $this->call('getObjectByReference', $param); - if ($result != false) - { - - $objects = $this->parseXML($result); - foreach($objects as $count => $object_data) - if (is_array($object_data["references"])) - { - foreach($object_data["references"] as $ref_data) - if ($ref_data["accessInfo"] != "object_deleted" && $ref == $ref_data["ref_id"]) - { - $all_objects[$ref_data["ref_id"]] = $object_data; - unset($all_objects[$ref_data["ref_id"]]["references"]); - $all_objects[$ref_data["ref_id"]]["ref_id"] = $ref_data["ref_id"]; - $all_objects[$ref_data["ref_id"]]["accessInfo"] = $ref_data["accessInfo"]; - $all_objects[$ref_data["ref_id"]]["operations"] = $ref_data["operations"]; - } - } - return $all_objects[$ref]; - } - return false; - } - - /** - * get object by title - * - * gets object by title - * @access public - * @param string key keyword - * @param string type object-type - * @return array object - */ - function getObjectByTitle($key, $type = "") - { - $param = [ - 'sid' => $this->getSID(), - 'title' => $key - ]; - $result = $this->call('getObjectsByTitle', $param); - if ($result != false) - { - $objects = $this->parseXML($result); - foreach($objects as $index => $object_data) - { - if (($type != "") AND ($object_data["type"] != $type)) - unset($objects[$index]); - elseif (! (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === 0)) - unset($objects[$index]); - } - reset($objects); - if (sizeof($objects) > 0) - return current($objects); - } - return false; - } - - /** - * get reference by title - * - * gets reference-id by object-title - * @access public - * @param string key keyword - * @param string type object-type - * @return string reference-id - */ - function getReferenceByTitle($key, $type = "") - { - $param = [ - 'sid' => $this->getSID(), - 'title' => $key - ]; - $result = $this->call('getObjectsByTitle', $param); - if ($result != false) - { - $objects = $this->parseXML($result); - foreach($objects as $index => $object_data) - { - if (($type != "") AND ($object_data["type"] != $type)) - unset($objects[$index]); - elseif (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === false) - unset($objects[$index]); - } - if (sizeof($objects) > 0) - foreach($objects as $object_data) - if (sizeof($object_data["references"]) > 0) - { - return $object_data["references"][0]["ref_id"]; - } - } - return false; - } - - /** - * add object - * - * adds new ilias-object - * @access public - * @param array object_data object-data - * @param string ref_id reference-id - * @return string result - */ - function addObject($object_data, $ref_id) - { - $type = $object_data["type"]; - $title = htmlReady($object_data["title"]); - $description = htmlReady($object_data["description"]); - - $xml = " - - - - $title - - - $description - - -"; - - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'object_xml' => $xml - ]; - return $this->call('addObject', $param); - } - - /** - * delete object - * - * deletes ilias-object - * @access public - * @param string ref_id reference-id - * @return boolean result - */ - function deleteObject($reference_id) - { - $param = [ - 'sid' => $this->getSID(), - 'reference_id' => $reference_id - ]; - return $this->call('deleteObject', $param); - } - - /** - * add reference - * - * add a new reference to an existing ilias-object - * @access public - * @param string object_id source-object-id - * @param string ref_id target-id - * @return string created reference-id - */ - function addReference($object_id, $ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'source_id' => $object_id, - 'target_id' => $ref_id - ]; - return $this->call('addReference', $param); - } - - /** - * get tree childs - * - * gets child-objects of the given tree node - * @access public - * @param string ref_id reference-id - * @param array types show only childs with these types - * @param string user_id user-id for permissions - * @return array objects - */ - function getTreeChilds($ref_id, $types = "", $user_id = "") - { - if ($types == "") - $types = []; - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'types' => $types - ]; - if ($user_id != "") - $param["user_id"] = $user_id; - $result = $this->call('getTreeChilds', $param); - if ($result != false) - { - - $objects = $this->parseXML($result); - foreach($objects as $count => $object_data) - if (is_array($object_data["references"])) - foreach($object_data["references"] as $ref_data) - if ($ref_data["accessInfo"] != "object_deleted") - { - $all_objects[$ref_data["ref_id"]] = $object_data; -// unset($all_objects[$ref_id]["references"]); - $all_objects[$ref_data["ref_id"]]["ref_id"] = $ref_data["ref_id"]; - $all_objects[$ref_data["ref_id"]]["accessInfo"] = $ref_data["accessInfo"]; - $all_objects[$ref_data["ref_id"]]["operations"] = $ref_data["operations"]; - } - if (sizeof($all_objects) > 0) { - return $all_objects; - } else { - return []; - } - } - return false; - } - -///////////////////////// -// RBAC-FUNCTIONS // -/////////////////////// - /** - * get operation - * - * gets all ilias operations - * @access public - * @return array operations - */ - function getOperations() - { - $param = [ - 'sid' => $this->getSID() - ]; - $result = $this->call('getOperations', $param); - if (is_array($result)) - foreach ($result as $operation_set) - $operations[$operation_set["operation"]] = $operation_set["ops_id"]; - return $operations; - } - - /** - * get object tree operations - * - * gets permissions for object at given tree-node - * @access public - * @param string ref_id reference-id - * @param string user_id user-id for permissions - * @return array operation-ids - */ - function getObjectTreeOperations($ref_id, $user_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'user_id' => $user_id - ]; - $result = $this->call('getObjectTreeOperations', $param); - if ($result != false) - { - $ops_ids = []; - foreach ($result as $operation_set) - $ops_ids[] = $operation_set["ops_id"]; - return $ops_ids; - } - return false; - } - - /** - * get user roles - * - * gets user roles - * @access public - * @param string user_id user-id - * @return array role-ids - */ - function getUserRoles($user_id) - { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id - ]; - $result = $this->call('getUserRoles', $param); - if ($result != false) - { - $objects = $this->parseXML($result); - $roles = []; - foreach ($objects as $count => $role) { - $roles[$count] = $role["obj_id"]; - } - return $roles; - } - return false; - } - - /** - * get local roles - * - * gets local roles for given object - * @access public - * @param string course_id object-id - * @return array role-objects - */ - function getLocalRoles($course_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $course_id - ]; - $result = $this->call('getLocalRoles', $param); - if ($result != false) - { - $objects = $this->parseXML($result); - return $objects; - } - return false; - } - - /** - * add role - * - * adds a new role - * @access public - * @param array role_data data for role-object - * @param string ref_id reference-id - * @return string role-id - */ - function addRole($role_data, $ref_id) - { - $type = "role"; - $title = htmlReady($role_data["title"]); - $description = htmlReady($role_data["description"]); - - $xml = " - - - - $title - - - $description - - -"; - - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'obj_xml' => $xml - ]; - $result = $this->call('addRole', $param); - if (is_array($result)) - return current($result); - else - return false; - } - - /** - * add role from tremplate - * - * adds a new role and adopts properties of the given role template - * @access public - * @param array role_data data for role-object - * @param string ref_id reference-id - * @param string role_id role-template-id - * @return string role-id - */ - function addRoleFromTemplate($role_data, $ref_id, $role_id) - { - $type = "role"; - $title = htmlReady($role_data["title"]); - $description = htmlReady($role_data["description"]); - - $xml = " - - - - $title - - - $description - - -"; - - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'obj_xml' => $xml, - 'role_template_id' => $role_id - ]; - $result = $this->call('addRoleFromTemplate', $param); - if (is_array($result)) - return current($result); - else - return false; - } - - /** - * delete user role entry - * - * deletes a role entry from the given user - * @access public - * @param string user_id user-id - * @param string role_id role-id - * @return boolean result - */ - function deleteUserRoleEntry($user_id, $role_id) - { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id, - 'role_id' => $role_id - ]; - return $this->call('deleteUserRoleEntry', $param); - } - - /** - * add user role entry - * - * adds a role entry for the given user - * @access public - * @param string user_id user-id - * @param string role_id role-id - * @return boolean result - */ - function addUserRoleEntry($user_id, $role_id) - { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id, - 'role_id' => $role_id - ]; - return $this->call('addUserRoleEntry', $param); - } - - /** - * grant permissions - * - * grants permissions for given operations at role-id and ref-id - * @access public - * @param array operations operation-array - * @param string role_id role-id - * @param string ref_id reference-id - * @return boolean result - */ - function grantPermissions($operations, $role_id, $ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'role_id' => $role_id, - 'operations' => $operations, - ]; - return $this->call('grantPermissions', $param); - } - - /** - * revoke permissions - * - * revokes all permissions role-id and ref-id - * @access public - * @param string role_id role-id - * @param string ref_id reference-id - * @return boolean result - */ - function revokePermissions($role_id, $ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'role_id' => $role_id, - ]; - return $this->call('revokePermissions', $param); - } - -///////////////////////// -// USER-FUNCTIONS // -/////////////////////// - - /** - * lookup user - * - * gets user-id for given username - * @access public - * @param string username username - * @return string user-id - */ - function lookupUser($username) - { - $param = [ - 'sid' => $this->getSID(), - 'user_name' => $username, - ]; - return $this->call('lookupUser', $param); // returns user_id - } - - /** - * get user - * - * gets user-data for given user-id - * @access public - * @param string user_id user-id - * @return array user-data - */ - function getUser($user_id) - { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id, - ]; - $result = $this->call('getUser', $param); // returns user-data-array - return $result; - } - - /** - * add user - * - * adds new user and sets role-id - * @access public - * @param array user_data user-data - * @param string role_id global role-id for new user - * @return string user-id - */ - function addUser($user_data, $role_id) - { - $param = [ - 'sid' => $this->getSID(), - 'user_data' => $user_data, - 'global_role_id' => $role_id - ]; - return $this->call('addUser', $param); // returns user_id - } - - /** - * update user - * - * update user-data - * @access public - * @param array user_data user-data - * @return string result - */ - function updateUser($user_data) - { - $param = [ - 'sid' => $this->getSID(), - 'user_data' => $user_data - ]; - return $this->call('updateUser', $param); // returns boolean - } - - /** - * update password - * - * update password with given string and write it uncrypted to the ilias-database - * @access public - * @param string user_id user-id - * @param string password password - * @return string result - */ - function updatePassword($user_id, $password) - { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id, - 'new_password' => $password - ]; - return $this->call('updatePassword', $param); // returns boolean - } - - /** - * delete user - * - * deletes user-account - * @access public - * @param string user_id user-id - * @return string result - */ - function deleteUser($user_id) - { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id - ]; - return $this->call('deleteUser', $param); // returns boolean - } - -//////////////////////////// -// COURSE-FUNCTIONS // -////////////////////////// - - /** - * is course member - * - * checks if user is course-member - * @access public - * @param string user_id user-id - * @param string course_id course-id - * @return boolean result - */ - function isMember($user_id, $course_id) - { - $param = [ - 'sid' => $this->getSID(), - 'course_id' => $course_id, - 'user_id' => $user_id - ]; - $status = $this->call('isAssignedToCourse', $param); // returns 0 if not assigned, 1 => course admin, 2 => course member or 3 => course tutor - if ($status == 0) - return false; - else - return true; - } - - /** - * add course member - * - * adds user to course - * @access public - * @param string user_id user-id - * @param string type member-type (Admin, Tutor or Member) - * @param string course_id course-id - * @return boolean result - */ - function addMember($user_id, $type, $course_id) - { - $param = [ - 'sid' => $this->getSID(), - 'course_id' => $course_id, - 'user_id' => $user_id, - 'type' => $type - ]; - return $this->call('assignCourseMember', $param); - } - - /** - * add course - * - * adds course - * @access public - * @param array course_data course-data - * @param string ref_id target-id - * @return string course-id - */ - function addCourse($course_data, $ref_id) - { - foreach($course_data as $key => $value) { - $course_data[$key] = htmlReady($course_data[$key]); - } - - $xml = $this->getCourseXML($course_data); - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'crs_xml' => $xml - ]; - $crs_id = $this->call('addCourse', $param); - return $crs_id; - } - - /** - * get course-xml - * - * gets course xml-object for given course-data - * @access public - * @param array course_data course-data - * @return string course-xml - */ - function getCourseXML($course_data) - { - $crs_language = $course_data["language"]; - $crs_admin_id = $course_data["admin_id"]; - $crs_title = $course_data["title"]; - $crs_desc = $course_data["description"]; - - $xml = " - - - - - - $crs_title - - - - $crs_desc - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - return $xml; - } - - /** - * check reference by title - * - * gets reference-id by object-title - * @access public - * @param string key keyword - * @param string type object-type - * @return string reference-id - */ - function checkReferenceById($id) - { - $param = [ - 'sid' => $this->getSID(), - 'reference_id' => $id - ]; - - $result = $this->call('getObjectByReference', $param); - if ($result != false) - { - $objects = $this->parseXML($result); - //echo "

".print_r($objects,1); - //echo "\n

"; - if(is_array($objects)){ - foreach($objects as $index => $object_data){ - if(is_array($object_data['references'])){ - foreach($object_data['references'] as $reference){ - if($reference['ref_id'] == $id && $reference['accessInfo'] != 'object_deleted') return $object_data['obj_id']; - } - } - } - } - } - return false; - } -} diff --git a/lib/elearning/Ilias3Soap.php b/lib/elearning/Ilias3Soap.php new file mode 100644 index 0000000..4fc5a65 --- /dev/null +++ b/lib/elearning/Ilias3Soap.php @@ -0,0 +1,1069 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module Ilias3Soap +* @package ELearning-Interface +*/ +class Ilias3Soap extends StudipSoapClient +{ + var $cms_type; + var $admin_sid; + var $user_sid; + var $user_type; + var $soap_cache; + var $caching_active = false; + + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms) + { + global $ELEARNING_INTERFACE_MODULES, $connected_cms; + $this->cms_type = $cms; + + parent::__construct($ELEARNING_INTERFACE_MODULES[$cms]["ABSOLUTE_PATH_SOAP"]); + $this->user_type = "admin"; + + $this->loadCacheData($cms); + } + + + + /** + * set usertype + * + * sets usertype fpr soap-calls + * @access public + * @param string user_type usertype (admin or user) + */ + function setUserType($user_type) + { + $this->user_type = $user_type; + } + + /** + * get sid + * + * returns soap-session-id + * @access public + * @return string session-id + */ + function getSID() + { + if ($this->user_type == "admin") + { + if ($this->admin_sid == false) + $this->login(); +// echo "a"; + return $this->admin_sid; + } + if ($this->user_type == "user") + { + if ($this->user_sid == false) + $this->login(); +// echo "u"; + return $this->user_sid; + } + return false; + } + + /** + * call soap-function + * + * calls soap-function with given parameters + * @access public + * @param string method method-name + * @param string params parameters + * @return mixed result + */ + function call($method, $params) + { + $index = md5($method . ":" . implode('-', $params)); + // return false if no session_id is given + if (($method != "login") AND ($params["sid"] == "")) + return false; +// echo $this->caching_active; + if (($this->caching_active == true) AND (isset($this->soap_cache[$index]))) + { +// echo $index; +// echo " from Cache
"; + $result = $this->soap_cache[$index]; + } + else + { + $result = $this->_call($method, $params); + // if Session is expired, re-login and try again + if (($method != "login") AND $this->soap_client->fault AND in_array(mb_strtolower($this->faultstring), ["session not valid","session invalid", "session idled"]) ) + { +// echo "LOGIN AGAIN."; + $caching_status = $this->caching_active; + $this->caching_active = false; + $params["sid"] = $this->login(); + $result = $this->_call($method, $params); + $this->caching_active = $caching_status; + } + elseif (! $this->soap_client->fault) + $this->soap_cache[$index] = $result; + } + return $result; + } + + /** + * load cache + * + * load soap-cache + * @access public + * @param string cms cms-type + */ + function loadCacheData($cms) + { + $this->soap_cache = (array)$_SESSION["cache_data"][$cms]; + } + + /** + * get caching status + * + * gets caching-status + * @access public + * @return boolean status + */ + function getCachingStatus() + { + return $this->caching_active; + } + + /** + * set caching status + * + * sets caching-status + * @access public + * @param boolean bool_value status + */ + function setCachingStatus($bool_value) + { + $this->caching_active = $bool_value; +// echo "SET:".$this->caching_active."
"; + } + + /** + * clear cache + * + * clears cache + * @access public + */ + function clearCache() + { + $this->soap_cache = []; + $_SESSION["cache_data"][$this->cms_type] = []; + + } + + /** + * save cache + * + * saves soap-cache in session-variable + * @access public + */ + function saveCacheData() + { + $_SESSION["cache_data"][$this->cms_type] = $this->soap_cache; + + } + + /** + * parse xml + * + * use xml-parser + * @access public + * @param string data xml-data + * @return array object + */ + function ParseXML($data) + { + $xml_parser = new Ilias3ObjectXMLParser($data); + $xml_parser->startParsing(); + return $xml_parser->getObjectData(); + } + + /** + * login + * + * login to soap-webservice + * @access public + * @return string result + */ + function login() + { + global $ELEARNING_INTERFACE_MODULES, $connected_cms; + if ($this->user_type == "admin") + $param = [ + 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], + 'username' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["username"], + 'password' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["password"] + ]; + elseif ($this->user_type == "user") + $param = [ + 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], + 'username' => $connected_cms[$this->cms_type]->user->getUsername(), + 'password' => $connected_cms[$this->cms_type]->user->getPassword() + ]; + $result = $this->call('login', $param); + if ($this->user_type == "admin") + $this->admin_sid = $result; + if ($this->user_type == "user") + $this->user_sid = $result; +// if ($this->user_type == "user") echo "SID".$this->call('login', $param).$param["username"]; + return $result; + } + + /** + * logout + * + * logout from soap-webservice + * @access public + * @return boolean result + */ + function logout() + { + $param = [ + 'sid' => $this->getSID() + ]; + return $this->call('logout', $param); + } + + +/////////////////////////// +// OBJECT-FUNCTIONS // +////////////////////////// + + /** + * search objects + * + * search for ilias-objects + * @access public + * @param array types types + * @param string key keyword + * @param string combination search-combination + * @param string user_id ilias-user-id + * @return array objects + */ + function searchObjects($types, $key, $combination, $user_id = "") + { + $param = [ + 'sid' => $this->getSID(), + 'types' => $types, + 'key' => $key, + 'combination' => $combination + ]; + if ($user_id != "") + $param["user_id"] = $user_id; + $result = $this->call('searchObjects', $param); + if ($result != false) + { + $objects = $this->parseXML($result); + $all_objects = []; + foreach($objects as $count => $object_data){ + if (is_array($object_data["references"])) + { + foreach($object_data["references"] as $ref_data) + if ($ref_data["accessInfo"] == "granted" + && (count($all_objects[$object_data["obj_id"]]["operations"]) < count($ref_data["operations"]))) + { + $all_objects[$object_data["obj_id"]] = $object_data; + unset($all_objects[$object_data["obj_id"]]["references"]); + $all_objects[$object_data["obj_id"]]["ref_id"] = $ref_data["ref_id"]; + $all_objects[$object_data["obj_id"]]["accessInfo"] = $ref_data["accessInfo"]; + $all_objects[$object_data["obj_id"]]["operations"] = $ref_data["operations"]; + } + } + } + if (count($all_objects)){ + foreach($all_objects as $one_object){ + $ret[$one_object['ref_id']] = $one_object; + } + return $ret; + } + } + return false; + + } + + /** + * get object by reference + * + * gets object by reference-id + * @access public + * @param ref reference_id + * @param string user_id ilias-user-id + * @return array object + */ + function getObjectByReference($ref, $user_id = "") + { + $param = [ + 'sid' => $this->getSID(), + 'reference_id' => $ref + ]; + if ($user_id != "") + $param["user_id"] = $user_id; + $result = $this->call('getObjectByReference', $param); + if ($result != false) + { + + $objects = $this->parseXML($result); + foreach($objects as $count => $object_data) + if (is_array($object_data["references"])) + { + foreach($object_data["references"] as $ref_data) + if ($ref_data["accessInfo"] != "object_deleted" && $ref == $ref_data["ref_id"]) + { + $all_objects[$ref_data["ref_id"]] = $object_data; + unset($all_objects[$ref_data["ref_id"]]["references"]); + $all_objects[$ref_data["ref_id"]]["ref_id"] = $ref_data["ref_id"]; + $all_objects[$ref_data["ref_id"]]["accessInfo"] = $ref_data["accessInfo"]; + $all_objects[$ref_data["ref_id"]]["operations"] = $ref_data["operations"]; + } + } + return $all_objects[$ref]; + } + return false; + } + + /** + * get object by title + * + * gets object by title + * @access public + * @param string key keyword + * @param string type object-type + * @return array object + */ + function getObjectByTitle($key, $type = "") + { + $param = [ + 'sid' => $this->getSID(), + 'title' => $key + ]; + $result = $this->call('getObjectsByTitle', $param); + if ($result != false) + { + $objects = $this->parseXML($result); + foreach($objects as $index => $object_data) + { + if (($type != "") AND ($object_data["type"] != $type)) + unset($objects[$index]); + elseif (! (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === 0)) + unset($objects[$index]); + } + reset($objects); + if (sizeof($objects) > 0) + return current($objects); + } + return false; + } + + /** + * get reference by title + * + * gets reference-id by object-title + * @access public + * @param string key keyword + * @param string type object-type + * @return string reference-id + */ + function getReferenceByTitle($key, $type = "") + { + $param = [ + 'sid' => $this->getSID(), + 'title' => $key + ]; + $result = $this->call('getObjectsByTitle', $param); + if ($result != false) + { + $objects = $this->parseXML($result); + foreach($objects as $index => $object_data) + { + if (($type != "") AND ($object_data["type"] != $type)) + unset($objects[$index]); + elseif (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === false) + unset($objects[$index]); + } + if (sizeof($objects) > 0) + foreach($objects as $object_data) + if (sizeof($object_data["references"]) > 0) + { + return $object_data["references"][0]["ref_id"]; + } + } + return false; + } + + /** + * add object + * + * adds new ilias-object + * @access public + * @param array object_data object-data + * @param string ref_id reference-id + * @return string result + */ + function addObject($object_data, $ref_id) + { + $type = $object_data["type"]; + $title = htmlReady($object_data["title"]); + $description = htmlReady($object_data["description"]); + + $xml = " + + + + $title + + + $description + + +"; + + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'object_xml' => $xml + ]; + return $this->call('addObject', $param); + } + + /** + * delete object + * + * deletes ilias-object + * @access public + * @param string ref_id reference-id + * @return boolean result + */ + function deleteObject($reference_id) + { + $param = [ + 'sid' => $this->getSID(), + 'reference_id' => $reference_id + ]; + return $this->call('deleteObject', $param); + } + + /** + * add reference + * + * add a new reference to an existing ilias-object + * @access public + * @param string object_id source-object-id + * @param string ref_id target-id + * @return string created reference-id + */ + function addReference($object_id, $ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'source_id' => $object_id, + 'target_id' => $ref_id + ]; + return $this->call('addReference', $param); + } + + /** + * get tree childs + * + * gets child-objects of the given tree node + * @access public + * @param string ref_id reference-id + * @param array types show only childs with these types + * @param string user_id user-id for permissions + * @return array objects + */ + function getTreeChilds($ref_id, $types = "", $user_id = "") + { + if ($types == "") + $types = []; + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'types' => $types + ]; + if ($user_id != "") + $param["user_id"] = $user_id; + $result = $this->call('getTreeChilds', $param); + if ($result != false) + { + + $objects = $this->parseXML($result); + foreach($objects as $count => $object_data) + if (is_array($object_data["references"])) + foreach($object_data["references"] as $ref_data) + if ($ref_data["accessInfo"] != "object_deleted") + { + $all_objects[$ref_data["ref_id"]] = $object_data; +// unset($all_objects[$ref_id]["references"]); + $all_objects[$ref_data["ref_id"]]["ref_id"] = $ref_data["ref_id"]; + $all_objects[$ref_data["ref_id"]]["accessInfo"] = $ref_data["accessInfo"]; + $all_objects[$ref_data["ref_id"]]["operations"] = $ref_data["operations"]; + } + if (sizeof($all_objects) > 0) { + return $all_objects; + } else { + return []; + } + } + return false; + } + +///////////////////////// +// RBAC-FUNCTIONS // +/////////////////////// + /** + * get operation + * + * gets all ilias operations + * @access public + * @return array operations + */ + function getOperations() + { + $param = [ + 'sid' => $this->getSID() + ]; + $result = $this->call('getOperations', $param); + if (is_array($result)) + foreach ($result as $operation_set) + $operations[$operation_set["operation"]] = $operation_set["ops_id"]; + return $operations; + } + + /** + * get object tree operations + * + * gets permissions for object at given tree-node + * @access public + * @param string ref_id reference-id + * @param string user_id user-id for permissions + * @return array operation-ids + */ + function getObjectTreeOperations($ref_id, $user_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'user_id' => $user_id + ]; + $result = $this->call('getObjectTreeOperations', $param); + if ($result != false) + { + $ops_ids = []; + foreach ($result as $operation_set) + $ops_ids[] = $operation_set["ops_id"]; + return $ops_ids; + } + return false; + } + + /** + * get user roles + * + * gets user roles + * @access public + * @param string user_id user-id + * @return array role-ids + */ + function getUserRoles($user_id) + { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id + ]; + $result = $this->call('getUserRoles', $param); + if ($result != false) + { + $objects = $this->parseXML($result); + $roles = []; + foreach ($objects as $count => $role) { + $roles[$count] = $role["obj_id"]; + } + return $roles; + } + return false; + } + + /** + * get local roles + * + * gets local roles for given object + * @access public + * @param string course_id object-id + * @return array role-objects + */ + function getLocalRoles($course_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $course_id + ]; + $result = $this->call('getLocalRoles', $param); + if ($result != false) + { + $objects = $this->parseXML($result); + return $objects; + } + return false; + } + + /** + * add role + * + * adds a new role + * @access public + * @param array role_data data for role-object + * @param string ref_id reference-id + * @return string role-id + */ + function addRole($role_data, $ref_id) + { + $type = "role"; + $title = htmlReady($role_data["title"]); + $description = htmlReady($role_data["description"]); + + $xml = " + + + + $title + + + $description + + +"; + + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'obj_xml' => $xml + ]; + $result = $this->call('addRole', $param); + if (is_array($result)) + return current($result); + else + return false; + } + + /** + * add role from tremplate + * + * adds a new role and adopts properties of the given role template + * @access public + * @param array role_data data for role-object + * @param string ref_id reference-id + * @param string role_id role-template-id + * @return string role-id + */ + function addRoleFromTemplate($role_data, $ref_id, $role_id) + { + $type = "role"; + $title = htmlReady($role_data["title"]); + $description = htmlReady($role_data["description"]); + + $xml = " + + + + $title + + + $description + + +"; + + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'obj_xml' => $xml, + 'role_template_id' => $role_id + ]; + $result = $this->call('addRoleFromTemplate', $param); + if (is_array($result)) + return current($result); + else + return false; + } + + /** + * delete user role entry + * + * deletes a role entry from the given user + * @access public + * @param string user_id user-id + * @param string role_id role-id + * @return boolean result + */ + function deleteUserRoleEntry($user_id, $role_id) + { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id, + 'role_id' => $role_id + ]; + return $this->call('deleteUserRoleEntry', $param); + } + + /** + * add user role entry + * + * adds a role entry for the given user + * @access public + * @param string user_id user-id + * @param string role_id role-id + * @return boolean result + */ + function addUserRoleEntry($user_id, $role_id) + { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id, + 'role_id' => $role_id + ]; + return $this->call('addUserRoleEntry', $param); + } + + /** + * grant permissions + * + * grants permissions for given operations at role-id and ref-id + * @access public + * @param array operations operation-array + * @param string role_id role-id + * @param string ref_id reference-id + * @return boolean result + */ + function grantPermissions($operations, $role_id, $ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'role_id' => $role_id, + 'operations' => $operations, + ]; + return $this->call('grantPermissions', $param); + } + + /** + * revoke permissions + * + * revokes all permissions role-id and ref-id + * @access public + * @param string role_id role-id + * @param string ref_id reference-id + * @return boolean result + */ + function revokePermissions($role_id, $ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'role_id' => $role_id, + ]; + return $this->call('revokePermissions', $param); + } + +///////////////////////// +// USER-FUNCTIONS // +/////////////////////// + + /** + * lookup user + * + * gets user-id for given username + * @access public + * @param string username username + * @return string user-id + */ + function lookupUser($username) + { + $param = [ + 'sid' => $this->getSID(), + 'user_name' => $username, + ]; + return $this->call('lookupUser', $param); // returns user_id + } + + /** + * get user + * + * gets user-data for given user-id + * @access public + * @param string user_id user-id + * @return array user-data + */ + function getUser($user_id) + { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id, + ]; + $result = $this->call('getUser', $param); // returns user-data-array + return $result; + } + + /** + * add user + * + * adds new user and sets role-id + * @access public + * @param array user_data user-data + * @param string role_id global role-id for new user + * @return string user-id + */ + function addUser($user_data, $role_id) + { + $param = [ + 'sid' => $this->getSID(), + 'user_data' => $user_data, + 'global_role_id' => $role_id + ]; + return $this->call('addUser', $param); // returns user_id + } + + /** + * update user + * + * update user-data + * @access public + * @param array user_data user-data + * @return string result + */ + function updateUser($user_data) + { + $param = [ + 'sid' => $this->getSID(), + 'user_data' => $user_data + ]; + return $this->call('updateUser', $param); // returns boolean + } + + /** + * update password + * + * update password with given string and write it uncrypted to the ilias-database + * @access public + * @param string user_id user-id + * @param string password password + * @return string result + */ + function updatePassword($user_id, $password) + { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id, + 'new_password' => $password + ]; + return $this->call('updatePassword', $param); // returns boolean + } + + /** + * delete user + * + * deletes user-account + * @access public + * @param string user_id user-id + * @return string result + */ + function deleteUser($user_id) + { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id + ]; + return $this->call('deleteUser', $param); // returns boolean + } + +//////////////////////////// +// COURSE-FUNCTIONS // +////////////////////////// + + /** + * is course member + * + * checks if user is course-member + * @access public + * @param string user_id user-id + * @param string course_id course-id + * @return boolean result + */ + function isMember($user_id, $course_id) + { + $param = [ + 'sid' => $this->getSID(), + 'course_id' => $course_id, + 'user_id' => $user_id + ]; + $status = $this->call('isAssignedToCourse', $param); // returns 0 if not assigned, 1 => course admin, 2 => course member or 3 => course tutor + if ($status == 0) + return false; + else + return true; + } + + /** + * add course member + * + * adds user to course + * @access public + * @param string user_id user-id + * @param string type member-type (Admin, Tutor or Member) + * @param string course_id course-id + * @return boolean result + */ + function addMember($user_id, $type, $course_id) + { + $param = [ + 'sid' => $this->getSID(), + 'course_id' => $course_id, + 'user_id' => $user_id, + 'type' => $type + ]; + return $this->call('assignCourseMember', $param); + } + + /** + * add course + * + * adds course + * @access public + * @param array course_data course-data + * @param string ref_id target-id + * @return string course-id + */ + function addCourse($course_data, $ref_id) + { + foreach($course_data as $key => $value) { + $course_data[$key] = htmlReady($course_data[$key]); + } + + $xml = $this->getCourseXML($course_data); + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'crs_xml' => $xml + ]; + $crs_id = $this->call('addCourse', $param); + return $crs_id; + } + + /** + * get course-xml + * + * gets course xml-object for given course-data + * @access public + * @param array course_data course-data + * @return string course-xml + */ + function getCourseXML($course_data) + { + $crs_language = $course_data["language"]; + $crs_admin_id = $course_data["admin_id"]; + $crs_title = $course_data["title"]; + $crs_desc = $course_data["description"]; + + $xml = " + + + + + + $crs_title + + + + $crs_desc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + return $xml; + } + + /** + * check reference by title + * + * gets reference-id by object-title + * @access public + * @param string key keyword + * @param string type object-type + * @return string reference-id + */ + function checkReferenceById($id) + { + $param = [ + 'sid' => $this->getSID(), + 'reference_id' => $id + ]; + + $result = $this->call('getObjectByReference', $param); + if ($result != false) + { + $objects = $this->parseXML($result); + //echo "

".print_r($objects,1); + //echo "\n

"; + if(is_array($objects)){ + foreach($objects as $index => $object_data){ + if(is_array($object_data['references'])){ + foreach($object_data['references'] as $reference){ + if($reference['ref_id'] == $id && $reference['accessInfo'] != 'object_deleted') return $object_data['obj_id']; + } + } + } + } + } + return false; + } +} diff --git a/lib/elearning/Ilias4ConnectedCMS.class.php b/lib/elearning/Ilias4ConnectedCMS.class.php deleted file mode 100644 index d45168f..0000000 --- a/lib/elearning/Ilias4ConnectedCMS.class.php +++ /dev/null @@ -1,272 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias4ConnectedCMS - * @package ELearning-Interface - */ -class Ilias4ConnectedCMS extends Ilias3ConnectedCMS -{ - var $user_category_node_id; - var $ldap_enable; - /** - * constructor - * - * init class. - * @access public - * @param string $cms system-type - */ - function __construct($cms) - { - global $messages; - parent::__construct($cms); - if (ELearningUtils::getConfigValue("user_category_id", $cms)) { - $this->user_category_node_id = ELearningUtils::getConfigValue("user_category_id", $cms); - } else { - $this->user_category_node_id = $this->main_category_node_id; - } - if (ELearningUtils::getConfigValue("ldap_enable", $cms)) { - $this->ldap_enable = ELearningUtils::getConfigValue("ldap_enable", $cms); - } - } - - /** - * Helper function to fetch children including objects in folders - * The typo in the function name, 'childs', is intentional to reflect the name of the ILIAS-SOAP call. - * - * @access public - * @param string $parent_id - * @return array result - */ - function getChilds($parent_id) { - $types[] = 'fold'; - foreach ($this->types as $type => $name) { - $types[] = $type; - } - - $result = $this->soap_client->getTreeChilds($parent_id, $types, $this->user->getId()); - if ($result) { - $parent_path = $this->soap_client->getRawPath($parent_id) . '_' . $parent_id; - foreach($result as $ref_id => $data) { - // Workaround: getTreeChilds() liefert ALLE Referenzen der beteiligten Objekte, hier sollen aber nur die aus dem Kurs geprüft werden. Deshalb Abgleich der Pfade aller gefundenen Objekt-Referenzen. - if (($data["accessInfo"] != "granted") OR ($this->soap_client->getRawPath($ref_id) != $parent_path)) - unset($result[$ref_id]); - elseif ($data['type'] == 'fold') { - unset($result[$ref_id]); - $result = $result + $this->getChilds($ref_id); - } - } - } - - if (is_array($result)) - return $result; - else - return []; - } - - /** - * check connected modules and update connections - * - * checks if there are modules in the course that are not connected to the seminar - * @access public - * @param string $course_id course-id - */ - function updateConnections($course_id) - { - global $connected_cms, $messages, $object_connections; - - $db = DBManager::get(); - - $result = $this->soap_client->getObjectByReference($course_id); - if ($result) { - $course_path = $this->soap_client->getRawPath($course_id) . '_' . $result["ref_id"]; - } - $this->soap_client->setCachingStatus(false); - // fetch childs - $result = $this->getChilds($course_id); - - if (is_array($result)) { - $check = $db->prepare("SELECT 1 FROM object_contentmodules WHERE object_id = ? AND module_id = ? AND system_type = ? AND module_type = ?"); - $found = []; - $added = 0; - $deleted = 0; - $messages["info"] .= "".sprintf(_("Aktualisierung der Zuordnungen zum System \"%s\":"), $this->getName()) . "
"; - foreach($result as $ref_id => $data) { - $check->execute([Context::getId(), $ref_id, $this->cms_type, $data["type"]]); - if (!$check->fetch()) { - $messages["info"] .= sprintf(_("Zuordnung zur Lerneinheit \"%s\" wurde hinzugefügt."), ($data["title"])) . "
"; - ObjectConnections::setConnection(Context::getId(), $ref_id, $data["type"], $this->cms_type); - $added++; - } - $found[] = $ref_id . '_' . $data["type"]; - } - $to_delete = $db->prepare("SELECT module_id,module_type FROM object_contentmodules WHERE module_type <> 'crs' AND object_id = ? AND system_type = ? AND CONCAT_WS('_', module_id,module_type) NOT IN (?)"); - $to_delete->execute([Context::getId(), $this->cms_type, count($found) ? $found : ['']]); - while ($row = $to_delete->fetch(PDO::FETCH_ASSOC)) { - ObjectConnections::unsetConnection(Context::getId(), $row["module_id"], $row["module_type"], $this->cms_type); - $deleted++; - $messages["info"] .= sprintf(_("Zuordnung zu \"%s\" wurde entfernt."), $row["module_id"] . '_' . $row["module_type"]) . "
"; - } - if (($added + $deleted) < 1) { - $messages["info"] .= _("Die Zuordnungen sind bereits auf dem aktuellen Stand.") . "
"; - } - } - ELearningUtils::bench("update connections"); - } - - /** - * create course - * - * creates new ilias course - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function createCourse($seminar_id) - { - global $messages, $ELEARNING_INTERFACE_MODULES; - - $crs_id = ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - - if ($crs_id == false) { - $seminar = Seminar::getInstance($seminar_id); - $home_institute = Institute::find($seminar->getInstitutId()); - if ($home_institute) { - $ref_id = ObjectConnections::getConnectionModuleId($home_institute->getId(), "cat", $this->cms_type); - } - if ($ref_id < 1) { - // Kategorie für Heimateinrichtung anlegen - $object_data["title"] = sprintf("%s", $home_institute->name); - $object_data["description"] = sprintf(_("Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung \"%s\"."), $home_institute->name); - $object_data["type"] = "cat"; - $object_data["owner"] = $this->soap_client->LookupUser($ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["username"]); - $ref_id = $this->soap_client->addObject($object_data, $this->main_category_node_id); - ObjectConnections::setConnection($home_institute->getId(), $ref_id, "cat", $this->cms_type); - } - if ($ref_id < 1) { - $ref_id = $this->main_category_node_id; - } - - // Kurs anlegen - $lang_array = explode("_", Config::get()->DEFAULT_LANGUAGE); - $course_data["language"] = $lang_array[0]; - $course_data["title"] = "Stud.IP-Kurs " . $seminar->getName(); - $course_data["description"] = ""; - $crs_id = $this->soap_client->addCourse($course_data, $ref_id); - if ($crs_id == false) { - $messages["error"] .= _("Zuordnungs-Fehler: Kurs konnte nicht angelegt werden."); - return false; - } - ObjectConnections::setConnection($seminar_id, $crs_id, "crs", $this->cms_type); - - // Rollen zuordnen - $this->permissions->CheckUserPermissions($crs_id); - } - return $crs_id; - } - - /** - * get preferences - * - * shows additional settings. - * @access public - */ - function getPreferences() - { - global $connected_cms; - - $role_template_name = Request::get('role_template_name'); - $cat_name = Request::get('cat_name'); - - $this->soap_client->setCachingStatus(false); - - $messages = ['error' => '']; - - if ($cat_name) { - $cat = $this->soap_client->getReferenceByTitle( trim( $cat_name ), "cat"); - if (!$cat) { - $messages["error"] .= sprintf(_('Das Objekt mit dem Namen "%s" wurde im System %s nicht gefunden.'), htmlReady($cat_name), htmlReady($this->getName())) . "
\n"; - } else { - ELearningUtils::setConfigValue("category_id", $cat, $this->cms_type); - $this->main_category_node_id = $cat; - } - } - - if (($this->main_category_node_id != false) AND (ELearningUtils::getConfigValue("user_category_id", $this->cms_type) == "")) { - $object_data["title"] = _("User-Daten"); - $object_data["description"] = _("Hier befinden sich die persönlichen Ordner der Stud.IP-User."); - $object_data["type"] = "cat"; - $object_data["owner"] = $this->user->getId(); - $user_cat = $connected_cms[$this->cms_type]->soap_client->addObject($object_data, $connected_cms[$this->cms_type]->main_category_node_id); - if ($user_cat) { - $this->user_category_node_id = $user_cat; - ELearningUtils::setConfigValue("user_category_id", $user_cat, $this->cms_type); - } else { - $messages["error"] .= _("Die Kategorie für User-Daten konnte nicht angelegt werden.") . "
\n"; - } - } - - if ($role_template_name != "") { - $role_template = $this->soap_client->getObjectByTitle( trim( $role_template_name ), "rolt" ); - if ($role_template == false) { - $messages["error"] .= sprintf(_("Das Rollen-Template mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($role_template_name), htmlReady($this->getName())) . "
\n"; - } - if (is_array($role_template)) { - ELearningUtils::setConfigValue("user_role_template_id", $role_template["obj_id"], $this->cms_type); - ELearningUtils::setConfigValue("user_role_template_name", $role_template["title"], $this->cms_type); - $this->user_role_template_id = $role_template["obj_id"]; - } - } - - if (Request::submitted('submit')) { - ELearningUtils::setConfigValue("encrypt_passwords", Request::option("encrypt_passwords"), $this->cms_type); - $encrypt_passwords = Request::option("encrypt_passwords"); - ELearningUtils::setConfigValue("ldap_enable", Request::option("ldap_enable"), $this->cms_type); - $this->ldap_enable = Request::option("ldap_enable"); - } else { - if (ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type) != "") - $encrypt_passwords = ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type); - } - - $cat = $this->soap_client->getObjectByReference( $this->main_category_node_id ); - $user_cat = $this->soap_client->getObjectByReference( $this->user_category_node_id ); - $title = $this->link->getModuleLink($user_cat["title"], $this->user_category_node_id, "cat"); - $ldap_options = []; - foreach (StudipAuthAbstract::GetInstance() as $plugin) { - if ($plugin instanceof StudipAuthLdap) { - $ldap_options[] = ''; - } - } - ob_start(); - ConnectedCMS::getPreferences(); - $module_types = ob_get_clean(); - - $template = $GLOBALS['template_factory']->open('elearning/ilias4_connected_cms_preferences.php'); - $template->set_attribute('messages', $messages); - $template->set_attribute('soap_error', $this->soap_client->getError()); - $template->set_attribute('soap_data', $this->soap_data); - $template->set_attribute('main_category_node_id', $this->main_category_node_id); - $template->set_attribute('main_category_node_id_title', $cat['title']); - $template->set_attribute('user_category_node_id', $this->user_category_node_id); - $template->set_attribute('user_category_node_id_title', $title); - $template->set_attribute('user_role_template_name', ELearningUtils::getConfigValue("user_role_template_name", $this->cms_type)); - $template->set_attribute('user_role_template_id', $this->user_role_template_id); - $template->set_attribute('encrypt_passwords', $encrypt_passwords); - $template->set_attribute('ldap_options', count($ldap_options) ? join("\n", array_merge([''], $ldap_options)) : ''); - $template->set_attribute('module_types', $module_types); - echo $template->render(); - } - -} diff --git a/lib/elearning/Ilias4ConnectedCMS.php b/lib/elearning/Ilias4ConnectedCMS.php new file mode 100644 index 0000000..d45168f --- /dev/null +++ b/lib/elearning/Ilias4ConnectedCMS.php @@ -0,0 +1,272 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias4ConnectedCMS + * @package ELearning-Interface + */ +class Ilias4ConnectedCMS extends Ilias3ConnectedCMS +{ + var $user_category_node_id; + var $ldap_enable; + /** + * constructor + * + * init class. + * @access public + * @param string $cms system-type + */ + function __construct($cms) + { + global $messages; + parent::__construct($cms); + if (ELearningUtils::getConfigValue("user_category_id", $cms)) { + $this->user_category_node_id = ELearningUtils::getConfigValue("user_category_id", $cms); + } else { + $this->user_category_node_id = $this->main_category_node_id; + } + if (ELearningUtils::getConfigValue("ldap_enable", $cms)) { + $this->ldap_enable = ELearningUtils::getConfigValue("ldap_enable", $cms); + } + } + + /** + * Helper function to fetch children including objects in folders + * The typo in the function name, 'childs', is intentional to reflect the name of the ILIAS-SOAP call. + * + * @access public + * @param string $parent_id + * @return array result + */ + function getChilds($parent_id) { + $types[] = 'fold'; + foreach ($this->types as $type => $name) { + $types[] = $type; + } + + $result = $this->soap_client->getTreeChilds($parent_id, $types, $this->user->getId()); + if ($result) { + $parent_path = $this->soap_client->getRawPath($parent_id) . '_' . $parent_id; + foreach($result as $ref_id => $data) { + // Workaround: getTreeChilds() liefert ALLE Referenzen der beteiligten Objekte, hier sollen aber nur die aus dem Kurs geprüft werden. Deshalb Abgleich der Pfade aller gefundenen Objekt-Referenzen. + if (($data["accessInfo"] != "granted") OR ($this->soap_client->getRawPath($ref_id) != $parent_path)) + unset($result[$ref_id]); + elseif ($data['type'] == 'fold') { + unset($result[$ref_id]); + $result = $result + $this->getChilds($ref_id); + } + } + } + + if (is_array($result)) + return $result; + else + return []; + } + + /** + * check connected modules and update connections + * + * checks if there are modules in the course that are not connected to the seminar + * @access public + * @param string $course_id course-id + */ + function updateConnections($course_id) + { + global $connected_cms, $messages, $object_connections; + + $db = DBManager::get(); + + $result = $this->soap_client->getObjectByReference($course_id); + if ($result) { + $course_path = $this->soap_client->getRawPath($course_id) . '_' . $result["ref_id"]; + } + $this->soap_client->setCachingStatus(false); + // fetch childs + $result = $this->getChilds($course_id); + + if (is_array($result)) { + $check = $db->prepare("SELECT 1 FROM object_contentmodules WHERE object_id = ? AND module_id = ? AND system_type = ? AND module_type = ?"); + $found = []; + $added = 0; + $deleted = 0; + $messages["info"] .= "".sprintf(_("Aktualisierung der Zuordnungen zum System \"%s\":"), $this->getName()) . "
"; + foreach($result as $ref_id => $data) { + $check->execute([Context::getId(), $ref_id, $this->cms_type, $data["type"]]); + if (!$check->fetch()) { + $messages["info"] .= sprintf(_("Zuordnung zur Lerneinheit \"%s\" wurde hinzugefügt."), ($data["title"])) . "
"; + ObjectConnections::setConnection(Context::getId(), $ref_id, $data["type"], $this->cms_type); + $added++; + } + $found[] = $ref_id . '_' . $data["type"]; + } + $to_delete = $db->prepare("SELECT module_id,module_type FROM object_contentmodules WHERE module_type <> 'crs' AND object_id = ? AND system_type = ? AND CONCAT_WS('_', module_id,module_type) NOT IN (?)"); + $to_delete->execute([Context::getId(), $this->cms_type, count($found) ? $found : ['']]); + while ($row = $to_delete->fetch(PDO::FETCH_ASSOC)) { + ObjectConnections::unsetConnection(Context::getId(), $row["module_id"], $row["module_type"], $this->cms_type); + $deleted++; + $messages["info"] .= sprintf(_("Zuordnung zu \"%s\" wurde entfernt."), $row["module_id"] . '_' . $row["module_type"]) . "
"; + } + if (($added + $deleted) < 1) { + $messages["info"] .= _("Die Zuordnungen sind bereits auf dem aktuellen Stand.") . "
"; + } + } + ELearningUtils::bench("update connections"); + } + + /** + * create course + * + * creates new ilias course + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function createCourse($seminar_id) + { + global $messages, $ELEARNING_INTERFACE_MODULES; + + $crs_id = ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + + if ($crs_id == false) { + $seminar = Seminar::getInstance($seminar_id); + $home_institute = Institute::find($seminar->getInstitutId()); + if ($home_institute) { + $ref_id = ObjectConnections::getConnectionModuleId($home_institute->getId(), "cat", $this->cms_type); + } + if ($ref_id < 1) { + // Kategorie für Heimateinrichtung anlegen + $object_data["title"] = sprintf("%s", $home_institute->name); + $object_data["description"] = sprintf(_("Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung \"%s\"."), $home_institute->name); + $object_data["type"] = "cat"; + $object_data["owner"] = $this->soap_client->LookupUser($ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["username"]); + $ref_id = $this->soap_client->addObject($object_data, $this->main_category_node_id); + ObjectConnections::setConnection($home_institute->getId(), $ref_id, "cat", $this->cms_type); + } + if ($ref_id < 1) { + $ref_id = $this->main_category_node_id; + } + + // Kurs anlegen + $lang_array = explode("_", Config::get()->DEFAULT_LANGUAGE); + $course_data["language"] = $lang_array[0]; + $course_data["title"] = "Stud.IP-Kurs " . $seminar->getName(); + $course_data["description"] = ""; + $crs_id = $this->soap_client->addCourse($course_data, $ref_id); + if ($crs_id == false) { + $messages["error"] .= _("Zuordnungs-Fehler: Kurs konnte nicht angelegt werden."); + return false; + } + ObjectConnections::setConnection($seminar_id, $crs_id, "crs", $this->cms_type); + + // Rollen zuordnen + $this->permissions->CheckUserPermissions($crs_id); + } + return $crs_id; + } + + /** + * get preferences + * + * shows additional settings. + * @access public + */ + function getPreferences() + { + global $connected_cms; + + $role_template_name = Request::get('role_template_name'); + $cat_name = Request::get('cat_name'); + + $this->soap_client->setCachingStatus(false); + + $messages = ['error' => '']; + + if ($cat_name) { + $cat = $this->soap_client->getReferenceByTitle( trim( $cat_name ), "cat"); + if (!$cat) { + $messages["error"] .= sprintf(_('Das Objekt mit dem Namen "%s" wurde im System %s nicht gefunden.'), htmlReady($cat_name), htmlReady($this->getName())) . "
\n"; + } else { + ELearningUtils::setConfigValue("category_id", $cat, $this->cms_type); + $this->main_category_node_id = $cat; + } + } + + if (($this->main_category_node_id != false) AND (ELearningUtils::getConfigValue("user_category_id", $this->cms_type) == "")) { + $object_data["title"] = _("User-Daten"); + $object_data["description"] = _("Hier befinden sich die persönlichen Ordner der Stud.IP-User."); + $object_data["type"] = "cat"; + $object_data["owner"] = $this->user->getId(); + $user_cat = $connected_cms[$this->cms_type]->soap_client->addObject($object_data, $connected_cms[$this->cms_type]->main_category_node_id); + if ($user_cat) { + $this->user_category_node_id = $user_cat; + ELearningUtils::setConfigValue("user_category_id", $user_cat, $this->cms_type); + } else { + $messages["error"] .= _("Die Kategorie für User-Daten konnte nicht angelegt werden.") . "
\n"; + } + } + + if ($role_template_name != "") { + $role_template = $this->soap_client->getObjectByTitle( trim( $role_template_name ), "rolt" ); + if ($role_template == false) { + $messages["error"] .= sprintf(_("Das Rollen-Template mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($role_template_name), htmlReady($this->getName())) . "
\n"; + } + if (is_array($role_template)) { + ELearningUtils::setConfigValue("user_role_template_id", $role_template["obj_id"], $this->cms_type); + ELearningUtils::setConfigValue("user_role_template_name", $role_template["title"], $this->cms_type); + $this->user_role_template_id = $role_template["obj_id"]; + } + } + + if (Request::submitted('submit')) { + ELearningUtils::setConfigValue("encrypt_passwords", Request::option("encrypt_passwords"), $this->cms_type); + $encrypt_passwords = Request::option("encrypt_passwords"); + ELearningUtils::setConfigValue("ldap_enable", Request::option("ldap_enable"), $this->cms_type); + $this->ldap_enable = Request::option("ldap_enable"); + } else { + if (ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type) != "") + $encrypt_passwords = ELearningUtils::getConfigValue("encrypt_passwords", $this->cms_type); + } + + $cat = $this->soap_client->getObjectByReference( $this->main_category_node_id ); + $user_cat = $this->soap_client->getObjectByReference( $this->user_category_node_id ); + $title = $this->link->getModuleLink($user_cat["title"], $this->user_category_node_id, "cat"); + $ldap_options = []; + foreach (StudipAuthAbstract::GetInstance() as $plugin) { + if ($plugin instanceof StudipAuthLdap) { + $ldap_options[] = ''; + } + } + ob_start(); + ConnectedCMS::getPreferences(); + $module_types = ob_get_clean(); + + $template = $GLOBALS['template_factory']->open('elearning/ilias4_connected_cms_preferences.php'); + $template->set_attribute('messages', $messages); + $template->set_attribute('soap_error', $this->soap_client->getError()); + $template->set_attribute('soap_data', $this->soap_data); + $template->set_attribute('main_category_node_id', $this->main_category_node_id); + $template->set_attribute('main_category_node_id_title', $cat['title']); + $template->set_attribute('user_category_node_id', $this->user_category_node_id); + $template->set_attribute('user_category_node_id_title', $title); + $template->set_attribute('user_role_template_name', ELearningUtils::getConfigValue("user_role_template_name", $this->cms_type)); + $template->set_attribute('user_role_template_id', $this->user_role_template_id); + $template->set_attribute('encrypt_passwords', $encrypt_passwords); + $template->set_attribute('ldap_options', count($ldap_options) ? join("\n", array_merge([''], $ldap_options)) : ''); + $template->set_attribute('module_types', $module_types); + echo $template->render(); + } + +} diff --git a/lib/elearning/Ilias4ConnectedLink.class.php b/lib/elearning/Ilias4ConnectedLink.class.php deleted file mode 100644 index a2bdb14..0000000 --- a/lib/elearning/Ilias4ConnectedLink.class.php +++ /dev/null @@ -1,124 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias4ConnectedLink - * @package ELearning-Interface - */ -class Ilias4ConnectedLink extends Ilias3ConnectedLink -{ - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms) - { - parent::__construct($cms); - $this->cms_link = "ilias3_referrer.php"; - } - - /** - * get module link - * - * returns link to the specified ilias object. works without initializing module-class. - * @access public - * @return string html-code - */ - function getModuleLink($title, $module_id, $module_type) - { - global $connected_cms, $view, $search_key, $cms_select, $current_module; - - if ($connected_cms[$this->cms_type]->isAuthNecessary() AND (! $connected_cms[$this->cms_type]->user->isConnected())) { - return false; - } - $output = "cms_link . "?" - . "client_id=" . $connected_cms[$this->cms_type]->getClientId() - . "&cms_select=" . $this->cms_type - . "&ref_id=" . $module_id - . "&type=" . $module_type - . "&target=start"). "\" target=\"_blank\" rel=\"noopener noreferrer\">"; - $output .= $title; - $output .= " "; - - return $output; - } - - /** - * get admin module links - * - * returns links add or remove a module from course - * @access public - * @return string returns html-code - */ - function getAdminModuleLinks() - { - global $connected_cms, $view, $search_key, $cms_select, $current_module; - - $output = ''; - - $result = false; - if (!$connected_cms[$this->cms_type]->content_module[$current_module]->isDummy()) { - $result = $connected_cms[$this->cms_type]->soap_client->getPath($connected_cms[$this->cms_type]->content_module[$current_module]->getId()); - } - if ($result) { - $output .= "Pfad: ". htmlReady($result) . "

"; - } - $output .= "
\n"; - $output .= CSRFProtection::tokenTag(); - $output .= "\n"; - $output .= "\n"; - $output .= "\n"; - $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; - $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; - $output .= "cms_type) . "\">\n"; - - if ($connected_cms[$this->cms_type]->content_module[$current_module]->isConnected()) { - $output .= " " . Button::create(_('Entfernen'), 'remove'); - } elseif ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_WRITE)) { - $output .= "
"; - if ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_COPY) AND (! in_array($connected_cms[$this->cms_type]->content_module[$current_module]->module_type, ["lm", "htlm", "sahs", "cat", "crs", "dbk"]))) { - $output .= ""; - $output .= _("Als Kopie anlegen") . " "; - $output .= Icon::create('info-circle', 'inactive', ['title' => _('Wenn Sie diese Option wählen, wird eine identische Kopie als eigenständige Instanz des Lernmoduls erstellt. Anderenfalls wird ein Link zum Lernmodul gesetzt.')])->asImg(); - $output .= "
"; - } - $output .= ""; - $output .= _("Keine Schreibrechte") . " "; - $output .= Icon::create('info-circle', 'inactive', ['title' => _('Nur der/die BesitzerIn des Lernmoduls hat Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); - $output .= "
"; - $output .= ""; - $output .= _("Mit Schreibrechten für alle Lehrenden dieser Veranstaltung") . " "; - $output .= Icon::create('info-circle', 'inactive', ['title' => _('Lehrende haben Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); - $output .= "
"; - $output .= ""; - $output .= _("Mit Schreibrechten für alle Lehrenden und Tutor/-innen dieser Veranstaltung") . " "; - $output .= Icon::create('info-circle', 'inactive', ['title' => _('Lehrende und Tutor/-innen haben Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); - $output .= "
"; - $output .= ""; - $output .= _("Mit Schreibrechten für alle Personen dieser Veranstaltung") . " "; - $output .= Icon::create('info-circle', 'inactive', ['title' => _('Lehrende, Tutor/-innen und Teilnehmende haben Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); - $output .= "
"; - $output .= "
" . Button::create(_('Hinzufügen'), 'add') . "
"; - } else { - $output .= " " . Button::create(_('Hinzufügen'), 'add'); - } - $output .= "
"; - - return $output; - } -} diff --git a/lib/elearning/Ilias4ConnectedLink.php b/lib/elearning/Ilias4ConnectedLink.php new file mode 100644 index 0000000..a2bdb14 --- /dev/null +++ b/lib/elearning/Ilias4ConnectedLink.php @@ -0,0 +1,124 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias4ConnectedLink + * @package ELearning-Interface + */ +class Ilias4ConnectedLink extends Ilias3ConnectedLink +{ + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms) + { + parent::__construct($cms); + $this->cms_link = "ilias3_referrer.php"; + } + + /** + * get module link + * + * returns link to the specified ilias object. works without initializing module-class. + * @access public + * @return string html-code + */ + function getModuleLink($title, $module_id, $module_type) + { + global $connected_cms, $view, $search_key, $cms_select, $current_module; + + if ($connected_cms[$this->cms_type]->isAuthNecessary() AND (! $connected_cms[$this->cms_type]->user->isConnected())) { + return false; + } + $output = "cms_link . "?" + . "client_id=" . $connected_cms[$this->cms_type]->getClientId() + . "&cms_select=" . $this->cms_type + . "&ref_id=" . $module_id + . "&type=" . $module_type + . "&target=start"). "\" target=\"_blank\" rel=\"noopener noreferrer\">"; + $output .= $title; + $output .= " "; + + return $output; + } + + /** + * get admin module links + * + * returns links add or remove a module from course + * @access public + * @return string returns html-code + */ + function getAdminModuleLinks() + { + global $connected_cms, $view, $search_key, $cms_select, $current_module; + + $output = ''; + + $result = false; + if (!$connected_cms[$this->cms_type]->content_module[$current_module]->isDummy()) { + $result = $connected_cms[$this->cms_type]->soap_client->getPath($connected_cms[$this->cms_type]->content_module[$current_module]->getId()); + } + if ($result) { + $output .= "Pfad: ". htmlReady($result) . "

"; + } + $output .= "
\n"; + $output .= CSRFProtection::tokenTag(); + $output .= "\n"; + $output .= "\n"; + $output .= "\n"; + $output .= "cms_type]->content_module[$current_module]->getModuleType()) . "\">\n"; + $output .= "cms_type]->content_module[$current_module]->getId()) . "\">\n"; + $output .= "cms_type) . "\">\n"; + + if ($connected_cms[$this->cms_type]->content_module[$current_module]->isConnected()) { + $output .= " " . Button::create(_('Entfernen'), 'remove'); + } elseif ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_WRITE)) { + $output .= "
"; + if ($connected_cms[$this->cms_type]->content_module[$current_module]->isAllowed(OPERATION_COPY) AND (! in_array($connected_cms[$this->cms_type]->content_module[$current_module]->module_type, ["lm", "htlm", "sahs", "cat", "crs", "dbk"]))) { + $output .= ""; + $output .= _("Als Kopie anlegen") . " "; + $output .= Icon::create('info-circle', 'inactive', ['title' => _('Wenn Sie diese Option wählen, wird eine identische Kopie als eigenständige Instanz des Lernmoduls erstellt. Anderenfalls wird ein Link zum Lernmodul gesetzt.')])->asImg(); + $output .= "
"; + } + $output .= ""; + $output .= _("Keine Schreibrechte") . " "; + $output .= Icon::create('info-circle', 'inactive', ['title' => _('Nur der/die BesitzerIn des Lernmoduls hat Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); + $output .= "
"; + $output .= ""; + $output .= _("Mit Schreibrechten für alle Lehrenden dieser Veranstaltung") . " "; + $output .= Icon::create('info-circle', 'inactive', ['title' => _('Lehrende haben Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); + $output .= "
"; + $output .= ""; + $output .= _("Mit Schreibrechten für alle Lehrenden und Tutor/-innen dieser Veranstaltung") . " "; + $output .= Icon::create('info-circle', 'inactive', ['title' => _('Lehrende und Tutor/-innen haben Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); + $output .= "
"; + $output .= ""; + $output .= _("Mit Schreibrechten für alle Personen dieser Veranstaltung") . " "; + $output .= Icon::create('info-circle', 'inactive', ['title' => _('Lehrende, Tutor/-innen und Teilnehmende haben Schreibzugriff für Inhalte und Struktur des Lernmoduls. Tutor/-innen und Lehrende können die Verknüpfung zur Veranstaltung wieder löschen.')])->asImg(); + $output .= "
"; + $output .= "
" . Button::create(_('Hinzufügen'), 'add') . "
"; + } else { + $output .= " " . Button::create(_('Hinzufügen'), 'add'); + } + $output .= "
"; + + return $output; + } +} diff --git a/lib/elearning/Ilias4ConnectedPermissions.class.php b/lib/elearning/Ilias4ConnectedPermissions.class.php deleted file mode 100644 index af70ba7..0000000 --- a/lib/elearning/Ilias4ConnectedPermissions.class.php +++ /dev/null @@ -1,126 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias4ConnectedPermission - * @package ELearning-Interface - */ -class Ilias4ConnectedPermissions extends Ilias3ConnectedPermissions -{ - var $operations; - var $allowed_operations; - var $tree_allowed_operations; - - var $USER_OPERATIONS; - var $AUTHOR_OPERATIONS; - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms) - { - parent::__construct($cms); - } - - /** - * check user permissions - * - * checks user permissions for connected course and changes setting if necessary - * @access public - * @param string $course_id course-id - * @return boolean returns false on error - */ - function checkUserPermissions($course_id = "") - { - global $connected_cms, $messages; - - if ($course_id == "") return false; - if ($connected_cms[$this->cms_type]->user->getId() == "") return false; - - // get course role folder and local roles - $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($course_id); - $active_role = ""; - $proper_role = ""; - $user_crs_role = $connected_cms[$this->cms_type]->crs_roles[$GLOBALS["perm"]->get_studip_perm(Context::getId())]; - if (is_array($local_roles)) { - foreach ($local_roles as $key => $role_data) { - // check only if local role is il_crs_member, -tutor or -admin - if (! (mb_strpos($role_data["title"], "_crs_") === false)) { - if ( in_array( $role_data["obj_id"], $connected_cms[$this->cms_type]->user->getRoles() ) ) { - $active_role = $role_data["obj_id"]; - } - if ( mb_strpos( $role_data["title"], $user_crs_role) > 0 ) { - $proper_role = $role_data["obj_id"]; - } - } - } - } - - // is user already course-member? otherwise add member with proper role - $is_member = $connected_cms[$this->cms_type]->soap_client->isMember( $connected_cms[$this->cms_type]->user->getId(), $course_id); - if (!$is_member) { - $member_data["usr_id"] = $connected_cms[$this->cms_type]->user->getId(); - $member_data["ref_id"] = $course_id; - $member_data["status"] = CRS_NO_NOTIFICATION; - $type = ""; - switch ($user_crs_role) - { - case "admin": - $member_data["role"] = CRS_ADMIN_ROLE; - $type = "Admin"; - break; - case "tutor": - $member_data["role"] = CRS_TUTOR_ROLE; - $type = "Tutor"; - break; - case "member": - $member_data["role"] = CRS_MEMBER_ROLE; - $type = "Member"; - break; - default: - } - $member_data["passed"] = CRS_PASSED_VALUE; - if ($type != "") { - $connected_cms[$this->cms_type]->soap_client->addMember( $connected_cms[$this->cms_type]->user->getId(), $type, $course_id ); - if ($GLOBALS["debug"] == true) echo "addMember"; - } - } - - // check if user has proper local role - // if not, change it - if ($active_role != $proper_role) { - if ($active_role != "") { - $connected_cms[$this->cms_type]->soap_client->deleteUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $active_role); - if ($GLOBALS["debug"] == true) echo "Role $active_role deleted."; - } - - if ($proper_role != "") { - $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $proper_role); - if ($GLOBALS["debug"] == true) echo "Role $proper_role added."; - } - - } - - if (! $this->getContentModulePerms( $course_id )) { - $messages["info"] .= _("Für den zugeordneten ILIAS-Kurs konnten keine Berechtigungen ermittelt werden.") . "
"; - } - - return true; - } -} -?> diff --git a/lib/elearning/Ilias4ConnectedPermissions.php b/lib/elearning/Ilias4ConnectedPermissions.php new file mode 100644 index 0000000..af70ba7 --- /dev/null +++ b/lib/elearning/Ilias4ConnectedPermissions.php @@ -0,0 +1,126 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias4ConnectedPermission + * @package ELearning-Interface + */ +class Ilias4ConnectedPermissions extends Ilias3ConnectedPermissions +{ + var $operations; + var $allowed_operations; + var $tree_allowed_operations; + + var $USER_OPERATIONS; + var $AUTHOR_OPERATIONS; + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms) + { + parent::__construct($cms); + } + + /** + * check user permissions + * + * checks user permissions for connected course and changes setting if necessary + * @access public + * @param string $course_id course-id + * @return boolean returns false on error + */ + function checkUserPermissions($course_id = "") + { + global $connected_cms, $messages; + + if ($course_id == "") return false; + if ($connected_cms[$this->cms_type]->user->getId() == "") return false; + + // get course role folder and local roles + $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($course_id); + $active_role = ""; + $proper_role = ""; + $user_crs_role = $connected_cms[$this->cms_type]->crs_roles[$GLOBALS["perm"]->get_studip_perm(Context::getId())]; + if (is_array($local_roles)) { + foreach ($local_roles as $key => $role_data) { + // check only if local role is il_crs_member, -tutor or -admin + if (! (mb_strpos($role_data["title"], "_crs_") === false)) { + if ( in_array( $role_data["obj_id"], $connected_cms[$this->cms_type]->user->getRoles() ) ) { + $active_role = $role_data["obj_id"]; + } + if ( mb_strpos( $role_data["title"], $user_crs_role) > 0 ) { + $proper_role = $role_data["obj_id"]; + } + } + } + } + + // is user already course-member? otherwise add member with proper role + $is_member = $connected_cms[$this->cms_type]->soap_client->isMember( $connected_cms[$this->cms_type]->user->getId(), $course_id); + if (!$is_member) { + $member_data["usr_id"] = $connected_cms[$this->cms_type]->user->getId(); + $member_data["ref_id"] = $course_id; + $member_data["status"] = CRS_NO_NOTIFICATION; + $type = ""; + switch ($user_crs_role) + { + case "admin": + $member_data["role"] = CRS_ADMIN_ROLE; + $type = "Admin"; + break; + case "tutor": + $member_data["role"] = CRS_TUTOR_ROLE; + $type = "Tutor"; + break; + case "member": + $member_data["role"] = CRS_MEMBER_ROLE; + $type = "Member"; + break; + default: + } + $member_data["passed"] = CRS_PASSED_VALUE; + if ($type != "") { + $connected_cms[$this->cms_type]->soap_client->addMember( $connected_cms[$this->cms_type]->user->getId(), $type, $course_id ); + if ($GLOBALS["debug"] == true) echo "addMember"; + } + } + + // check if user has proper local role + // if not, change it + if ($active_role != $proper_role) { + if ($active_role != "") { + $connected_cms[$this->cms_type]->soap_client->deleteUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $active_role); + if ($GLOBALS["debug"] == true) echo "Role $active_role deleted."; + } + + if ($proper_role != "") { + $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry( $connected_cms[$this->cms_type]->user->getId(), $proper_role); + if ($GLOBALS["debug"] == true) echo "Role $proper_role added."; + } + + } + + if (! $this->getContentModulePerms( $course_id )) { + $messages["info"] .= _("Für den zugeordneten ILIAS-Kurs konnten keine Berechtigungen ermittelt werden.") . "
"; + } + + return true; + } +} +?> diff --git a/lib/elearning/Ilias4ConnectedUser.class.php b/lib/elearning/Ilias4ConnectedUser.class.php deleted file mode 100644 index be8b88c..0000000 --- a/lib/elearning/Ilias4ConnectedUser.class.php +++ /dev/null @@ -1,168 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias4ConnectedUser - * @package ELearning-Interface - */ -class Ilias4ConnectedUser extends Ilias3ConnectedUser -{ - var $roles; - var $user_sid; - var $auth_plugin; - - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms, $user_id = false) - { - // get auth_plugin - $user_id = $user_id ? $user_id : $GLOBALS['user']->id; - $this->auth_plugin = DBManager::get()->query("SELECT IFNULL(auth_plugin, 'standard') FROM auth_user_md5 WHERE user_id = '" . $user_id . "'")->fetchColumn(); - parent::__construct($cms, $user_id); - } - - /** - * new user - * - * save new user - * @access public - * @return boolean returns false on error - */ - function newUser($ignore_encrypt_passwords = false) - { - global $connected_cms, $auth, $messages; - - if ($this->getLoginData($this->login)) { - //automatische Zuordnung von bestehenden Ilias Accounts - //nur wenn ldap Modus benutzt wird und Stud.IP Nutzer passendes ldap plugin hat - if ( - $connected_cms[$this->cms_type]->USER_AUTO_CREATE - && !$connected_cms[$this->cms_type]->USER_PREFIX - && $this->auth_plugin - && $this->auth_plugin !== 'standard' - && $this->auth_plugin === $connected_cms[$this->cms_type]->ldap_enable - ) { - if (!$this->external_password) { - $this->setPassword(md5(uniqid("4dfmjsnll"))); - } - $ok = $connected_cms[$this->cms_type]->soap_client->updatePassword($this->id, $this->external_password); - $this->setConnection($this->getUserType(), true); - if ($ok) { - $messages["info"] .= sprintf(_("Verbindung mit Nutzer ID %s wiederhergestellt."), $this->id); - } - return true; - } - $messages["error"] .= sprintf(_("Es existiert bereits ein Account mit dem Benutzernamen \"%s\" (Account ID %s)."), $this->login, $this->id) . "
\n"; - return false; - } - - // data for user-account in ILIAS 4 - $user_data["login"] = $this->login; - $user_data["passwd"] = $this->external_password; - $user_data["firstname"] = $this->firstname; - $user_data["lastname"] = $this->lastname; - $user_data["title"] = $this->title; - $user_data["gender"] = $this->gender; - $user_data["email"] = $this->email; - $user_data["street"] = $this->street; - $user_data["phone_home"] = $this->phone_home; - $user_data["time_limit_unlimited"] = 1; - $user_data["active"] = 1; - $user_data["approve_date"] = date('Y-m-d H:i:s'); - $user_data["accepted_agreement"] = true; - // new values for ILIAS 4 - $user_data["agree_date"] = date('Y-m-d H:i:s'); - $user_data["external_account"] = $this->login; - if ($this->auth_plugin && $this->auth_plugin != "standard" && ($this->auth_plugin == $connected_cms[$this->cms_type]->ldap_enable)) { - $user_data["auth_mode"] = "ldap"; - } else { - $user_data["auth_mode"] = "default"; - } - if ($connected_cms[$this->cms_type]->user_style != "") { - $user_data["user_style"] = $connected_cms[$this->cms_type]->user_style; - } - if ($connected_cms[$this->cms_type]->user_skin != "") { - $user_data["user_skin"] = $connected_cms[$this->cms_type]->user_skin; - } - - $role_id = $connected_cms[$this->cms_type]->roles[$auth->auth["perm"]]; - - $user_id = $connected_cms[$this->cms_type]->soap_client->addUser($user_data, $role_id); - - if ($user_id != false) { - $this->id = $user_id; - - // $connected_cms[$this->cms_type]->soap_client->updatePassword($user_id, $user_data["passwd"]); - - // $this->newUserCategory(); - - $this->setConnection(USER_TYPE_CREATED); - return true; - } - return false; - } - - /** - * create new user category - * - * create new user category - * @access public - * @return boolean returns false on error - */ - function newUserCategory() - { - global $connected_cms, $messages; - - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - - // data for user-category in ILIAS 4 - $object_data["title"] = sprintf(_("Eigene Daten von %s (%s)."), $this->getName(), $this->getId()); - $object_data["description"] = sprintf(_("Hier befinden sich die persönlichen Lernmodule des Benutzers %s."), $this->getName()); - $object_data["type"] = "cat"; - $object_data["owner"] = $this->getId(); - - $cat = $connected_cms[$this->cms_type]->soap_client->getReferenceByTitle($object_data["title"]); - if ($cat != false && $connected_cms[$this->cms_type]->soap_client->checkReferenceById($cat) ) { - $messages["info"] .= sprintf(_("Ihre persönliche Kategorie wurde bereits angelegt."), $this->login) . "
\n"; - $this->category = $cat; - } else { - $this->category = $connected_cms[$this->cms_type]->soap_client->addObject($object_data, $connected_cms[$this->cms_type]->user_category_node_id); - } - if ($this->category != false) { - parent::setConnection($this->getUserType(), true); - } else { - echo "CATEGORY_ERROR".$connected_cms[$this->cms_type]->user_category_node_id ."-"; - return false; - } - // data for personal user-role in ILIAS 4 - $role_data["title"] = "studip_usr" . $this->getId() . "_cat" . $this->category; - $role_data["description"] = sprintf(_("User-Rolle von %s. Diese Rolle wurde von Stud.IP generiert."), $this->getName()); - $role_id = $connected_cms[$this->cms_type]->soap_client->getObjectByTitle($role_data["title"], "role"); - if ($role_id != false) { - $messages["info"] .= sprintf(_("Ihre persönliche Userrolle wurde bereits angelegt."), $this->login) . "
\n"; - } else { - $role_id = $connected_cms[$this->cms_type]->soap_client->addRoleFromTemplate($role_data, $this->category, $connected_cms[$this->cms_type]->user_role_template_id); - } - $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry($this->getId(), $role_id); - // delete permissions for all global roles for this category - foreach ($connected_cms[$this->cms_type]->global_roles as $key => $role) { - $connected_cms[$this->cms_type]->soap_client->revokePermissions($role, $this->category); - } - return true; - } -} diff --git a/lib/elearning/Ilias4ConnectedUser.php b/lib/elearning/Ilias4ConnectedUser.php new file mode 100644 index 0000000..be8b88c --- /dev/null +++ b/lib/elearning/Ilias4ConnectedUser.php @@ -0,0 +1,168 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias4ConnectedUser + * @package ELearning-Interface + */ +class Ilias4ConnectedUser extends Ilias3ConnectedUser +{ + var $roles; + var $user_sid; + var $auth_plugin; + + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms, $user_id = false) + { + // get auth_plugin + $user_id = $user_id ? $user_id : $GLOBALS['user']->id; + $this->auth_plugin = DBManager::get()->query("SELECT IFNULL(auth_plugin, 'standard') FROM auth_user_md5 WHERE user_id = '" . $user_id . "'")->fetchColumn(); + parent::__construct($cms, $user_id); + } + + /** + * new user + * + * save new user + * @access public + * @return boolean returns false on error + */ + function newUser($ignore_encrypt_passwords = false) + { + global $connected_cms, $auth, $messages; + + if ($this->getLoginData($this->login)) { + //automatische Zuordnung von bestehenden Ilias Accounts + //nur wenn ldap Modus benutzt wird und Stud.IP Nutzer passendes ldap plugin hat + if ( + $connected_cms[$this->cms_type]->USER_AUTO_CREATE + && !$connected_cms[$this->cms_type]->USER_PREFIX + && $this->auth_plugin + && $this->auth_plugin !== 'standard' + && $this->auth_plugin === $connected_cms[$this->cms_type]->ldap_enable + ) { + if (!$this->external_password) { + $this->setPassword(md5(uniqid("4dfmjsnll"))); + } + $ok = $connected_cms[$this->cms_type]->soap_client->updatePassword($this->id, $this->external_password); + $this->setConnection($this->getUserType(), true); + if ($ok) { + $messages["info"] .= sprintf(_("Verbindung mit Nutzer ID %s wiederhergestellt."), $this->id); + } + return true; + } + $messages["error"] .= sprintf(_("Es existiert bereits ein Account mit dem Benutzernamen \"%s\" (Account ID %s)."), $this->login, $this->id) . "
\n"; + return false; + } + + // data for user-account in ILIAS 4 + $user_data["login"] = $this->login; + $user_data["passwd"] = $this->external_password; + $user_data["firstname"] = $this->firstname; + $user_data["lastname"] = $this->lastname; + $user_data["title"] = $this->title; + $user_data["gender"] = $this->gender; + $user_data["email"] = $this->email; + $user_data["street"] = $this->street; + $user_data["phone_home"] = $this->phone_home; + $user_data["time_limit_unlimited"] = 1; + $user_data["active"] = 1; + $user_data["approve_date"] = date('Y-m-d H:i:s'); + $user_data["accepted_agreement"] = true; + // new values for ILIAS 4 + $user_data["agree_date"] = date('Y-m-d H:i:s'); + $user_data["external_account"] = $this->login; + if ($this->auth_plugin && $this->auth_plugin != "standard" && ($this->auth_plugin == $connected_cms[$this->cms_type]->ldap_enable)) { + $user_data["auth_mode"] = "ldap"; + } else { + $user_data["auth_mode"] = "default"; + } + if ($connected_cms[$this->cms_type]->user_style != "") { + $user_data["user_style"] = $connected_cms[$this->cms_type]->user_style; + } + if ($connected_cms[$this->cms_type]->user_skin != "") { + $user_data["user_skin"] = $connected_cms[$this->cms_type]->user_skin; + } + + $role_id = $connected_cms[$this->cms_type]->roles[$auth->auth["perm"]]; + + $user_id = $connected_cms[$this->cms_type]->soap_client->addUser($user_data, $role_id); + + if ($user_id != false) { + $this->id = $user_id; + + // $connected_cms[$this->cms_type]->soap_client->updatePassword($user_id, $user_data["passwd"]); + + // $this->newUserCategory(); + + $this->setConnection(USER_TYPE_CREATED); + return true; + } + return false; + } + + /** + * create new user category + * + * create new user category + * @access public + * @return boolean returns false on error + */ + function newUserCategory() + { + global $connected_cms, $messages; + + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + + // data for user-category in ILIAS 4 + $object_data["title"] = sprintf(_("Eigene Daten von %s (%s)."), $this->getName(), $this->getId()); + $object_data["description"] = sprintf(_("Hier befinden sich die persönlichen Lernmodule des Benutzers %s."), $this->getName()); + $object_data["type"] = "cat"; + $object_data["owner"] = $this->getId(); + + $cat = $connected_cms[$this->cms_type]->soap_client->getReferenceByTitle($object_data["title"]); + if ($cat != false && $connected_cms[$this->cms_type]->soap_client->checkReferenceById($cat) ) { + $messages["info"] .= sprintf(_("Ihre persönliche Kategorie wurde bereits angelegt."), $this->login) . "
\n"; + $this->category = $cat; + } else { + $this->category = $connected_cms[$this->cms_type]->soap_client->addObject($object_data, $connected_cms[$this->cms_type]->user_category_node_id); + } + if ($this->category != false) { + parent::setConnection($this->getUserType(), true); + } else { + echo "CATEGORY_ERROR".$connected_cms[$this->cms_type]->user_category_node_id ."-"; + return false; + } + // data for personal user-role in ILIAS 4 + $role_data["title"] = "studip_usr" . $this->getId() . "_cat" . $this->category; + $role_data["description"] = sprintf(_("User-Rolle von %s. Diese Rolle wurde von Stud.IP generiert."), $this->getName()); + $role_id = $connected_cms[$this->cms_type]->soap_client->getObjectByTitle($role_data["title"], "role"); + if ($role_id != false) { + $messages["info"] .= sprintf(_("Ihre persönliche Userrolle wurde bereits angelegt."), $this->login) . "
\n"; + } else { + $role_id = $connected_cms[$this->cms_type]->soap_client->addRoleFromTemplate($role_data, $this->category, $connected_cms[$this->cms_type]->user_role_template_id); + } + $connected_cms[$this->cms_type]->soap_client->addUserRoleEntry($this->getId(), $role_id); + // delete permissions for all global roles for this category + foreach ($connected_cms[$this->cms_type]->global_roles as $key => $role) { + $connected_cms[$this->cms_type]->soap_client->revokePermissions($role, $this->category); + } + return true; + } +} diff --git a/lib/elearning/Ilias4ContentModule.class.php b/lib/elearning/Ilias4ContentModule.class.php deleted file mode 100644 index f8552c3..0000000 --- a/lib/elearning/Ilias4ContentModule.class.php +++ /dev/null @@ -1,106 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias4ContentModule - * @package ELearning-Interface - */ -class Ilias4ContentModule extends Ilias3ContentModule -{ - var $object_id; - - /** - * constructor - * - * init class. - * @access public - * @param string $module_id module-id - * @param string $module_type module-type - * @param string $cms_type system-type - */ - function __construct($module_id, $module_type, $cms_type) - { - parent::__construct($module_id, $module_type, $cms_type); - } - - /** - * set connection - * - * sets connection with seminar - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function setConnection($seminar_id) - { - global $connected_cms, $messages; - - $write_permission = Request::option("write_permission"); - - $crs_id = ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); - $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); - $connected_cms[$this->cms_type]->soap_client->clearCache(); - - // Check, ob Kurs in ILIAS gelöscht wurde - if (($crs_id != false) AND ($connected_cms[$this->cms_type]->soap_client->getObjectByReference($crs_id) == false)) { - ObjectConnections::unsetConnection($seminar_id, $crs_id, "crs", $this->cms_type); - $messages["info"] .= _("Der zugeordnete ILIAS-Kurs (ID $crs_id) existiert nicht mehr. Ein neuer Kurs wird angelegt.") . "
"; - $crs_id = false; - } - - $crs_id == $connected_cms[$this->cms_type]->createCourse($seminar_id); - - if ($crs_id == false) return false; - - $ref_id = $this->getId(); - if (Request::get("copy_object") == "1") { - $connected_cms[$this->cms_type]->soap_client->user_type = 'user'; - $ref_id = $connected_cms[$this->cms_type]->soap_client->copyObject($this->id, $crs_id); - $connected_cms[$this->cms_type]->soap_client->user_type = 'admin'; - } else { - $ref_id = $connected_cms[$this->cms_type]->soap_client->addReference($this->id, $crs_id); - } - if (!$ref_id) { - $messages["error"] .= _("Zuordnungs-Fehler: Objekt konnte nicht angelegt werden."); - return false; - } - $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($crs_id); - $member_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ]); - $admin_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_WRITE, OPERATION_DELETE]); - $admin_operations_no_delete = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_WRITE]); - $admin_operations_readonly = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_DELETE]); - foreach ($local_roles as $key => $role_data) { - // check only if local role is il_crs_member, -tutor or -admin - if (mb_strpos($role_data["title"], "il_crs_") === 0) { - if(mb_strpos($role_data["title"], 'il_crs_member') === 0){ - $operations = ($write_permission == "autor") ? $admin_operations_no_delete : $member_operations; - } elseif(mb_strpos($role_data["title"], 'il_crs_tutor') === 0){ - $operations = (($write_permission == "tutor") || ($write_permission == "autor")) ? $admin_operations : $admin_operations_readonly; - } elseif(mb_strpos($role_data["title"], 'il_crs_admin') === 0){ - $operations = (($write_permission == "dozent") || ($write_permission == "tutor") || ($write_permission == "autor")) ? $admin_operations : $admin_operations_readonly; - } else { - continue; - } - $connected_cms[$this->cms_type]->soap_client->revokePermissions($role_data["obj_id"], $ref_id); - $connected_cms[$this->cms_type]->soap_client->grantPermissions($operations, $role_data["obj_id"], $ref_id); - } - } - if ($ref_id) { - $this->setId($ref_id); - return ContentModule::setConnection($seminar_id); - } else { - $messages["error"] .= _("Die Zuordnung konnte nicht gespeichert werden."); - } - return false; - } -} diff --git a/lib/elearning/Ilias4ContentModule.php b/lib/elearning/Ilias4ContentModule.php new file mode 100644 index 0000000..f8552c3 --- /dev/null +++ b/lib/elearning/Ilias4ContentModule.php @@ -0,0 +1,106 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias4ContentModule + * @package ELearning-Interface + */ +class Ilias4ContentModule extends Ilias3ContentModule +{ + var $object_id; + + /** + * constructor + * + * init class. + * @access public + * @param string $module_id module-id + * @param string $module_type module-type + * @param string $cms_type system-type + */ + function __construct($module_id, $module_type, $cms_type) + { + parent::__construct($module_id, $module_type, $cms_type); + } + + /** + * set connection + * + * sets connection with seminar + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function setConnection($seminar_id) + { + global $connected_cms, $messages; + + $write_permission = Request::option("write_permission"); + + $crs_id = ObjectConnections::getConnectionModuleId($seminar_id, "crs", $this->cms_type); + $connected_cms[$this->cms_type]->soap_client->setCachingStatus(false); + $connected_cms[$this->cms_type]->soap_client->clearCache(); + + // Check, ob Kurs in ILIAS gelöscht wurde + if (($crs_id != false) AND ($connected_cms[$this->cms_type]->soap_client->getObjectByReference($crs_id) == false)) { + ObjectConnections::unsetConnection($seminar_id, $crs_id, "crs", $this->cms_type); + $messages["info"] .= _("Der zugeordnete ILIAS-Kurs (ID $crs_id) existiert nicht mehr. Ein neuer Kurs wird angelegt.") . "
"; + $crs_id = false; + } + + $crs_id == $connected_cms[$this->cms_type]->createCourse($seminar_id); + + if ($crs_id == false) return false; + + $ref_id = $this->getId(); + if (Request::get("copy_object") == "1") { + $connected_cms[$this->cms_type]->soap_client->user_type = 'user'; + $ref_id = $connected_cms[$this->cms_type]->soap_client->copyObject($this->id, $crs_id); + $connected_cms[$this->cms_type]->soap_client->user_type = 'admin'; + } else { + $ref_id = $connected_cms[$this->cms_type]->soap_client->addReference($this->id, $crs_id); + } + if (!$ref_id) { + $messages["error"] .= _("Zuordnungs-Fehler: Objekt konnte nicht angelegt werden."); + return false; + } + $local_roles = $connected_cms[$this->cms_type]->soap_client->getLocalRoles($crs_id); + $member_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ]); + $admin_operations = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_WRITE, OPERATION_DELETE]); + $admin_operations_no_delete = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_WRITE]); + $admin_operations_readonly = $connected_cms[$this->cms_type]->permissions->getOperationArray([OPERATION_VISIBLE, OPERATION_READ, OPERATION_DELETE]); + foreach ($local_roles as $key => $role_data) { + // check only if local role is il_crs_member, -tutor or -admin + if (mb_strpos($role_data["title"], "il_crs_") === 0) { + if(mb_strpos($role_data["title"], 'il_crs_member') === 0){ + $operations = ($write_permission == "autor") ? $admin_operations_no_delete : $member_operations; + } elseif(mb_strpos($role_data["title"], 'il_crs_tutor') === 0){ + $operations = (($write_permission == "tutor") || ($write_permission == "autor")) ? $admin_operations : $admin_operations_readonly; + } elseif(mb_strpos($role_data["title"], 'il_crs_admin') === 0){ + $operations = (($write_permission == "dozent") || ($write_permission == "tutor") || ($write_permission == "autor")) ? $admin_operations : $admin_operations_readonly; + } else { + continue; + } + $connected_cms[$this->cms_type]->soap_client->revokePermissions($role_data["obj_id"], $ref_id); + $connected_cms[$this->cms_type]->soap_client->grantPermissions($operations, $role_data["obj_id"], $ref_id); + } + } + if ($ref_id) { + $this->setId($ref_id); + return ContentModule::setConnection($seminar_id); + } else { + $messages["error"] .= _("Die Zuordnung konnte nicht gespeichert werden."); + } + return false; + } +} diff --git a/lib/elearning/Ilias4Soap.class.php b/lib/elearning/Ilias4Soap.class.php deleted file mode 100644 index 27c28fb..0000000 --- a/lib/elearning/Ilias4Soap.class.php +++ /dev/null @@ -1,181 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias4Soap - * @package ELearning-Interface - */ -class Ilias4Soap extends Ilias3Soap -{ - var $cms_type; - var $admin_sid; - var $user_sid; - var $user_type; - var $soap_cache; - var $separator_string; - - /** - * constructor - * - * init class. - * @access - * @param string $cms system-type - */ - function __construct($cms) - { - parent::__construct($cms); - $this->separator_string = " / "; - } - - /** - * add user by importUsers - * - * adds new user and sets role-id - * @access public - * @param array user_data user-data - * @param string role_id global role-id for new user - * @return string user-id - */ - function addUser($user_data, $role_id) - { - foreach($user_data as $key => $value) { - $user_data[$key] = htmlReady($user_data[$key]); - } - - $usr_xml = " - - -".$user_data["login"]." -".$user_data["passwd"]." -".$user_data["firstname"]." -".$user_data["lastname"]." -".$user_data["title"]." -".$user_data["gender"]." -".$user_data["email"]." -".$user_data["street"]." -".$user_data["phone_home"]." - -true -".$user_data["time_limit_unlimited"]." -0 -".$user_data["approve_date"]." -".$user_data["agree_date"].""; - if (($user_data["user_skin"] != "") OR ($user_data["user_style"] != "")) { - $usr_xml .= ""; - } - $usr_xml .= " -".$user_data["external_account"]." - -"; - - $param = [ - 'sid' => $this->getSID(), - 'folder_id' => -1, - 'usr_xml' => $usr_xml, - 'conflict_role' => 1, - 'send_account_mail' => 0 - ]; - $result = $this->call('importUsers', $param); - - $s = simplexml_load_string($result); - - if ((string)$s->rows->row->column[3] == "successful") - return (string)$s->rows->row->column[0]; - else - return false; - } - - /** - * copy object - * - * copy ilias-object - * @access public - * @param string source_id reference-id - * @param string target_id reference-id - * @return string result - */ - function copyObject($source_id, $target_id) - { - $xml = ""; - - $param = [ - 'sid' => $this->getSID(), - 'xml' => $xml - ]; - return $this->call('copyObject', $param); - } - - /** - * get path - * - * returns repository-path to ilias-object - * @access public - * @param string source_id reference-id - * @param string target_id reference-id - * @return string result - */ - function getPath($ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id - ]; - $result = $this->call('getPathForRefId', $param); - - if ($result) { - $s = simplexml_load_string($result); - - foreach ($s->rows->row as $row) { - $path[] = (string)$row->column[2]; - } - } - - if (is_array($path)) { - return implode($this->separator_string, $path); - } else { - return false; - } - } - - /** - * - * returns repository-path to ilias-object - * - * @access public - * @param string source_id reference-id - * @param string target_id reference-id - * @return string result - */ - function getRawPath($ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id - ]; - $result = $this->call('getPathForRefId', $param); - - if ($result) { - $s = simplexml_load_string($result); - - foreach ($s->rows->row as $row) { - $path[] = (string)$row->column[0]; - } - } - - if (is_array($path)) { - return implode('_', $path); - } else { - return false; - } - } -} diff --git a/lib/elearning/Ilias4Soap.php b/lib/elearning/Ilias4Soap.php new file mode 100644 index 0000000..27c28fb --- /dev/null +++ b/lib/elearning/Ilias4Soap.php @@ -0,0 +1,181 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias4Soap + * @package ELearning-Interface + */ +class Ilias4Soap extends Ilias3Soap +{ + var $cms_type; + var $admin_sid; + var $user_sid; + var $user_type; + var $soap_cache; + var $separator_string; + + /** + * constructor + * + * init class. + * @access + * @param string $cms system-type + */ + function __construct($cms) + { + parent::__construct($cms); + $this->separator_string = " / "; + } + + /** + * add user by importUsers + * + * adds new user and sets role-id + * @access public + * @param array user_data user-data + * @param string role_id global role-id for new user + * @return string user-id + */ + function addUser($user_data, $role_id) + { + foreach($user_data as $key => $value) { + $user_data[$key] = htmlReady($user_data[$key]); + } + + $usr_xml = " + + +".$user_data["login"]." +".$user_data["passwd"]." +".$user_data["firstname"]." +".$user_data["lastname"]." +".$user_data["title"]." +".$user_data["gender"]." +".$user_data["email"]." +".$user_data["street"]." +".$user_data["phone_home"]." + +true +".$user_data["time_limit_unlimited"]." +0 +".$user_data["approve_date"]." +".$user_data["agree_date"].""; + if (($user_data["user_skin"] != "") OR ($user_data["user_style"] != "")) { + $usr_xml .= ""; + } + $usr_xml .= " +".$user_data["external_account"]." + +"; + + $param = [ + 'sid' => $this->getSID(), + 'folder_id' => -1, + 'usr_xml' => $usr_xml, + 'conflict_role' => 1, + 'send_account_mail' => 0 + ]; + $result = $this->call('importUsers', $param); + + $s = simplexml_load_string($result); + + if ((string)$s->rows->row->column[3] == "successful") + return (string)$s->rows->row->column[0]; + else + return false; + } + + /** + * copy object + * + * copy ilias-object + * @access public + * @param string source_id reference-id + * @param string target_id reference-id + * @return string result + */ + function copyObject($source_id, $target_id) + { + $xml = ""; + + $param = [ + 'sid' => $this->getSID(), + 'xml' => $xml + ]; + return $this->call('copyObject', $param); + } + + /** + * get path + * + * returns repository-path to ilias-object + * @access public + * @param string source_id reference-id + * @param string target_id reference-id + * @return string result + */ + function getPath($ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id + ]; + $result = $this->call('getPathForRefId', $param); + + if ($result) { + $s = simplexml_load_string($result); + + foreach ($s->rows->row as $row) { + $path[] = (string)$row->column[2]; + } + } + + if (is_array($path)) { + return implode($this->separator_string, $path); + } else { + return false; + } + } + + /** + * + * returns repository-path to ilias-object + * + * @access public + * @param string source_id reference-id + * @param string target_id reference-id + * @return string result + */ + function getRawPath($ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id + ]; + $result = $this->call('getPathForRefId', $param); + + if ($result) { + $s = simplexml_load_string($result); + + foreach ($s->rows->row as $row) { + $path[] = (string)$row->column[0]; + } + } + + if (is_array($path)) { + return implode('_', $path); + } else { + return false; + } + } +} diff --git a/lib/elearning/Ilias5ConnectedCMS.class.php b/lib/elearning/Ilias5ConnectedCMS.class.php deleted file mode 100644 index 011d046..0000000 --- a/lib/elearning/Ilias5ConnectedCMS.class.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias5ConnectedCMS - * @package ELearning-Interface - */ -class Ilias5ConnectedCMS extends Ilias4ConnectedCMS -{ - -} diff --git a/lib/elearning/Ilias5ConnectedCMS.php b/lib/elearning/Ilias5ConnectedCMS.php new file mode 100644 index 0000000..011d046 --- /dev/null +++ b/lib/elearning/Ilias5ConnectedCMS.php @@ -0,0 +1,21 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias5ConnectedCMS + * @package ELearning-Interface + */ +class Ilias5ConnectedCMS extends Ilias4ConnectedCMS +{ + +} diff --git a/lib/elearning/Ilias5ConnectedLink.class.php b/lib/elearning/Ilias5ConnectedLink.class.php deleted file mode 100644 index 0ce4a0f..0000000 --- a/lib/elearning/Ilias5ConnectedLink.class.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias5ConnectedLink - * @package ELearning-Interface - */ -class Ilias5ConnectedLink extends Ilias4ConnectedLink -{ -} -?> \ No newline at end of file diff --git a/lib/elearning/Ilias5ConnectedLink.php b/lib/elearning/Ilias5ConnectedLink.php new file mode 100644 index 0000000..0ce4a0f --- /dev/null +++ b/lib/elearning/Ilias5ConnectedLink.php @@ -0,0 +1,16 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias5ConnectedLink + * @package ELearning-Interface + */ +class Ilias5ConnectedLink extends Ilias4ConnectedLink +{ +} +?> \ No newline at end of file diff --git a/lib/elearning/Ilias5ConnectedPermissions.class.php b/lib/elearning/Ilias5ConnectedPermissions.class.php deleted file mode 100644 index 17d6f0d..0000000 --- a/lib/elearning/Ilias5ConnectedPermissions.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias4ConnectedPermission - * @package ELearning-Interface - */ -class Ilias5ConnectedPermissions extends Ilias4ConnectedPermissions -{ - -} -?> \ No newline at end of file diff --git a/lib/elearning/Ilias5ConnectedPermissions.php b/lib/elearning/Ilias5ConnectedPermissions.php new file mode 100644 index 0000000..17d6f0d --- /dev/null +++ b/lib/elearning/Ilias5ConnectedPermissions.php @@ -0,0 +1,17 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias4ConnectedPermission + * @package ELearning-Interface + */ +class Ilias5ConnectedPermissions extends Ilias4ConnectedPermissions +{ + +} +?> \ No newline at end of file diff --git a/lib/elearning/Ilias5ConnectedUser.class.php b/lib/elearning/Ilias5ConnectedUser.class.php deleted file mode 100644 index 6172976..0000000 --- a/lib/elearning/Ilias5ConnectedUser.class.php +++ /dev/null @@ -1,40 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias5ConnectedUser - * @package ELearning-Interface - */ -class Ilias5ConnectedUser extends Ilias4ConnectedUser -{ - - /** - * verify login data - * - * returns true, if login-data is valid - * @access public - * @param string $username username - * @param string $password password - * @return boolean login-validation - */ - function verifyLogin($username, $password) - { - global $connected_cms, $messages; - $result = $connected_cms[$this->cms_type]->soap_client->checkPassword($username, $password); - if (strpos($result, '::') > 0) { - return $this->getLoginData($username); - } - return false; - } - - function setConnection($user_type, $ignore_encrypted_passwords = false) - { - $this->external_password = ''; - parent::setConnection($user_type, true); - } -} \ No newline at end of file diff --git a/lib/elearning/Ilias5ConnectedUser.php b/lib/elearning/Ilias5ConnectedUser.php new file mode 100644 index 0000000..6172976 --- /dev/null +++ b/lib/elearning/Ilias5ConnectedUser.php @@ -0,0 +1,40 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias5ConnectedUser + * @package ELearning-Interface + */ +class Ilias5ConnectedUser extends Ilias4ConnectedUser +{ + + /** + * verify login data + * + * returns true, if login-data is valid + * @access public + * @param string $username username + * @param string $password password + * @return boolean login-validation + */ + function verifyLogin($username, $password) + { + global $connected_cms, $messages; + $result = $connected_cms[$this->cms_type]->soap_client->checkPassword($username, $password); + if (strpos($result, '::') > 0) { + return $this->getLoginData($username); + } + return false; + } + + function setConnection($user_type, $ignore_encrypted_passwords = false) + { + $this->external_password = ''; + parent::setConnection($user_type, true); + } +} \ No newline at end of file diff --git a/lib/elearning/Ilias5ContentModule.class.php b/lib/elearning/Ilias5ContentModule.class.php deleted file mode 100644 index 3f50c02..0000000 --- a/lib/elearning/Ilias5ContentModule.class.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias5ContentModule - * @package ELearning-Interface - */ -class Ilias5ContentModule extends Ilias4ContentModule -{ - -} \ No newline at end of file diff --git a/lib/elearning/Ilias5ContentModule.php b/lib/elearning/Ilias5ContentModule.php new file mode 100644 index 0000000..3f50c02 --- /dev/null +++ b/lib/elearning/Ilias5ContentModule.php @@ -0,0 +1,16 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias5ContentModule + * @package ELearning-Interface + */ +class Ilias5ContentModule extends Ilias4ContentModule +{ + +} \ No newline at end of file diff --git a/lib/elearning/Ilias5Soap.class.php b/lib/elearning/Ilias5Soap.class.php deleted file mode 100644 index cf8bd4c..0000000 --- a/lib/elearning/Ilias5Soap.class.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @access public - * @modulegroup elearning_interface_modules - * @module Ilias5Soap - * @package ELearning-Interface - */ -class Ilias5Soap extends Ilias4Soap -{ - var $cms_type; - var $admin_sid; - var $user_sid; - var $user_type; - var $soap_cache; - var $separator_string; - - /** - * call soap-function - * - * calls soap-function with given parameters - * @access public - * @param string method method-name - * @param string params parameters - * @return mixed result - */ - function call($method, $params) - { - $index = md5($method . ":" . implode('-', $params)); - // return false if no session_id is given - if (($method != "login") AND ($params["sid"] == "")) - return false; -// echo $this->caching_active; - if (($this->caching_active == true) AND (isset($this->soap_cache[$index]))) - { -// echo $index; -// echo " from Cache
"; - $result = $this->soap_cache[$index]; - } - else - { - $result = $this->_call($method, $params); - // if Session is expired, re-login and try again - if (($method != "login") AND $this->soap_client->fault AND in_array(mb_strtolower($this->faultstring), ["session not valid","session invalid", "session idled"]) ) - { - $caching_status = $this->caching_active; - $this->caching_active = false; - $user_type = $this->user_type; - $this->user_type = 'admin'; - $params["sid"] = $this->login(); - $result = $this->_call($method, $params); - $this->caching_active = $caching_status; - $this->user_type = $user_type; - } - elseif (! $this->soap_client->fault) - $this->soap_cache[$index] = $result; - } - return $result; - } - - /** - * login - * - * login to soap-webservice - * @access public - * @return string result - */ - function login() - { - global $ELEARNING_INTERFACE_MODULES, $connected_cms; - if ($this->user_type == "admin") { - $param = [ - 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], - 'username' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["username"], - 'password' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["password"] - ]; - $result = $this->call('login', $param); - } elseif ($this->user_type == "user") { - $param = [ - 'sid' => $this->admin_sid, - 'user_id' => $connected_cms[$this->cms_type]->user->getId() - ]; - $result = $this->call('loginStudipUser', $param); - } - if ($this->user_type == "admin") - $this->admin_sid = $result; - if ($this->user_type == "user") - $this->user_sid = $result; - return $result; - } - - /** - * Check Auth - * - * login to soap-webservice - * @access public - * @return string result - */ - function checkPassword($username, $password) - { - global $ELEARNING_INTERFACE_MODULES, $connected_cms; - $param = [ - 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], - 'username' => $username, - 'password' => $password - ]; - $result = $this->call('login', $param); - return $result; - } -} diff --git a/lib/elearning/Ilias5Soap.php b/lib/elearning/Ilias5Soap.php new file mode 100644 index 0000000..cf8bd4c --- /dev/null +++ b/lib/elearning/Ilias5Soap.php @@ -0,0 +1,114 @@ + + * @access public + * @modulegroup elearning_interface_modules + * @module Ilias5Soap + * @package ELearning-Interface + */ +class Ilias5Soap extends Ilias4Soap +{ + var $cms_type; + var $admin_sid; + var $user_sid; + var $user_type; + var $soap_cache; + var $separator_string; + + /** + * call soap-function + * + * calls soap-function with given parameters + * @access public + * @param string method method-name + * @param string params parameters + * @return mixed result + */ + function call($method, $params) + { + $index = md5($method . ":" . implode('-', $params)); + // return false if no session_id is given + if (($method != "login") AND ($params["sid"] == "")) + return false; +// echo $this->caching_active; + if (($this->caching_active == true) AND (isset($this->soap_cache[$index]))) + { +// echo $index; +// echo " from Cache
"; + $result = $this->soap_cache[$index]; + } + else + { + $result = $this->_call($method, $params); + // if Session is expired, re-login and try again + if (($method != "login") AND $this->soap_client->fault AND in_array(mb_strtolower($this->faultstring), ["session not valid","session invalid", "session idled"]) ) + { + $caching_status = $this->caching_active; + $this->caching_active = false; + $user_type = $this->user_type; + $this->user_type = 'admin'; + $params["sid"] = $this->login(); + $result = $this->_call($method, $params); + $this->caching_active = $caching_status; + $this->user_type = $user_type; + } + elseif (! $this->soap_client->fault) + $this->soap_cache[$index] = $result; + } + return $result; + } + + /** + * login + * + * login to soap-webservice + * @access public + * @return string result + */ + function login() + { + global $ELEARNING_INTERFACE_MODULES, $connected_cms; + if ($this->user_type == "admin") { + $param = [ + 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], + 'username' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["username"], + 'password' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["password"] + ]; + $result = $this->call('login', $param); + } elseif ($this->user_type == "user") { + $param = [ + 'sid' => $this->admin_sid, + 'user_id' => $connected_cms[$this->cms_type]->user->getId() + ]; + $result = $this->call('loginStudipUser', $param); + } + if ($this->user_type == "admin") + $this->admin_sid = $result; + if ($this->user_type == "user") + $this->user_sid = $result; + return $result; + } + + /** + * Check Auth + * + * login to soap-webservice + * @access public + * @return string result + */ + function checkPassword($username, $password) + { + global $ELEARNING_INTERFACE_MODULES, $connected_cms; + $param = [ + 'client' => $ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["client"], + 'username' => $username, + 'password' => $password + ]; + $result = $this->call('login', $param); + return $result; + } +} diff --git a/lib/elearning/LonCapaConnectedCMS.class.php b/lib/elearning/LonCapaConnectedCMS.class.php deleted file mode 100644 index 2f86aa8..0000000 --- a/lib/elearning/LonCapaConnectedCMS.class.php +++ /dev/null @@ -1,67 +0,0 @@ -seminarId = Context::getId(); - $this->user = User::findCurrent(); - $this->lcRequest = new LonCapaRequest(); - $this->cmsUrl = $this->ABSOLUTE_PATH_ELEARNINGMODULES; - } - - - /** - * search for content modules - * - * returns found content modules - * @throws AccessDeniedException - * @param string $key keyword - * @return array list of content modules - */ - public function searchContentModules($key) - { - - if (!$GLOBALS['perm']->have_studip_perm('tutor', $this->seminarId)) { - throw new AccessDeniedException(); - } - - $url = $this->cmsUrl . '/courses?search=' . urlencode($key) . '&owner=' . urlencode($this->user->username); - $response = $this->lcRequest->request($url); - - if ($response) { - $courses = new SimpleXMLElement($response); - - $result = []; - foreach ($courses->course as $course) { - $temp = explode(':', (string)$course->owner); - - $result[] = [ - 'ref_id' => (string)$course->id, - 'title' => (string)$course->description, - 'authors' => $temp[0], - 'type' => $this->cms_type - ]; - } - } - - return $result; - } -} diff --git a/lib/elearning/LonCapaConnectedCMS.php b/lib/elearning/LonCapaConnectedCMS.php new file mode 100644 index 0000000..2f86aa8 --- /dev/null +++ b/lib/elearning/LonCapaConnectedCMS.php @@ -0,0 +1,67 @@ +seminarId = Context::getId(); + $this->user = User::findCurrent(); + $this->lcRequest = new LonCapaRequest(); + $this->cmsUrl = $this->ABSOLUTE_PATH_ELEARNINGMODULES; + } + + + /** + * search for content modules + * + * returns found content modules + * @throws AccessDeniedException + * @param string $key keyword + * @return array list of content modules + */ + public function searchContentModules($key) + { + + if (!$GLOBALS['perm']->have_studip_perm('tutor', $this->seminarId)) { + throw new AccessDeniedException(); + } + + $url = $this->cmsUrl . '/courses?search=' . urlencode($key) . '&owner=' . urlencode($this->user->username); + $response = $this->lcRequest->request($url); + + if ($response) { + $courses = new SimpleXMLElement($response); + + $result = []; + foreach ($courses->course as $course) { + $temp = explode(':', (string)$course->owner); + + $result[] = [ + 'ref_id' => (string)$course->id, + 'title' => (string)$course->description, + 'authors' => $temp[0], + 'type' => $this->cms_type + ]; + } + } + + return $result; + } +} diff --git a/lib/elearning/LonCapaConnectedLink.class.php b/lib/elearning/LonCapaConnectedLink.class.php deleted file mode 100644 index c00958b..0000000 --- a/lib/elearning/LonCapaConnectedLink.class.php +++ /dev/null @@ -1,67 +0,0 @@ - $this->cms_type, 'module' => $current_module]); - - return Studip\LinkButton::create(_('Starten'), $url, [ - 'target' => '_blank', - 'rel' => 'noopener noreferrer', - ]); - } - - /** - * get admin module links - * - * returns links add or remove a module from course - * @return string returns html-code - */ - public function getAdminModuleLinks() - { - global $connected_cms, $view, $search_key, $cms_select, $current_module; - global $template_factory; - - $template = $template_factory->open('elearning/loncapa_connected_link_edit'); - $template->current_module = $connected_cms[$this->cms_type]->content_module[$current_module]->getId(); - $template->connected = $connected_cms[$this->cms_type]->content_module[$current_module]->isConnected(); - $template->cms_type = $this->cms_type; - $template->search_key = $search_key; - return $template->render(compact('view', 'search_key', 'cms_select', 'current_module')); - } - - /** - * returns url for connected LonCapa course - * - * @param string $module_id LonCapa ID - * @param string $course_id Stud.IP course ID - * @return string url for LonCapa - */ - public function getRedirectUrl($module_id, $course_id) - { - return sprintf( - '%s/enter/%s?token=%s&courseid=%s&systemid=%s', - $this->cms_link, - $module_id, - Token::create(60), - $course_id, - $this->cms_type - ); - } -} diff --git a/lib/elearning/LonCapaConnectedLink.php b/lib/elearning/LonCapaConnectedLink.php new file mode 100644 index 0000000..c00958b --- /dev/null +++ b/lib/elearning/LonCapaConnectedLink.php @@ -0,0 +1,67 @@ + $this->cms_type, 'module' => $current_module]); + + return Studip\LinkButton::create(_('Starten'), $url, [ + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + ]); + } + + /** + * get admin module links + * + * returns links add or remove a module from course + * @return string returns html-code + */ + public function getAdminModuleLinks() + { + global $connected_cms, $view, $search_key, $cms_select, $current_module; + global $template_factory; + + $template = $template_factory->open('elearning/loncapa_connected_link_edit'); + $template->current_module = $connected_cms[$this->cms_type]->content_module[$current_module]->getId(); + $template->connected = $connected_cms[$this->cms_type]->content_module[$current_module]->isConnected(); + $template->cms_type = $this->cms_type; + $template->search_key = $search_key; + return $template->render(compact('view', 'search_key', 'cms_select', 'current_module')); + } + + /** + * returns url for connected LonCapa course + * + * @param string $module_id LonCapa ID + * @param string $course_id Stud.IP course ID + * @return string url for LonCapa + */ + public function getRedirectUrl($module_id, $course_id) + { + return sprintf( + '%s/enter/%s?token=%s&courseid=%s&systemid=%s', + $this->cms_link, + $module_id, + Token::create(60), + $course_id, + $this->cms_type + ); + } +} diff --git a/lib/elearning/LonCapaContentModule.class.php b/lib/elearning/LonCapaContentModule.class.php deleted file mode 100644 index 0f16cd0..0000000 --- a/lib/elearning/LonCapaContentModule.class.php +++ /dev/null @@ -1,86 +0,0 @@ -lcRequest = new LonCapaRequest(); - $this->cmsUrl = $GLOBALS['ELEARNING_INTERFACE_MODULES'][$cms_type]['ABSOLUTE_PATH_ELEARNINGMODULES']; - - parent::__construct($module_id, $module_type, $cms_type); - } - - /** - *fetch data from LonCapa - * - */ - public function readData() - { - $url = $this->cmsUrl . '/course/' . urlencode($this->id); - $response = $this->lcRequest->request($url); - - if ($response) { - $courses = new SimpleXMLElement($response); - $course = $courses->course[0]; - - list($author, $dummy) = explode(':', (string)$course->owner); - - $this->id = (string)$course->id; - $this->title = (string)$course->description; - $this->authors = $author; - } - - } - - /** - * get permission-status - * - * - * @param string $operation operation - * @return boolean allowed - */ - public function isAllowed($operation) - { - return true; - } - - /** - * store connection between Stud.IP course and LonCapa course - * - * @param string $seminar_id - * @return bool - */ - public function setConnection($seminar_id) - { - $this->is_connected = true; - return ObjectConnections::setConnection( - $seminar_id, - $this->id, - $this->module_type, - $this->cms_type - ); - } -} diff --git a/lib/elearning/LonCapaContentModule.php b/lib/elearning/LonCapaContentModule.php new file mode 100644 index 0000000..0f16cd0 --- /dev/null +++ b/lib/elearning/LonCapaContentModule.php @@ -0,0 +1,86 @@ +lcRequest = new LonCapaRequest(); + $this->cmsUrl = $GLOBALS['ELEARNING_INTERFACE_MODULES'][$cms_type]['ABSOLUTE_PATH_ELEARNINGMODULES']; + + parent::__construct($module_id, $module_type, $cms_type); + } + + /** + *fetch data from LonCapa + * + */ + public function readData() + { + $url = $this->cmsUrl . '/course/' . urlencode($this->id); + $response = $this->lcRequest->request($url); + + if ($response) { + $courses = new SimpleXMLElement($response); + $course = $courses->course[0]; + + list($author, $dummy) = explode(':', (string)$course->owner); + + $this->id = (string)$course->id; + $this->title = (string)$course->description; + $this->authors = $author; + } + + } + + /** + * get permission-status + * + * + * @param string $operation operation + * @return boolean allowed + */ + public function isAllowed($operation) + { + return true; + } + + /** + * store connection between Stud.IP course and LonCapa course + * + * @param string $seminar_id + * @return bool + */ + public function setConnection($seminar_id) + { + $this->is_connected = true; + return ObjectConnections::setConnection( + $seminar_id, + $this->id, + $this->module_type, + $this->cms_type + ); + } +} diff --git a/lib/elearning/LonCapaRequest.class.php b/lib/elearning/LonCapaRequest.class.php deleted file mode 100644 index ecf3075..0000000 --- a/lib/elearning/LonCapaRequest.class.php +++ /dev/null @@ -1,110 +0,0 @@ -ch = curl_init(); - $this->initOptions(); - } - - /** - * initializes curl options - */ - public function initOptions() - { - $this->options = [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FOLLOWLOCATION => true, - //CURLOPT_CAINFO => '', - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_SSL_VERIFYHOST => false - ]; - } - - /** - * close connection - */ - public function __destruct() - { - curl_close($this->ch); - } - - /** - * set curl options - * @param $key - * @param $value - */ - public function setOption($key, $value) - { - $this->options[$key] = $value; - } - - /** - * do a curl request on the given url and return the result if successfull - * - * @param $url string - * @param array $postfields - * @return string - */ - public function request($url, $postfields = null) - { - $result = $this->sendRequest($url, $postfields); - if ($result['statusCode'] == 200) { - return $result['response']; - } else { - // TODO: fehlermeldung wäre schöner - return null; - } - } - - /** - * do a curl request on the given url and return the result if successfull - * @param $url string - * @param array $postfields - * @return array - */ - protected function sendRequest($url, $postfields = null) - { - $options = $this->options; - $options[CURLOPT_URL] = $url; - - if ($postfields) { - $options[CURLOPT_POST] = true; - $options[CURLOPT_POSTFIELDS] = $postfields; - } - - curl_setopt_array($this->ch, $options); - $response = curl_exec($this->ch); - - $statusCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); - - if ($response === false) { - $last_error = curl_error($this->ch); - Log::error(__CLASS__ . ' curl_exec failed: ' . $last_error); - } - return compact('statusCode', 'response'); - } -} diff --git a/lib/elearning/LonCapaRequest.php b/lib/elearning/LonCapaRequest.php new file mode 100644 index 0000000..ecf3075 --- /dev/null +++ b/lib/elearning/LonCapaRequest.php @@ -0,0 +1,110 @@ +ch = curl_init(); + $this->initOptions(); + } + + /** + * initializes curl options + */ + public function initOptions() + { + $this->options = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + //CURLOPT_CAINFO => '', + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false + ]; + } + + /** + * close connection + */ + public function __destruct() + { + curl_close($this->ch); + } + + /** + * set curl options + * @param $key + * @param $value + */ + public function setOption($key, $value) + { + $this->options[$key] = $value; + } + + /** + * do a curl request on the given url and return the result if successfull + * + * @param $url string + * @param array $postfields + * @return string + */ + public function request($url, $postfields = null) + { + $result = $this->sendRequest($url, $postfields); + if ($result['statusCode'] == 200) { + return $result['response']; + } else { + // TODO: fehlermeldung wäre schöner + return null; + } + } + + /** + * do a curl request on the given url and return the result if successfull + * @param $url string + * @param array $postfields + * @return array + */ + protected function sendRequest($url, $postfields = null) + { + $options = $this->options; + $options[CURLOPT_URL] = $url; + + if ($postfields) { + $options[CURLOPT_POST] = true; + $options[CURLOPT_POSTFIELDS] = $postfields; + } + + curl_setopt_array($this->ch, $options); + $response = curl_exec($this->ch); + + $statusCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); + + if ($response === false) { + $last_error = curl_error($this->ch); + Log::error(__CLASS__ . ' curl_exec failed: ' . $last_error); + } + return compact('statusCode', 'response'); + } +} diff --git a/lib/elearning/ObjectConnections.class.php b/lib/elearning/ObjectConnections.class.php deleted file mode 100644 index e360f71..0000000 --- a/lib/elearning/ObjectConnections.class.php +++ /dev/null @@ -1,254 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ObjectConnections -* @package ELearning-Interface -*/ -class ObjectConnections -{ - var $id; - var $object_connections; - /** - * constructor - * - * init class. - * @access public - * @param string $object_id object-id - */ - function __construct($object_id = "") - { - $this->id = $object_id; - if ($object_id != "") - $this->readData(); - } - - /** - * read object connections - * - * gets object connections from database - * @access public - */ - function readData() - { - global $ELEARNING_INTERFACE_MODULES; - - $this->object_connections = []; - - $query = "SELECT system_type, module_type, module_id, chdate - FROM object_contentmodules - WHERE object_id = ? - ORDER BY chdate DESC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->id]); - - $module_count = 0; - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - // show only connected modules with valid module-type - if ($ELEARNING_INTERFACE_MODULES[$row['system_type']]['types'][$row['module_type']] == '') { - continue; - } - $module_count += 1; - $d_system_type = $row['system_type']; - $d_module_type = $row['module_type']; - $d_module_id = $row['module_id']; - - $reference = $d_system_type . '_' . $d_module_type . '_' . $d_module_id; - $this->object_connections[$reference]['cms'] = $d_system_type; - $this->object_connections[$reference]['type'] = $d_module_type; - $this->object_connections[$reference]['id'] = $d_module_id; - $this->object_connections[$reference]['chdate'] = $row['chdate']; - } - - if ($module_count == 0) { - $this->object_connections = false; - } - } - - /** - * get object connections - * - * returns object connections - * @access public - * @return array object connections - */ - function getConnections() - { - return $this->object_connections; - } - - /** - * get connection-status - * - * returns true, if object has connections - * @access public - * @return boolean connection-status - */ - function isConnected() - { - return (boolean) $this->object_connections; - } - - /** - * get connection-status - * - * returns true, if object has connections - * @access public - * @param string $object_id object-id (optional) - * @return boolean connection-status - */ - public static function isObjectConnected($object_id) - { - $query = "SELECT 1 FROM object_contentmodules WHERE object_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$object_id]); - return (bool)$statement->fetchColumn(); - } - - /** - * get module-id - * - * returns module-id of given connection - * @access public - * @param string $connection_object_id object-id - * @param string $connection_module_type module-type - * @param string $connection_cms system-type - * @return string module-id - */ - public static function getConnectionModuleId($connection_object_id, $connection_module_type, $connection_cms) - { - $query = "SELECT module_id - FROM object_contentmodules - WHERE object_id = ? AND system_type = ? AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_cms, - $connection_module_type - ]); - return $statement->fetchColumn() ?: false; - } - - /** - * set connection - * - * sets connection with object - * @access public - * @param string $connection_object_id object-id - * @param string $connection_module_id module-id - * @param string $connection_module_type module-type - * @param string $connection_cms system-type - * @return boolean successful - */ - public static function setConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) - { - $query = "SELECT 1 - FROM object_contentmodules - WHERE object_id = ? AND module_id = ? AND system_type = ? - AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - $check = $statement->fetchColumn(); - - if ($check) { - $query = "UPDATE object_contentmodules - SET module_type = ?, chdate = UNIX_TIMESTAMP() - WHERE object_id = ? AND module_id = ? AND system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_module_type, - $connection_object_id, - $connection_module_id, - $connection_cms - ]); - } else { - $query = "INSERT INTO object_contentmodules - (object_id, module_id, system_type, module_type, mkdate, chdate) - VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - } - return true; - } - - /** - * unset connection - * - * deletes connection with object - * @access public - * @param string $connection_object_id object-id - * @param string $connection_module_id module-id - * @param string $connection_module_type module-type - * @param string $connection_cms system-type - * @return boolean successful - */ - public static function unsetConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) - { - $query = "SELECT 1 - FROM object_contentmodules - WHERE object_id = ? AND module_id = ? AND system_type = ? - AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - $check = $statement->fetchColumn(); - - - if ($check) { - $query = "DELETE FROM object_contentmodules - WHERE object_id = ? AND module_id = ? AND system_type = ? - AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - return true; - } - return false; - } - - public static function GetConnectedSystems($object_id) - { - $query = "SELECT DISTINCT system_type - FROM object_contentmodules - WHERE object_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$object_id]); - return $statement->fetchAll(PDO::FETCH_COLUMN); - } - - public static function DeleteAllConnections($object_id, $cms_type) - { - $query = "DELETE FROM object_contentmodules - WHERE object_id = ? AND system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$object_id, $cms_type]); - return $statement->rowCount(); - } -} diff --git a/lib/elearning/ObjectConnections.php b/lib/elearning/ObjectConnections.php new file mode 100644 index 0000000..e360f71 --- /dev/null +++ b/lib/elearning/ObjectConnections.php @@ -0,0 +1,254 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ObjectConnections +* @package ELearning-Interface +*/ +class ObjectConnections +{ + var $id; + var $object_connections; + /** + * constructor + * + * init class. + * @access public + * @param string $object_id object-id + */ + function __construct($object_id = "") + { + $this->id = $object_id; + if ($object_id != "") + $this->readData(); + } + + /** + * read object connections + * + * gets object connections from database + * @access public + */ + function readData() + { + global $ELEARNING_INTERFACE_MODULES; + + $this->object_connections = []; + + $query = "SELECT system_type, module_type, module_id, chdate + FROM object_contentmodules + WHERE object_id = ? + ORDER BY chdate DESC"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->id]); + + $module_count = 0; + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + // show only connected modules with valid module-type + if ($ELEARNING_INTERFACE_MODULES[$row['system_type']]['types'][$row['module_type']] == '') { + continue; + } + $module_count += 1; + $d_system_type = $row['system_type']; + $d_module_type = $row['module_type']; + $d_module_id = $row['module_id']; + + $reference = $d_system_type . '_' . $d_module_type . '_' . $d_module_id; + $this->object_connections[$reference]['cms'] = $d_system_type; + $this->object_connections[$reference]['type'] = $d_module_type; + $this->object_connections[$reference]['id'] = $d_module_id; + $this->object_connections[$reference]['chdate'] = $row['chdate']; + } + + if ($module_count == 0) { + $this->object_connections = false; + } + } + + /** + * get object connections + * + * returns object connections + * @access public + * @return array object connections + */ + function getConnections() + { + return $this->object_connections; + } + + /** + * get connection-status + * + * returns true, if object has connections + * @access public + * @return boolean connection-status + */ + function isConnected() + { + return (boolean) $this->object_connections; + } + + /** + * get connection-status + * + * returns true, if object has connections + * @access public + * @param string $object_id object-id (optional) + * @return boolean connection-status + */ + public static function isObjectConnected($object_id) + { + $query = "SELECT 1 FROM object_contentmodules WHERE object_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$object_id]); + return (bool)$statement->fetchColumn(); + } + + /** + * get module-id + * + * returns module-id of given connection + * @access public + * @param string $connection_object_id object-id + * @param string $connection_module_type module-type + * @param string $connection_cms system-type + * @return string module-id + */ + public static function getConnectionModuleId($connection_object_id, $connection_module_type, $connection_cms) + { + $query = "SELECT module_id + FROM object_contentmodules + WHERE object_id = ? AND system_type = ? AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_cms, + $connection_module_type + ]); + return $statement->fetchColumn() ?: false; + } + + /** + * set connection + * + * sets connection with object + * @access public + * @param string $connection_object_id object-id + * @param string $connection_module_id module-id + * @param string $connection_module_type module-type + * @param string $connection_cms system-type + * @return boolean successful + */ + public static function setConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) + { + $query = "SELECT 1 + FROM object_contentmodules + WHERE object_id = ? AND module_id = ? AND system_type = ? + AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + $check = $statement->fetchColumn(); + + if ($check) { + $query = "UPDATE object_contentmodules + SET module_type = ?, chdate = UNIX_TIMESTAMP() + WHERE object_id = ? AND module_id = ? AND system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_module_type, + $connection_object_id, + $connection_module_id, + $connection_cms + ]); + } else { + $query = "INSERT INTO object_contentmodules + (object_id, module_id, system_type, module_type, mkdate, chdate) + VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + } + return true; + } + + /** + * unset connection + * + * deletes connection with object + * @access public + * @param string $connection_object_id object-id + * @param string $connection_module_id module-id + * @param string $connection_module_type module-type + * @param string $connection_cms system-type + * @return boolean successful + */ + public static function unsetConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) + { + $query = "SELECT 1 + FROM object_contentmodules + WHERE object_id = ? AND module_id = ? AND system_type = ? + AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + $check = $statement->fetchColumn(); + + + if ($check) { + $query = "DELETE FROM object_contentmodules + WHERE object_id = ? AND module_id = ? AND system_type = ? + AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + return true; + } + return false; + } + + public static function GetConnectedSystems($object_id) + { + $query = "SELECT DISTINCT system_type + FROM object_contentmodules + WHERE object_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$object_id]); + return $statement->fetchAll(PDO::FETCH_COLUMN); + } + + public static function DeleteAllConnections($object_id, $cms_type) + { + $query = "DELETE FROM object_contentmodules + WHERE object_id = ? AND system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$object_id, $cms_type]); + return $statement->rowCount(); + } +} diff --git a/lib/elearning/PmWikiConnectedCMS.class.php b/lib/elearning/PmWikiConnectedCMS.class.php deleted file mode 100644 index 7de0a78..0000000 --- a/lib/elearning/PmWikiConnectedCMS.class.php +++ /dev/null @@ -1,86 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module PmWikiConnectedCMS -* @package ELearning-Interface -*/ - -class PmWikiConnectedCMS extends ConnectedCMS -{ - public $client; - public $api_key; - public $field_script; - - function __construct($cms) - { - parent::__construct($cms); - $this->client = WebserviceClient::instance( $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['ABSOLUTE_PATH_SOAP'] . - '?' . $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['URL_PARAMS'], - $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['WEBSERVICE_CLASS']); - - $this->api_key = $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key']; - } - - function init($cms) - { - parent::init($cms); - $this->field_script = $GLOBALS['ELEARNING_INTERFACE_MODULES'][$cms]["field_script"]; - } - - /** - * search for content modules - * - * returns found content modules - * @param string $key keyword - * @return array list of content modules - */ - public function searchContentModules($key) - { - $fields_found = $this->client->call("search_content_modules", [ - $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key'], - $key - ]); - - $result = []; - foreach ($fields_found as $field) { - $result[$field['field_id']] = [ - 'ref_id' => $field['field_id'], - 'type' => $field['field_type'], - 'obj_id' => null, - 'create_date' => $field['create_date'], - 'last_update' => $field['change_date'], - 'title' => $field['field_title'], - 'description' => $field['field_description'], - ]; - } - return $result; - } - -} diff --git a/lib/elearning/PmWikiConnectedCMS.php b/lib/elearning/PmWikiConnectedCMS.php new file mode 100644 index 0000000..7de0a78 --- /dev/null +++ b/lib/elearning/PmWikiConnectedCMS.php @@ -0,0 +1,86 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module PmWikiConnectedCMS +* @package ELearning-Interface +*/ + +class PmWikiConnectedCMS extends ConnectedCMS +{ + public $client; + public $api_key; + public $field_script; + + function __construct($cms) + { + parent::__construct($cms); + $this->client = WebserviceClient::instance( $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['ABSOLUTE_PATH_SOAP'] . + '?' . $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['URL_PARAMS'], + $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['WEBSERVICE_CLASS']); + + $this->api_key = $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key']; + } + + function init($cms) + { + parent::init($cms); + $this->field_script = $GLOBALS['ELEARNING_INTERFACE_MODULES'][$cms]["field_script"]; + } + + /** + * search for content modules + * + * returns found content modules + * @param string $key keyword + * @return array list of content modules + */ + public function searchContentModules($key) + { + $fields_found = $this->client->call("search_content_modules", [ + $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key'], + $key + ]); + + $result = []; + foreach ($fields_found as $field) { + $result[$field['field_id']] = [ + 'ref_id' => $field['field_id'], + 'type' => $field['field_type'], + 'obj_id' => null, + 'create_date' => $field['create_date'], + 'last_update' => $field['change_date'], + 'title' => $field['field_title'], + 'description' => $field['field_description'], + ]; + } + return $result; + } + +} diff --git a/lib/elearning/PmWikiConnectedLink.class.php b/lib/elearning/PmWikiConnectedLink.class.php deleted file mode 100644 index 8ed7416..0000000 --- a/lib/elearning/PmWikiConnectedLink.class.php +++ /dev/null @@ -1,134 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module PmWikiConnectedLink -* @package ELearning-Interface -*/ - -class PmWikiConnectedLink extends ConnectedLink -{ - function __construct($cms) - { - parent::__construct($cms); - $this->cms_link = "pmwiki_referrer.php"; - } - - /** - * get user module links - * - * returns content module links for user - * @access public - * @return string html-code - */ - - function getUserModuleLinks() - { - $range_id = Context::getId(); - $username = get_username($GLOBALS['auth']->auth['uid']); - - global $connected_cms, $view, $search_key, $cms_select, $current_module; - - // hier muss die Authentifizierung mit übergeben werden... - // - if (Context::isCourse()) { - $context = 'seminar'; - - $status = StudipSeminarHelper::get_user_status($username, $range_id); - - } else if (Context::isInstitute()) { - $context = 'institute'; - - $status = StudipInstituteHelper::get_user_status($username, $range_id); - } - - ob_start(); ?> -
- - - auth['uname']) ?>'> - - - - - - - -
- - - -
- - - - - - - - - cms_type]->content_module[$current_module]->isConnected()) : ?> - -   - - - -   - - - -
- +* @access public +* @modulegroup elearning_interface_modules +* @module PmWikiConnectedLink +* @package ELearning-Interface +*/ + +class PmWikiConnectedLink extends ConnectedLink +{ + function __construct($cms) + { + parent::__construct($cms); + $this->cms_link = "pmwiki_referrer.php"; + } + + /** + * get user module links + * + * returns content module links for user + * @access public + * @return string html-code + */ + + function getUserModuleLinks() + { + $range_id = Context::getId(); + $username = get_username($GLOBALS['auth']->auth['uid']); + + global $connected_cms, $view, $search_key, $cms_select, $current_module; + + // hier muss die Authentifizierung mit übergeben werden... + // + if (Context::isCourse()) { + $context = 'seminar'; + + $status = StudipSeminarHelper::get_user_status($username, $range_id); + + } else if (Context::isInstitute()) { + $context = 'institute'; + + $status = StudipInstituteHelper::get_user_status($username, $range_id); + } + + ob_start(); ?> +
+ + + auth['uname']) ?>'> + + + + + + + +
+ + + +
+ + + + + + + + + cms_type]->content_module[$current_module]->isConnected()) : ?> + +   + + + +   + + + +
+ -* @access public -* @modulegroup elearning_interface_modules -* @module PmWikiContentModule -* @package ELearning-Interface -*/ - -class PmWikiContentModule extends ContentModule -{ - public $link; - public $client; - public $chdate; - public $accepted_users; - - /** - * constructor - * - * init class. - * @access public - * @param string $module_id module-id - * @param string $module_type module-type - * @param string $cms_type system-type - */ - - function __construct($module_id, $module_type, $cms_type) - { - parent::__construct($module_id, $module_type, $cms_type); - $this->link = $GLOBALS['connected_cms'][$this->cms_type]->ABSOLUTE_PATH_ELEARNINGMODULES.$this->id."/"; - $this->client = WebserviceClient::instance( $this->link. '?' . - $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['URL_PARAMS'], - $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['WEBSERVICE_CLASS']); - } - - /** - * reads data for content module - * - */ - - function readData() - { - global $connected_cms, $view, $search_key, $cms_select, $current_module; - - $args = [$GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key'], $this->id]; - - $field_data = $connected_cms[$this->cms_type]->client->call('get_field_info', $args); - - $this->title = $field_data['field_title']; - $this->authors = $field_data['field_author']; - $this->chdate = $field_data['change_date']; - - $this->accepted_users = $field_data['field_accepted_users']; - - return false; - } - - /** - * get permission-status - * - * returns true, if operation is allowed - * @access public - * @param string $operation operation - * @return boolean allowed - */ - - function isAllowed($operation) - { - global $connected_cms, $view, $search_key, $cms_select, $current_module; - - if (Config::get()->STUDIP_INSTALLATION_ID) - { - $username = Config::get()->STUDIP_INSTALLATION_ID."#".$GLOBALS['auth']->auth['uname']; - } else - { - $username = $GLOBALS['auth']->auth['uname']; - } - - $args = [$GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key'],$this->id, $username]; - - $authorized = $connected_cms[$this->cms_type]->client->call('field_accessable_by_user', $args); - - if ($authorized) - { - return true; - } else - { - # old authorization - if (is_array($this->accepted_users) && in_array($username, $this->accepted_users)) - return true; - else - return false; - } - - } -} diff --git a/lib/elearning/PmWikiContentModule.php b/lib/elearning/PmWikiContentModule.php new file mode 100644 index 0000000..06533dd --- /dev/null +++ b/lib/elearning/PmWikiContentModule.php @@ -0,0 +1,115 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module PmWikiContentModule +* @package ELearning-Interface +*/ + +class PmWikiContentModule extends ContentModule +{ + public $link; + public $client; + public $chdate; + public $accepted_users; + + /** + * constructor + * + * init class. + * @access public + * @param string $module_id module-id + * @param string $module_type module-type + * @param string $cms_type system-type + */ + + function __construct($module_id, $module_type, $cms_type) + { + parent::__construct($module_id, $module_type, $cms_type); + $this->link = $GLOBALS['connected_cms'][$this->cms_type]->ABSOLUTE_PATH_ELEARNINGMODULES.$this->id."/"; + $this->client = WebserviceClient::instance( $this->link. '?' . + $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['URL_PARAMS'], + $GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['WEBSERVICE_CLASS']); + } + + /** + * reads data for content module + * + */ + + function readData() + { + global $connected_cms, $view, $search_key, $cms_select, $current_module; + + $args = [$GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key'], $this->id]; + + $field_data = $connected_cms[$this->cms_type]->client->call('get_field_info', $args); + + $this->title = $field_data['field_title']; + $this->authors = $field_data['field_author']; + $this->chdate = $field_data['change_date']; + + $this->accepted_users = $field_data['field_accepted_users']; + + return false; + } + + /** + * get permission-status + * + * returns true, if operation is allowed + * @access public + * @param string $operation operation + * @return boolean allowed + */ + + function isAllowed($operation) + { + global $connected_cms, $view, $search_key, $cms_select, $current_module; + + if (Config::get()->STUDIP_INSTALLATION_ID) + { + $username = Config::get()->STUDIP_INSTALLATION_ID."#".$GLOBALS['auth']->auth['uname']; + } else + { + $username = $GLOBALS['auth']->auth['uname']; + } + + $args = [$GLOBALS['ELEARNING_INTERFACE_MODULES'][$this->cms_type]['soap_data']['api-key'],$this->id, $username]; + + $authorized = $connected_cms[$this->cms_type]->client->call('field_accessable_by_user', $args); + + if ($authorized) + { + return true; + } else + { + # old authorization + if (is_array($this->accepted_users) && in_array($username, $this->accepted_users)) + return true; + else + return false; + } + + } +} diff --git a/lib/exceptions/ClipboardException.class.php b/lib/exceptions/ClipboardException.class.php deleted file mode 100644 index 676da93..0000000 --- a/lib/exceptions/ClipboardException.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright 2019 - * @since 4.5 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a clipboard functionality cannot work - * as expected. - */ -class ClipboardException extends Exception -{ - -} diff --git a/lib/exceptions/ClipboardException.php b/lib/exceptions/ClipboardException.php new file mode 100644 index 0000000..676da93 --- /dev/null +++ b/lib/exceptions/ClipboardException.php @@ -0,0 +1,25 @@ + + * @copyright 2019 + * @since 4.5 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a clipboard functionality cannot work + * as expected. + */ +class ClipboardException extends Exception +{ + +} diff --git a/lib/exceptions/resources/GlobalResourceLockException.class.php b/lib/exceptions/resources/GlobalResourceLockException.class.php deleted file mode 100644 index 165843f..0000000 --- a/lib/exceptions/resources/GlobalResourceLockException.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright 2017-2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.5 - */ - -/** - * This exception is thrown when a general error occurs when dealing with - * GlobalResourceLock objects. - */ -class GlobalResourceLockException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/GlobalResourceLockException.php b/lib/exceptions/resources/GlobalResourceLockException.php new file mode 100644 index 0000000..165843f --- /dev/null +++ b/lib/exceptions/resources/GlobalResourceLockException.php @@ -0,0 +1,25 @@ + + * @copyright 2017-2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.5 + */ + +/** + * This exception is thrown when a general error occurs when dealing with + * GlobalResourceLock objects. + */ +class GlobalResourceLockException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/GlobalResourceLockOverlapException.class.php b/lib/exceptions/resources/GlobalResourceLockOverlapException.class.php deleted file mode 100644 index 6b05cf5..0000000 --- a/lib/exceptions/resources/GlobalResourceLockOverlapException.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright 2017-2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.5 - */ - -/** - * This exception is thrown when a global resource lock overlaps - * with another global resource lock. - */ -class GlobalResourceLockOverlapException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/GlobalResourceLockOverlapException.php b/lib/exceptions/resources/GlobalResourceLockOverlapException.php new file mode 100644 index 0000000..6b05cf5 --- /dev/null +++ b/lib/exceptions/resources/GlobalResourceLockOverlapException.php @@ -0,0 +1,25 @@ + + * @copyright 2017-2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.5 + */ + +/** + * This exception is thrown when a global resource lock overlaps + * with another global resource lock. + */ +class GlobalResourceLockOverlapException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/InvalidResourceCategoryException.class.php b/lib/exceptions/resources/InvalidResourceCategoryException.class.php deleted file mode 100644 index df4d36c..0000000 --- a/lib/exceptions/resources/InvalidResourceCategoryException.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource category does not exist - * or a resource is used with a resource category that does not match - * or when a resource category cannot be created due to invalid data. - */ -class InvalidResourceCategoryException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/InvalidResourceCategoryException.php b/lib/exceptions/resources/InvalidResourceCategoryException.php new file mode 100644 index 0000000..df4d36c --- /dev/null +++ b/lib/exceptions/resources/InvalidResourceCategoryException.php @@ -0,0 +1,25 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource category does not exist + * or a resource is used with a resource category that does not match + * or when a resource category cannot be created due to invalid data. + */ +class InvalidResourceCategoryException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/InvalidResourceClassException.class.php b/lib/exceptions/resources/InvalidResourceClassException.class.php deleted file mode 100644 index 8420a83..0000000 --- a/lib/exceptions/resources/InvalidResourceClassException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a Resource object has an - * invalid class_name property. - */ -class InvalidResourceClassException extends Exception -{ - -} diff --git a/lib/exceptions/resources/InvalidResourceClassException.php b/lib/exceptions/resources/InvalidResourceClassException.php new file mode 100644 index 0000000..8420a83 --- /dev/null +++ b/lib/exceptions/resources/InvalidResourceClassException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a Resource object has an + * invalid class_name property. + */ +class InvalidResourceClassException extends Exception +{ + +} diff --git a/lib/exceptions/resources/InvalidResourceException.class.php b/lib/exceptions/resources/InvalidResourceException.class.php deleted file mode 100644 index e9850b8..0000000 --- a/lib/exceptions/resources/InvalidResourceException.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource is invalid, - * either because of its place in the resource hierarchy - * or because it has been initialised with the wrong Resource class. - */ -class InvalidResourceException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/InvalidResourceException.php b/lib/exceptions/resources/InvalidResourceException.php new file mode 100644 index 0000000..e9850b8 --- /dev/null +++ b/lib/exceptions/resources/InvalidResourceException.php @@ -0,0 +1,25 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource is invalid, + * either because of its place in the resource hierarchy + * or because it has been initialised with the wrong Resource class. + */ +class InvalidResourceException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/InvalidResourceRequestException.class.php b/lib/exceptions/resources/InvalidResourceRequestException.class.php deleted file mode 100644 index 8ec9dab..0000000 --- a/lib/exceptions/resources/InvalidResourceRequestException.class.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource request is invalid. - */ -class InvalidResourceRequestException extends InvalidArgumentException -{ -} diff --git a/lib/exceptions/resources/InvalidResourceRequestException.php b/lib/exceptions/resources/InvalidResourceRequestException.php new file mode 100644 index 0000000..8ec9dab --- /dev/null +++ b/lib/exceptions/resources/InvalidResourceRequestException.php @@ -0,0 +1,22 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource request is invalid. + */ +class InvalidResourceRequestException extends InvalidArgumentException +{ +} diff --git a/lib/exceptions/resources/NoResourceClassException.class.php b/lib/exceptions/resources/NoResourceClassException.class.php deleted file mode 100644 index 74e68fd..0000000 --- a/lib/exceptions/resources/NoResourceClassException.class.php +++ /dev/null @@ -1,11 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a general error occurs when dealing with - * ResourceBooking objects. - */ -class ResourceBookingException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourceBookingException.php b/lib/exceptions/resources/ResourceBookingException.php new file mode 100644 index 0000000..c1db11c --- /dev/null +++ b/lib/exceptions/resources/ResourceBookingException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a general error occurs when dealing with + * ResourceBooking objects. + */ +class ResourceBookingException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourceBookingOverlapException.class.php b/lib/exceptions/resources/ResourceBookingOverlapException.class.php deleted file mode 100644 index f59cc39..0000000 --- a/lib/exceptions/resources/ResourceBookingOverlapException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource booking overlaps with - * other resource bookings or with a resource lock. - */ -class ResourceBookingOverlapException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourceBookingOverlapException.php b/lib/exceptions/resources/ResourceBookingOverlapException.php new file mode 100644 index 0000000..f59cc39 --- /dev/null +++ b/lib/exceptions/resources/ResourceBookingOverlapException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource booking overlaps with + * other resource bookings or with a resource lock. + */ +class ResourceBookingOverlapException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourceBookingRangeException.class.php b/lib/exceptions/resources/ResourceBookingRangeException.class.php deleted file mode 100644 index 50e7144..0000000 --- a/lib/exceptions/resources/ResourceBookingRangeException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource booking has an invalid range-ID - * or if a resource booking shall be created and no range-ID is given. - */ -class ResourceBookingRangeException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourceBookingRangeException.php b/lib/exceptions/resources/ResourceBookingRangeException.php new file mode 100644 index 0000000..50e7144 --- /dev/null +++ b/lib/exceptions/resources/ResourceBookingRangeException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource booking has an invalid range-ID + * or if a resource booking shall be created and no range-ID is given. + */ +class ResourceBookingRangeException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourceException.class.php b/lib/exceptions/resources/ResourceException.class.php deleted file mode 100644 index 686cc33..0000000 --- a/lib/exceptions/resources/ResourceException.class.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is a general exception in the resource management. - */ -class ResourceException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourceException.php b/lib/exceptions/resources/ResourceException.php new file mode 100644 index 0000000..686cc33 --- /dev/null +++ b/lib/exceptions/resources/ResourceException.php @@ -0,0 +1,23 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is a general exception in the resource management. + */ +class ResourceException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourceNoTimeRangeException.class.php b/lib/exceptions/resources/ResourceNoTimeRangeException.class.php deleted file mode 100644 index 891b583..0000000 --- a/lib/exceptions/resources/ResourceNoTimeRangeException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when at least one time range is required - * in a method but no time range is provided. - */ -class ResourceNoTimeRangeException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourceNoTimeRangeException.php b/lib/exceptions/resources/ResourceNoTimeRangeException.php new file mode 100644 index 0000000..891b583 --- /dev/null +++ b/lib/exceptions/resources/ResourceNoTimeRangeException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when at least one time range is required + * in a method but no time range is provided. + */ +class ResourceNoTimeRangeException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourcePermissionException.class.php b/lib/exceptions/resources/ResourcePermissionException.class.php deleted file mode 100644 index 3a0c919..0000000 --- a/lib/exceptions/resources/ResourcePermissionException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when resource permissions are invalid - * or insufficient to perform an action on a resource. - */ -class ResourcePermissionException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourcePermissionException.php b/lib/exceptions/resources/ResourcePermissionException.php new file mode 100644 index 0000000..3a0c919 --- /dev/null +++ b/lib/exceptions/resources/ResourcePermissionException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when resource permissions are invalid + * or insufficient to perform an action on a resource. + */ +class ResourcePermissionException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourcePropertyDefinitionException.class.php b/lib/exceptions/resources/ResourcePropertyDefinitionException.class.php deleted file mode 100644 index 963ca64..0000000 --- a/lib/exceptions/resources/ResourcePropertyDefinitionException.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource property definition is requested - * but not defined for the resource property or if it is defined - * but cannot be stored. - */ -class ResourcePropertyDefinitionException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourcePropertyDefinitionException.php b/lib/exceptions/resources/ResourcePropertyDefinitionException.php new file mode 100644 index 0000000..963ca64 --- /dev/null +++ b/lib/exceptions/resources/ResourcePropertyDefinitionException.php @@ -0,0 +1,25 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource property definition is requested + * but not defined for the resource property or if it is defined + * but cannot be stored. + */ +class ResourcePropertyDefinitionException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourcePropertyException.class.php b/lib/exceptions/resources/ResourcePropertyException.class.php deleted file mode 100644 index 83f871f..0000000 --- a/lib/exceptions/resources/ResourcePropertyException.class.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource property is requested - * but not defined for the resource type or if it is defined - * but cannot be stored. - */ -class ResourcePropertyException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourcePropertyException.php b/lib/exceptions/resources/ResourcePropertyException.php new file mode 100644 index 0000000..83f871f --- /dev/null +++ b/lib/exceptions/resources/ResourcePropertyException.php @@ -0,0 +1,25 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource property is requested + * but not defined for the resource type or if it is defined + * but cannot be stored. + */ +class ResourcePropertyException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourcePropertyStateException.class.php b/lib/exceptions/resources/ResourcePropertyStateException.class.php deleted file mode 100644 index 1880265..0000000 --- a/lib/exceptions/resources/ResourcePropertyStateException.class.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource property state is invalid. - */ -class ResourcePropertyStateException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourcePropertyStateException.php b/lib/exceptions/resources/ResourcePropertyStateException.php new file mode 100644 index 0000000..1880265 --- /dev/null +++ b/lib/exceptions/resources/ResourcePropertyStateException.php @@ -0,0 +1,23 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource property state is invalid. + */ +class ResourcePropertyStateException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourceRequestException.class.php b/lib/exceptions/resources/ResourceRequestException.class.php deleted file mode 100644 index 1e8d071..0000000 --- a/lib/exceptions/resources/ResourceRequestException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource request cannot be stored - * or another fatal error occurs when hanlding a ResourceRequest object. - */ -class ResourceRequestException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/ResourceRequestException.php b/lib/exceptions/resources/ResourceRequestException.php new file mode 100644 index 0000000..1e8d071 --- /dev/null +++ b/lib/exceptions/resources/ResourceRequestException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource request cannot be stored + * or another fatal error occurs when hanlding a ResourceRequest object. + */ +class ResourceRequestException extends InvalidArgumentException +{ + +} diff --git a/lib/exceptions/resources/ResourceUnavailableException.class.php b/lib/exceptions/resources/ResourceUnavailableException.class.php deleted file mode 100644 index b335d14..0000000 --- a/lib/exceptions/resources/ResourceUnavailableException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource is requested - * but no resource request can be created for it. - */ -class ResourceUnavailableException extends Exception -{ - -} diff --git a/lib/exceptions/resources/ResourceUnavailableException.php b/lib/exceptions/resources/ResourceUnavailableException.php new file mode 100644 index 0000000..b335d14 --- /dev/null +++ b/lib/exceptions/resources/ResourceUnavailableException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource is requested + * but no resource request can be created for it. + */ +class ResourceUnavailableException extends Exception +{ + +} diff --git a/lib/exceptions/resources/ResourceUnlockableException.class.php b/lib/exceptions/resources/ResourceUnlockableException.class.php deleted file mode 100644 index 11bc92b..0000000 --- a/lib/exceptions/resources/ResourceUnlockableException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2017 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is thrown when a resource shall be locked - * but no resource lock can be created for it. - */ -class ResourceUnlockableException extends Exception -{ - -} diff --git a/lib/exceptions/resources/ResourceUnlockableException.php b/lib/exceptions/resources/ResourceUnlockableException.php new file mode 100644 index 0000000..11bc92b --- /dev/null +++ b/lib/exceptions/resources/ResourceUnlockableException.php @@ -0,0 +1,24 @@ + + * @copyright 2017 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is thrown when a resource shall be locked + * but no resource lock can be created for it. + */ +class ResourceUnlockableException extends Exception +{ + +} diff --git a/lib/exceptions/resources/SeparableRoomException.class.php b/lib/exceptions/resources/SeparableRoomException.class.php deleted file mode 100644 index 78c9912..0000000 --- a/lib/exceptions/resources/SeparableRoomException.class.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This exception is intended to be used in case of errors that are - * related to separable rooms. - */ -class SeparableRoomException extends InvalidArgumentException -{ - -} diff --git a/lib/exceptions/resources/SeparableRoomException.php b/lib/exceptions/resources/SeparableRoomException.php new file mode 100644 index 0000000..78c9912 --- /dev/null +++ b/lib/exceptions/resources/SeparableRoomException.php @@ -0,0 +1,24 @@ + + * @copyright 2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * This exception is intended to be used in case of errors that are + * related to separable rooms. + */ +class SeparableRoomException extends InvalidArgumentException +{ + +} diff --git a/lib/filesystem/FileArchiveManager.class.php b/lib/filesystem/FileArchiveManager.class.php deleted file mode 100644 index 704bb44..0000000 --- a/lib/filesystem/FileArchiveManager.class.php +++ /dev/null @@ -1,998 +0,0 @@ - - * @copyright 2016 data-quest - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - - -/** - * The FileArchiveManager class gives programmers a simple way to handle - * file archives by providing different methods for packing and unpacking - * file archives in a simple manner. - */ -class FileArchiveManager -{ - - //ARCHIVE HELPER METHODS - - - /** - * Adds a file to an archive using its FileType object. - * - * @param ZipArchive $archive The Zip archive where the FileRef shall be added to. - * @param FileType $file_type The FileType which shall be added to the zip archive. - * @param string $user_id The user who wishes to add the FileRef to the archive. - * @param string $archive_fs_path The path of the file inside the archive's file system. - * @param bool $do_user_permission_checks Set to true if reading/downloading permissions - * shall be checked. False otherwise. Default is true. - * @param bool $ignore_user Set to true, if a file - * which has no download restrictions shall be included - * and the user-specific download condition check shall be ignored. - * If this parameter is set to true, the user_id parameter is irrelevant. - * The default for this parameter is false. - * @return bool True on success, false on failure. - * - * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more - * special exception is thrown. - */ - public static function addFileTypeToArchive( - ZipArchive $archive, - FileType $file_type, - $user_id = null, - $archive_fs_path = '', - $do_user_permission_checks = true, - $ignore_user = false, - &$file_list = null - ) - { - $archive_max_size = Config::get()->ZIP_DOWNLOAD_MAX_SIZE * 1024 * 1024; - //For FileRef objects we first have to do permission checks - //using the FileRef's folder object. - $adding_allowed = false; - - if ($do_user_permission_checks) { - $folder = $file_type->getFolderType(); - if (!$folder) { - return false; - } - - if ($folder->isReadable($user_id) && $file_type->isDownloadable($user_id)) { - //FileRef is readable and downloadable for the user (identified by $user_id). - $adding_allowed = true; - } - } elseif ($ignore_user) { - //we have to check the download condition by looking at the - //terms of use object of the FileType: - $terms_of_use = $file_type->getTermsOfUse(); - if ($terms_of_use && $terms_of_use->download_condition == 0) { - $adding_allowed = true; - } - } else { - //Totally skip permission checks: - $adding_allowed = true; - } - - if ($adding_allowed) { - //Adding the FileType is allowed: - $file_contains_link = false; - if ($file_type instanceof LibraryFile) { - $file_contains_link = !$file_type->hasFileAttached(); - } else { - $file_contains_link = $file_type instanceof URLFile; - } - - // Increase download counter - if ($file_type instanceof StandardFile) { - $file_ref = $file_type->getFileRef(); - $file_ref->incrementDownloadCounter(); - } - - if ($file_contains_link) { - //The FileType references a link: - //Put the URL into a file ending with .url: - $url = $file_type->getDownloadURL(); - if ($url) { - //The URL has been fetched and we can put it - //in a file in the archive: - $archive->addFromString( - $archive_fs_path . FileManager::cleanFileName($file_type->getFilename()) . '.url', - "[InternetShortcut]\nURL={$url}\n" - ); - //Check the file size of the archive: - if (file_exists($archive->filename) && filesize($archive->filename) > $archive_max_size) { - throw new FileArchiveManagerException( - sprintf( - _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'), - relsize($archive_max_size) - ) - ); - } - if (is_array($file_list)) { - $user = $file_type->getUser(); - $file_list[] = [ - 'name' => FileManager::cleanFileName($file_type->getFilename()), - 'size' => $file_type->getSize(), - 'first_name' => ($user instanceof User) ? $user->vorname : '', - 'last_name' => ($user instanceof User) ? $user->nachname : '', - 'downloads' => $file_type->getDownloads(), - 'mkdate' => date('d.m.Y H:i', $file_type->getMakeDate()), - 'path' => ($archive_fs_path . $file_type->getFilename()) - ]; - } - return true; - } - } else { - if (!($file_type instanceof StandardFile)) { - $file_type = $file_type->convertToStandardFile(); - } - if (!($file_type instanceof StandardFile)) { - //The file type could not be converted to a standard file. - //We cannot continue. - return false; - } - //Get the file's path (if the file exists) and add the file to the archive: - $path = $file_type->getPath(); - if ($path) { - //It is a file in the file system: - if (file_exists($path)) { - $archive->addFile($path, $archive_fs_path . FileManager::cleanFileName($file_type->getFilename())); - //Check the file size of the archive: - if (file_exists($archive->filename) && filesize($archive->filename) > $archive_max_size) { - throw new FileArchiveManagerException( - sprintf( - _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'), - relsize($archive_max_size) - ) - ); - } - //Add the file to the file list (if available): - if (is_array($file_list)) { - $archive_max_num_files = Config::get()->ZIP_DOWNLOAD_MAX_FILES; - $archive_max_size = Config::get()->ZIP_DOWNLOAD_MAX_SIZE * 1024 * 1024; //1048576 bytes = 1 Mebibyte - $user = $file_type->getUser(); - $file_list[] = [ - 'name' => FileManager::cleanFileName($file_type->getFilename()), - 'size' => $file_type->getSize(), - 'first_name' => ($user instanceof User) ? $user->vorname : '', - 'last_name' => ($user instanceof User) ? $user->nachname : '', - 'downloads' => $file_type->getDownloads(), - 'mkdate' => date('d.m.Y H:i', $file_type->getMakeDate()), - 'path' => ($archive_fs_path . FileManager::cleanFileName($file_type->getFilename())) - ]; - if (count($file_list) > $archive_max_num_files) { - $archive->unchangeAll(); - unlink($archive->filename); - throw new FileArchiveManagerException( - sprintf( - _('Das Archiv beinhaltet zu viele Dateibereich-Objekte! Die Obergrenze liegt bei %d Objekten!'), - $archive_max_num_files - ) - ); - } - if (array_sum(array_column($file_list, 'size')) > $archive_max_size) { - $archive->unchangeAll(); - unlink($archive->filename); - throw new FileArchiveManagerException( - sprintf( - _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'), - relsize($archive_max_size) - ) - ); - } - } - } - } - } - } - - //Something must have gone wrong: - return false; - } - - - /** - * Adds a FileRef to a Zip archive. - * This is only a wrapper to addFileTypeToArchive that exists only - * for compatibility reasons. - * - * @see addFileTypeToArchive - */ - public static function addFileRefToArchive( - ZipArchive $archive, - FileRef $file_ref, - $user_id = null, - $archive_fs_path = '', - $do_user_permission_checks = true, - $ignore_user = false, - &$file_list = null - ) - { - $file_type = $file_ref->getFileType(); - if ($file_type instanceof FileType) { - return self::addFileTypeToArchive( - $archive, - $file_type, - $user_id, - $archive_fs_path, - $do_user_permission_checks, - $ignore_user, - $file_list - ); - } - //The file type variable does not contain a FileType object. - return false; - } - - - /** - * Adds a FolderType instance to a Zip archive. - * - * @param ZipArchive $archive The Zip archive where the FileRef shall be added to. - * @param FileRef $file_ref The FileRef which shall be added to the zip archive. - * @param string $user_id The user who wishes to add the FileRef to the archive. - * @param string $archive_fs_path The path of the folder inside the archive's file system. - * @param bool $do_user_permission_checks Set to true if reading/downloading permissions - * shall be checked. False otherwise. Default is true. - * @param bool $keep_hierarchy True, if the folder hierarchy shall be kept. - * False, if the folder hierarchy shall be flattened. - * @param bool $ignore_user Set to true, if a folder - * of type StandardFolder shall be included without checking - * if the user (identified by user_id) can read it. - * @return bool True on success, false on failure. - * - * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more - * special exception is thrown. - */ - public static function addFolderToArchive( - ZipArchive $archive, - FolderType $folder, - $user_id = null, - $archive_fs_path = '', - $do_user_permission_checks = true, - $keep_hierarchy = true, - $ignore_user = false, - &$file_list = null - ) { - - if ($do_user_permission_checks) { - //Check if the folder is readable for the user (identified by $user_id): - if (!$folder->isReadable($user_id)) { - //Folder is not readable: - return false; - } - } elseif ($ignore_user - && !($folder instanceof StandardFolder) - && in_array($folder->range_type, ['course', 'institute'])) - { - //If user permissions shall be skipped the folder must be - //an instance of StandardFolder and the folder's range type - //must be course or institute since we can only be sure - //that StandardFolder instances in courses or institutes - //are readable by everyone. - return false; - } - - $folder_zip_path = $archive_fs_path; - if ($keep_hierarchy) { - $folder_zip_path .= FileManager::cleanFileName($folder->name); - $archive->addEmptyDir($folder_zip_path); - } - foreach ($folder->getFiles() as $file) { - - /*if (!$file_ref instanceof FileRef) { TODO: OwnCloudPlugin is this ready? - $plugin = PluginManager::getInstance()->getPlugin($folder->range_id); - if (!$plugin) { - $plugin = PluginManager::getInstance()->getPlugin($folder->range_type);; - } - if ($plugin) { - $file_ref = $plugin->getPreparedFile($file_ref->id, true); - } - }*/ - - self::addFileTypeToArchive( - $archive, - $file, - $user_id, - //keep hierarchy in zip file (files and subdirectories) - $keep_hierarchy ? $folder_zip_path . '/' : '', - $do_user_permission_checks, - $ignore_user, - $file_list - ); - } - - foreach ($folder->getSubfolders() as $subfolder) { - self::addFolderToArchive( - $archive, - $subfolder, - $user_id, - //keep hierarchy in zip file (files and subdirectories) - $keep_hierarchy ? $folder_zip_path . '/' : '', - $do_user_permission_checks, - $keep_hierarchy, - $ignore_user, - $file_list - ); - } - - return true; - } - - - //ARCHIVE CREATION METHODS - - - /** - * General method for creating file archives. - * - * This method is a generalisation for all archive creation methods. - * For easier archive creation you may use the other archive creation - * methods which work with less arguments. - * - * @param Array $file_area_objects Array of FileRef, FileURL, Folder or FolderType objects. - * $file_area_objects may contain a mix between those object types. - * @param string $user_id The user who wishes to pack files. - * @param string $archive_file_path The path for the archive file. - * @param bool $do_user_permission_checks Set to true if individual - * reading/downloading permissions shall be checked. False otherwise. - * Default is true. - * @param bool $keep_hierarchy True, if the folder hierarchy shall be kept. - * False, if the folder hierarchy shall be flattened. Default is true. - * @param bool $ignore_user Set to true, if all files - * which have no download restrictions and all folders which are of type - * StandardFolder shall be included and the user-specific - * download condition check shall be ignored. - * If this parameter is set to true, the user_id parameter is irrelevant. - * The default for this parameter is false. - * @param string $zip_encoding encoding for filenames in zip - * @param bool $add_filelist_to_archive If this is set to true a file list - * in the CSV format will be added to the archive. Its name is hardcoded - * to archive_filelist.csv. The default value of $add_filelist_to_archive - * is false which means no file list is added. - * - * @return bool True, if the archive file was created and saved successfully - * at $archive_file_path, false otherwise. - * - * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more - * special exception is thrown. - */ - public static function createArchive( - $file_area_objects = [], - $user_id = null, - $archive_file_path = '', - $do_user_permission_checks = true, - $keep_hierarchy = true, - $ignore_user = false, - $zip_encoding = 'UTF-8', - $add_filelist_to_archive = false - ) - { - - // check if archive path is set: - if (!$archive_file_path) { - throw new FileArchiveManagerException( - _('Der Zielpfad für das Archiv wurde nicht angegeben!') - ); - } - - // $file_area_objects must be a non-empty array! - // Otherwise we would return an empty Zip archive. - if (!is_array($file_area_objects) || empty($file_area_objects)) { - throw new FileArchiveManagerException( - _('Es wurden keine Dateien ausgewählt!') - ); - } - - // We can create the Zip archive now since its path exists in the file system - // and furthermore there are file area objects available. - $archive = new Studip\ZipArchive(); - if (!$archive->open($archive_file_path, ZipArchive::CREATE | ZipArchive::OVERWRITE)) { - throw new FileArchiveManagerException('Error opening new ZIP archive!'); - } - $archive->setOutputEncoding($zip_encoding); - - //If $file_list is not an array - //then no files are added to the file list. - $file_list = null; - if ($add_filelist_to_archive) { - $file_list = []; - } - - foreach ($file_area_objects as $file_area_object) { - if ($file_area_object instanceof FileRef) { - self::addFileRefToArchive( - $archive, - $file_area_object, - $user_id, - '', - $do_user_permission_checks, - $ignore_user, - $file_list - ); - } elseif ($file_area_object instanceof FileType) { - self::addFileTypeToArchive( - $archive, - $file_area_object, - $user_id, - '', - $do_user_permission_checks, - $ignore_user, - $file_list - ); - } elseif ($file_area_object instanceof Folder || $file_area_object instanceof FolderType) { - $folder = $file_area_object; - if ($folder instanceof Folder) { - //We use FolderType instances here. - $folder = $folder->getTypedFolder(); - } - - self::addFolderToArchive( - $archive, - $folder, - $user_id, - '', - $do_user_permission_checks, - $keep_hierarchy, - $ignore_user, - $file_list - ); - } - } - - if ($archive->numFiles > 0) { - //At least one file is in the archive. - - if ($add_filelist_to_archive) { - //If a file list shall be included in the ZIP archive - //we must now make a CSV file out of file_list: - - $csv_data = array_merge( - [ - [ - _('Name'), - _('Größe'), - _('Vorname'), - _('Nachname'), - _('Downloads'), - _('Datum'), - _('Pfad') - ] - ], - $file_list - ); - - //The CSV file has been generated. - //Now we must add it to the archive: - $archive->addFromString('archive_filelist.csv', array_to_csv($csv_data)); - } - - //Now the ZIP file is really finished: - return $archive->close(); - } - - //empty archive - throw new FileArchiveManagerException( - _('Das ZIP Archiv enthält keine Dateien!') - ); - } - - /** - * Puts files (identified by their file refs) into one file archive. - * - * @param FileRef[] $file_refs Array of FileRef objects. - * @param User $user The user who wishes to pack files. - * @param string $archive_file_path The path for the archive file. - * @param bool $do_user_permission_checks Set to true if reading/downloading - * permissions shall be checked. False otherwise. Default is true. - * - * @return bool True, if the archive file was created and saved successfully - * at $archive_file_path, false otherwise. - * - * @throws Exception|FileArchiveManagerException If an error occurs - * a general exception or a more special exception is thrown. - */ - public static function createArchiveFromFileRefs( - $file_refs, - User $user, - $archive_file_path = '', - $do_user_permission_checks = true - ) - { - if (!$archive_file_path) { - throw new FileArchiveManagerException( - _('Der Zielpfad für das Archiv wurde nicht angegeben!') - ); - } - - //We must now collect all the files from these FileRefs and copy them - //into the new archive file. - - return self::createArchive( - $file_refs, - $user->id, - $archive_file_path, - $do_user_permission_checks, - false //do not keep the file hierarchy - ); - } - - /** - * Returns all children of a folder type. - * - * @param FolderType $folder - * @return array - */ - private static function getFolderChildren(FolderType $folder) - { - $children = []; - foreach ($folder->subfolders as $folder) { - $children[] = $folder; - } - foreach ($folder->file_refs as $ref) { - $children[] = $ref; - } - return $children; - } - - /** - * Creates an archive that contains all files of a course the given user - * is allowed to download. - * - * @param FolderType $folder The folder whose files shall be put inside an archive. - * @param string $user_id The ID of the user who wishes to put the course's files into an archive - * @param string $archive_file_path The path for the archive file. - * @param bool $do_user_permission_checks Set to true if reading/downloading permissions - * shall be checked. False otherwise. Default is true. - * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. - * If $keep_hierarchy is set to false you will get an archive that contains only files - * and no subdirectories. - * - * @return bool True, if the archive file was created and saved successfully - * at $archive_file_path, false otherwise. - * - * @throws Exception|FileArchiveManagerException If an error occurs - * a general exception or a more special exception is thrown. - */ - public static function createArchiveFromFolder( - FolderType $folder, - $user_id = null, - $archive_file_path = '', - $do_user_permission_checks = true, - $keep_hierarchy = true - ) - { - return self::createArchive( - self::getFolderChildren($folder), - $user_id, - $archive_file_path, - $do_user_permission_checks, - $keep_hierarchy - ); - } - - /** - * Creates an archive that contains all files of a course the given user - * is allowed to download. - * - * @param string $course_id The ID of the course whose files shall be put inside an archive. - * @param string $user_id The ID of the user who wishes to put the course's files into an archive - * @param string $archive_file_path The path for the archive file. - * @param bool $do_user_permission_checks Set to true if reading/downloading permissions - * shall be checked. False otherwise. Default is true. - * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. - * If $keep_hierarchy is set to false you will get an archive that contains only files - * and no subdirectories. - * - * @return bool True, if the archive file was created and saved successfully - * at $archive_file_path, false otherwise. - * - * @throws Exception|FileArchiveManagerException If an error occurs - * a general exception or a more special exception is thrown. - */ - public static function createArchiveFromCourse( - $course_id, - $user_id = null, - $archive_file_path = '', - $do_user_permission_checks = true, - $keep_hierarchy = true - ) - { - $folder = Folder::findTopFolder($course_id); - if (!$folder) { - return null; - } - - $folder = $folder->getTypedFolder(); - if (!$folder) { - return null; - } - - return self::createArchive( - self::getFolderChildren($folder), - $user_id, - $archive_file_path, - $do_user_permission_checks, - $keep_hierarchy - ); - } - - - /** - * Creates an archive that contains all files of an institute the given user - * is allowed to download. - * - * @param string $institute_id The ID of the institute whose files shall be put inside an archive. - * @param string $user_id The ID of the user who wishes to put the institute's files into an archive - * @param string $archive_file_path The path for the archive file. - * @param bool $do_user_permission_checks Set to true if reading/downloading permissions - * shall be checked. False otherwise. Default is true. - * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. - * If $keep_hierarchy is set to false you will get an archive that contains only files - * and no subdirectories. - * - * @return bool True, if the archive file was created and saved successfully - * at $archive_file_path, false otherwise. - * - * @throws Exception|FileArchiveManagerException If an error occurs - * a general exception or a more special exception is thrown. - */ - public static function createArchiveFromInstitute( - $institute_id, - $user_id = null, - $archive_file_path = '', - $do_user_permission_checks = true, - $keep_hierarchy = true) - { - $folder = Folder::findTopFolder($institute_id); - if(!$folder) { - return null; - } - - $folder = $folder->getTypedFolder(); - if(!$folder) { - return null; - } - - return self::createArchive( - self::getFolderChildren($folder), - $archive_file_path, - $do_user_permission_checks, - $keep_hierarchy - ); - } - - /** - * Creates an archive that contains all files of a user, if the current - * user has root permissions to do this. - * - * @param string $user_id The ID of the user whose files shall be put inside an archive. - * @param string $archive_file_path The path for the archive file. - * @param bool $do_user_permission_checks Set to true if reading/downloading permissions - * shall be checked. False otherwise. Default is true. - * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. - * If $keep_hierarchy is set to false you will get an archive that contains only files - * and no subdirectories. - * - * @return bool True, if the archive file was created and saved successfully - * at $archive_file_path, false otherwise. - * - * @throws Exception|FileArchiveManagerException If an error occurs - * a general exception or a more special exception is thrown. - */ - public static function createArchiveFromUser( - $user_id, - $archive_file_path = '', - $do_user_permission_checks = true, - $keep_hierarchy = true - ) - { - $folder = Folder::findTopFolder($user_id); - if (!$folder) { - return null; - } - - $folder = $folder->getTypedFolder(); - if (!$folder) { - return null; - } - - return self::createArchive( - self::getFolderChildren($folder), - $archive_file_path, - $do_user_permission_checks, - $keep_hierarchy - ); - } - - - /** - * This method creates an archive with the content of a physical folder - * (A folder inside the operating system's file system). - * - * @param string $folder_path The path to the physical folder - * which content shall be added to a file archive. - * @param string $archive_file_path The path to the archive file which - * shall be created. - * - * @return True, if all files were added successfully, false otherwise. - * - * @throws Exception|FileArchiveManagerException If an error occurs - * a general exception or a more special exception is thrown. - */ - public static function createArchiveFromPhysicalFolder($folder_path, $archive_file_path) - { - if (!$folder_path || !$archive_file_path) { - //we can't work with empty paths! - return false; - } - - if (!file_exists($folder_path)) { - //path to physical folder does not exist! - throw new FileArchiveManagerException( - _('Der Ordner wurde im Dateisystem des Servers nicht gefunden!') - ); - } - - //Put all the content of the folder inside an archive: - $archive = Studip\ZipArchive::create($archive_file_path, true); - $result = $archive->addFromPath($folder_path); - $archive->close(); - return $result; - } - - //ARCHIVE EXTRACTION METHODS - - /** - * This is a helper method that builds a subfolder hierarchy inside - * a folder by looking at a string representing a file system path. - * - * The variable $path contains a hierarchy of subfolders that shall be created - * inside the given folder. If $path contains "folder1/folder2/folder3" then - * the given folder will get a subfolder named "folder1". The folder - * "folder1" itself will get a subfolder named "folder2" and so on. - * - * @param FolderType $folder The folder where a subfolder path shall be created. - * @param User $user The user who wishes to create the path. - * @param string $path The path which shall be created inside $folder. - * - * @return FolderType[] An array with FolderType objects representing - * each element of $path. - */ - public static function createFolderPath(FolderType $folder, User $user, $path = '') - { - if (!$path) { - return []; - } - - // now we strip leading and trailing slashes, whitespaces and other characters: - // then we convert path into an array of strings: - $path = trim($path, ' /'); - $path = explode('/', $path); - - //now we loop through path and build subfolders: - $folder_path = []; - - $current_folder = $folder; - foreach ($path as $new_folder_name) { - //first we check if the folder already exists: - foreach ($current_folder->getSubfolders() as $subfolder) { - if ($subfolder->name === $new_folder_name) { - //We have found a folder that has the name $new_folder_name: - //No need to create a new folder, we can use that folder - //and continue with it: - $current_folder = $subfolder; - $folder_path[] = $subfolder; - - //start next iteration of the outer foreach loop: - continue 2; - } - } - - //If code execution has reached this point we have looped - //throug all subfolders of the current folder and couldn't find - //any subfolder that matches the name given in $new_folder_name. - //Therefore we must create a new folder here, if possible: - - //Check the user's permissions first: - if ($current_folder->isSubfolderAllowed($user->id)) { - //Create a subfolder: - $result = FileManager::createSubFolder( - $current_folder, - $user, - get_class($current_folder) === RootFolder::class ? StandardFolder::class : get_class($current_folder), - $new_folder_name - ); - - if ($result instanceof FolderType) { - $folder_path[] = $result; - } - } - } - return $folder_path; - } - - /** - * Extracts one file from an opened archive and stores it in a folder. - * - * @param ZipArchive $archive The archive from which a file shall be extracted. - * @param string $archive_path The path of the file in the archive. - * @param FolderType $target_folder The folder where the file shall be stored. - * @param User $user The user who wishes to extract the file from the archive. - * - * @return FileType|null FileType instance on success, null otherwise. - */ - public static function extractFileFromArchive( - Studip\ZipArchive $archive, - $archive_path, - FolderType $target_folder, - User $user - ) - { - $file_resource = $archive->getStream($archive_path); - $file_info = $archive->statName($archive_path); - - if (!$file_resource) { - return null; - } - - $studip_file = new File(); - $studip_file->user_id = $user->id; - $studip_file->name = $archive->convertArchiveFilename(basename($archive_path)); - $studip_file->mime_type = get_mime_type($studip_file->name); - $studip_file->size = $file_info['size']; - $studip_file->id = $studip_file->getNewId(); - //$file->store(); - - // Ok, we have a file object in the database. Now we must connect - // it with the data file by extracting the data file into - // the place, where the file's content has to be placed. - $file_dir = pathinfo($studip_file->getPath(), PATHINFO_DIRNAME); - $file_path = $file_dir . '/' . $studip_file->id; - - // Create the directory for the file, if necessary: - if (!is_dir($file_dir)) { - mkdir($file_dir); - } - - // Ok, now we read all data from $file_resource and put it into - // the file's path: - if (file_put_contents($file_path, $file_resource) === false) { - //Something went wrong: abort and clean up! - //$file->delete(); - return null; - } - - // Ok, we now must create a File: - $file_ref = new FileRef(); - $file_ref->file_id = $studip_file->id; - $file_ref->folder_id = $target_folder->getId(); - $file_ref->user_id = $user->id; - $file_ref->name = $studip_file->name; - $file_ref->file = $studip_file; - $file = new StandardFile($file_ref); - if ($saved_file = $target_folder->addFile($file, $user->id)) { - return $saved_file; - } - - //Something went wrong: - return null; - } - - /** - * Extracts an archive into a folder inside the Stud.IP file area. - * - * @param FileType $archive_file The archive file which shall be extracted. - * @param FolderType $folder The folder where the archive shall be extracted. - * @param string $user_id The ID of the user who wants to extract the archive. - * - * @return FileType[] Array with extracted files, represented as FileRef objects. - */ - public static function extractArchiveFileToFolder( - FileType $archive_file, - FolderType $folder, - $user_id = null - ) - { - $user = $user_id ? User::find($user_id) : User::findCurrent(); - if (!$user) { - return []; - } - - // Determine, if the folder is writable for the user identified by $user_id: - if (!$folder->isWritable($user->id)) { - return []; - } - - // Determine if we can keep the zip archive's folder hierarchy: - $keep_hierarchy = $folder->isSubfolderAllowed($user->id); - - $archive = new Studip\ZipArchive(); - $standard_archive_file = $archive_file->convertToStandardFile(); - if (!($standard_archive_file instanceof StandardFile)) { - //Error converting the archive file. - return []; - } - $archive->open($standard_archive_file->getPath()); - - // loop over all entries in the zip archive and put each entry - // in the current folder or one of its subfolders: - $files = []; - - for ($i = 0; $i < $archive->numFiles; $i++) { - $entry_info = $archive->statIndex($i); - $entry_info_name = $archive->convertArchiveFilename($entry_info['name']); - // split the entry's path into its path and its name component: - $entry_path = ltrim(pathinfo($entry_info_name, PATHINFO_DIRNAME), '.'); - $entry_name = pathinfo($entry_info_name, PATHINFO_BASENAME); - - // check if $entry_info['name'] ends with a slash: - // In that case it is a directory entry: - $entry_is_directory = preg_match('/\/$/', $entry_info_name); - - //The folder where the extracted file/folder shall be inserted: - $extracted_entry_destination_folder = $folder; - - if ($keep_hierarchy) { - //Keep the archive's folder hierarchy: - //We may have to create subfolders. - if (basename($entry_path)) { - //The file/folder doesn't lie in the "top folder" of the archive: - //Pass the path to createFolderPath and let it generate - //a folder path before extracting the file: - $folder_path = self::createFolderPath( - $folder, - $user, - $entry_path - ); - - //Get the last element of $folder_path: - $last_folder_path_element = array_pop($folder_path); - - //Compare $extracted_entry_destination_folder's name with the name of the - //last path item in $file_archive_path. Only if they are equal - //we can use that folder to store the file. Otherwise - //we must continue with the next file entry in the archive: - if ($last_folder_path_element - && $last_folder_path_element->name === basename($entry_path)) - { - $extracted_entry_destination_folder = $last_folder_path_element; - } - } - } - - if ($entry_is_directory) { - //We have to create a subfolder if it doesn't exist yet: - self::createFolderPath( - $extracted_entry_destination_folder, - $user, - $entry_name - ); - } else { - //we extract one file: - //$entry_info['name'] is necessary because we need the full path - //to the entry inside the archive. - $file = self::extractFileFromArchive( - $archive, - $entry_info['name'], - $extracted_entry_destination_folder, - $user - ); - - if ($file instanceof FileType) { - $files[] = $file; - } - } - } - - return $files; - } -} diff --git a/lib/filesystem/FileArchiveManager.php b/lib/filesystem/FileArchiveManager.php new file mode 100644 index 0000000..704bb44 --- /dev/null +++ b/lib/filesystem/FileArchiveManager.php @@ -0,0 +1,998 @@ + + * @copyright 2016 data-quest + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + + +/** + * The FileArchiveManager class gives programmers a simple way to handle + * file archives by providing different methods for packing and unpacking + * file archives in a simple manner. + */ +class FileArchiveManager +{ + + //ARCHIVE HELPER METHODS + + + /** + * Adds a file to an archive using its FileType object. + * + * @param ZipArchive $archive The Zip archive where the FileRef shall be added to. + * @param FileType $file_type The FileType which shall be added to the zip archive. + * @param string $user_id The user who wishes to add the FileRef to the archive. + * @param string $archive_fs_path The path of the file inside the archive's file system. + * @param bool $do_user_permission_checks Set to true if reading/downloading permissions + * shall be checked. False otherwise. Default is true. + * @param bool $ignore_user Set to true, if a file + * which has no download restrictions shall be included + * and the user-specific download condition check shall be ignored. + * If this parameter is set to true, the user_id parameter is irrelevant. + * The default for this parameter is false. + * @return bool True on success, false on failure. + * + * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more + * special exception is thrown. + */ + public static function addFileTypeToArchive( + ZipArchive $archive, + FileType $file_type, + $user_id = null, + $archive_fs_path = '', + $do_user_permission_checks = true, + $ignore_user = false, + &$file_list = null + ) + { + $archive_max_size = Config::get()->ZIP_DOWNLOAD_MAX_SIZE * 1024 * 1024; + //For FileRef objects we first have to do permission checks + //using the FileRef's folder object. + $adding_allowed = false; + + if ($do_user_permission_checks) { + $folder = $file_type->getFolderType(); + if (!$folder) { + return false; + } + + if ($folder->isReadable($user_id) && $file_type->isDownloadable($user_id)) { + //FileRef is readable and downloadable for the user (identified by $user_id). + $adding_allowed = true; + } + } elseif ($ignore_user) { + //we have to check the download condition by looking at the + //terms of use object of the FileType: + $terms_of_use = $file_type->getTermsOfUse(); + if ($terms_of_use && $terms_of_use->download_condition == 0) { + $adding_allowed = true; + } + } else { + //Totally skip permission checks: + $adding_allowed = true; + } + + if ($adding_allowed) { + //Adding the FileType is allowed: + $file_contains_link = false; + if ($file_type instanceof LibraryFile) { + $file_contains_link = !$file_type->hasFileAttached(); + } else { + $file_contains_link = $file_type instanceof URLFile; + } + + // Increase download counter + if ($file_type instanceof StandardFile) { + $file_ref = $file_type->getFileRef(); + $file_ref->incrementDownloadCounter(); + } + + if ($file_contains_link) { + //The FileType references a link: + //Put the URL into a file ending with .url: + $url = $file_type->getDownloadURL(); + if ($url) { + //The URL has been fetched and we can put it + //in a file in the archive: + $archive->addFromString( + $archive_fs_path . FileManager::cleanFileName($file_type->getFilename()) . '.url', + "[InternetShortcut]\nURL={$url}\n" + ); + //Check the file size of the archive: + if (file_exists($archive->filename) && filesize($archive->filename) > $archive_max_size) { + throw new FileArchiveManagerException( + sprintf( + _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'), + relsize($archive_max_size) + ) + ); + } + if (is_array($file_list)) { + $user = $file_type->getUser(); + $file_list[] = [ + 'name' => FileManager::cleanFileName($file_type->getFilename()), + 'size' => $file_type->getSize(), + 'first_name' => ($user instanceof User) ? $user->vorname : '', + 'last_name' => ($user instanceof User) ? $user->nachname : '', + 'downloads' => $file_type->getDownloads(), + 'mkdate' => date('d.m.Y H:i', $file_type->getMakeDate()), + 'path' => ($archive_fs_path . $file_type->getFilename()) + ]; + } + return true; + } + } else { + if (!($file_type instanceof StandardFile)) { + $file_type = $file_type->convertToStandardFile(); + } + if (!($file_type instanceof StandardFile)) { + //The file type could not be converted to a standard file. + //We cannot continue. + return false; + } + //Get the file's path (if the file exists) and add the file to the archive: + $path = $file_type->getPath(); + if ($path) { + //It is a file in the file system: + if (file_exists($path)) { + $archive->addFile($path, $archive_fs_path . FileManager::cleanFileName($file_type->getFilename())); + //Check the file size of the archive: + if (file_exists($archive->filename) && filesize($archive->filename) > $archive_max_size) { + throw new FileArchiveManagerException( + sprintf( + _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'), + relsize($archive_max_size) + ) + ); + } + //Add the file to the file list (if available): + if (is_array($file_list)) { + $archive_max_num_files = Config::get()->ZIP_DOWNLOAD_MAX_FILES; + $archive_max_size = Config::get()->ZIP_DOWNLOAD_MAX_SIZE * 1024 * 1024; //1048576 bytes = 1 Mebibyte + $user = $file_type->getUser(); + $file_list[] = [ + 'name' => FileManager::cleanFileName($file_type->getFilename()), + 'size' => $file_type->getSize(), + 'first_name' => ($user instanceof User) ? $user->vorname : '', + 'last_name' => ($user instanceof User) ? $user->nachname : '', + 'downloads' => $file_type->getDownloads(), + 'mkdate' => date('d.m.Y H:i', $file_type->getMakeDate()), + 'path' => ($archive_fs_path . FileManager::cleanFileName($file_type->getFilename())) + ]; + if (count($file_list) > $archive_max_num_files) { + $archive->unchangeAll(); + unlink($archive->filename); + throw new FileArchiveManagerException( + sprintf( + _('Das Archiv beinhaltet zu viele Dateibereich-Objekte! Die Obergrenze liegt bei %d Objekten!'), + $archive_max_num_files + ) + ); + } + if (array_sum(array_column($file_list, 'size')) > $archive_max_size) { + $archive->unchangeAll(); + unlink($archive->filename); + throw new FileArchiveManagerException( + sprintf( + _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'), + relsize($archive_max_size) + ) + ); + } + } + } + } + } + } + + //Something must have gone wrong: + return false; + } + + + /** + * Adds a FileRef to a Zip archive. + * This is only a wrapper to addFileTypeToArchive that exists only + * for compatibility reasons. + * + * @see addFileTypeToArchive + */ + public static function addFileRefToArchive( + ZipArchive $archive, + FileRef $file_ref, + $user_id = null, + $archive_fs_path = '', + $do_user_permission_checks = true, + $ignore_user = false, + &$file_list = null + ) + { + $file_type = $file_ref->getFileType(); + if ($file_type instanceof FileType) { + return self::addFileTypeToArchive( + $archive, + $file_type, + $user_id, + $archive_fs_path, + $do_user_permission_checks, + $ignore_user, + $file_list + ); + } + //The file type variable does not contain a FileType object. + return false; + } + + + /** + * Adds a FolderType instance to a Zip archive. + * + * @param ZipArchive $archive The Zip archive where the FileRef shall be added to. + * @param FileRef $file_ref The FileRef which shall be added to the zip archive. + * @param string $user_id The user who wishes to add the FileRef to the archive. + * @param string $archive_fs_path The path of the folder inside the archive's file system. + * @param bool $do_user_permission_checks Set to true if reading/downloading permissions + * shall be checked. False otherwise. Default is true. + * @param bool $keep_hierarchy True, if the folder hierarchy shall be kept. + * False, if the folder hierarchy shall be flattened. + * @param bool $ignore_user Set to true, if a folder + * of type StandardFolder shall be included without checking + * if the user (identified by user_id) can read it. + * @return bool True on success, false on failure. + * + * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more + * special exception is thrown. + */ + public static function addFolderToArchive( + ZipArchive $archive, + FolderType $folder, + $user_id = null, + $archive_fs_path = '', + $do_user_permission_checks = true, + $keep_hierarchy = true, + $ignore_user = false, + &$file_list = null + ) { + + if ($do_user_permission_checks) { + //Check if the folder is readable for the user (identified by $user_id): + if (!$folder->isReadable($user_id)) { + //Folder is not readable: + return false; + } + } elseif ($ignore_user + && !($folder instanceof StandardFolder) + && in_array($folder->range_type, ['course', 'institute'])) + { + //If user permissions shall be skipped the folder must be + //an instance of StandardFolder and the folder's range type + //must be course or institute since we can only be sure + //that StandardFolder instances in courses or institutes + //are readable by everyone. + return false; + } + + $folder_zip_path = $archive_fs_path; + if ($keep_hierarchy) { + $folder_zip_path .= FileManager::cleanFileName($folder->name); + $archive->addEmptyDir($folder_zip_path); + } + foreach ($folder->getFiles() as $file) { + + /*if (!$file_ref instanceof FileRef) { TODO: OwnCloudPlugin is this ready? + $plugin = PluginManager::getInstance()->getPlugin($folder->range_id); + if (!$plugin) { + $plugin = PluginManager::getInstance()->getPlugin($folder->range_type);; + } + if ($plugin) { + $file_ref = $plugin->getPreparedFile($file_ref->id, true); + } + }*/ + + self::addFileTypeToArchive( + $archive, + $file, + $user_id, + //keep hierarchy in zip file (files and subdirectories) + $keep_hierarchy ? $folder_zip_path . '/' : '', + $do_user_permission_checks, + $ignore_user, + $file_list + ); + } + + foreach ($folder->getSubfolders() as $subfolder) { + self::addFolderToArchive( + $archive, + $subfolder, + $user_id, + //keep hierarchy in zip file (files and subdirectories) + $keep_hierarchy ? $folder_zip_path . '/' : '', + $do_user_permission_checks, + $keep_hierarchy, + $ignore_user, + $file_list + ); + } + + return true; + } + + + //ARCHIVE CREATION METHODS + + + /** + * General method for creating file archives. + * + * This method is a generalisation for all archive creation methods. + * For easier archive creation you may use the other archive creation + * methods which work with less arguments. + * + * @param Array $file_area_objects Array of FileRef, FileURL, Folder or FolderType objects. + * $file_area_objects may contain a mix between those object types. + * @param string $user_id The user who wishes to pack files. + * @param string $archive_file_path The path for the archive file. + * @param bool $do_user_permission_checks Set to true if individual + * reading/downloading permissions shall be checked. False otherwise. + * Default is true. + * @param bool $keep_hierarchy True, if the folder hierarchy shall be kept. + * False, if the folder hierarchy shall be flattened. Default is true. + * @param bool $ignore_user Set to true, if all files + * which have no download restrictions and all folders which are of type + * StandardFolder shall be included and the user-specific + * download condition check shall be ignored. + * If this parameter is set to true, the user_id parameter is irrelevant. + * The default for this parameter is false. + * @param string $zip_encoding encoding for filenames in zip + * @param bool $add_filelist_to_archive If this is set to true a file list + * in the CSV format will be added to the archive. Its name is hardcoded + * to archive_filelist.csv. The default value of $add_filelist_to_archive + * is false which means no file list is added. + * + * @return bool True, if the archive file was created and saved successfully + * at $archive_file_path, false otherwise. + * + * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more + * special exception is thrown. + */ + public static function createArchive( + $file_area_objects = [], + $user_id = null, + $archive_file_path = '', + $do_user_permission_checks = true, + $keep_hierarchy = true, + $ignore_user = false, + $zip_encoding = 'UTF-8', + $add_filelist_to_archive = false + ) + { + + // check if archive path is set: + if (!$archive_file_path) { + throw new FileArchiveManagerException( + _('Der Zielpfad für das Archiv wurde nicht angegeben!') + ); + } + + // $file_area_objects must be a non-empty array! + // Otherwise we would return an empty Zip archive. + if (!is_array($file_area_objects) || empty($file_area_objects)) { + throw new FileArchiveManagerException( + _('Es wurden keine Dateien ausgewählt!') + ); + } + + // We can create the Zip archive now since its path exists in the file system + // and furthermore there are file area objects available. + $archive = new Studip\ZipArchive(); + if (!$archive->open($archive_file_path, ZipArchive::CREATE | ZipArchive::OVERWRITE)) { + throw new FileArchiveManagerException('Error opening new ZIP archive!'); + } + $archive->setOutputEncoding($zip_encoding); + + //If $file_list is not an array + //then no files are added to the file list. + $file_list = null; + if ($add_filelist_to_archive) { + $file_list = []; + } + + foreach ($file_area_objects as $file_area_object) { + if ($file_area_object instanceof FileRef) { + self::addFileRefToArchive( + $archive, + $file_area_object, + $user_id, + '', + $do_user_permission_checks, + $ignore_user, + $file_list + ); + } elseif ($file_area_object instanceof FileType) { + self::addFileTypeToArchive( + $archive, + $file_area_object, + $user_id, + '', + $do_user_permission_checks, + $ignore_user, + $file_list + ); + } elseif ($file_area_object instanceof Folder || $file_area_object instanceof FolderType) { + $folder = $file_area_object; + if ($folder instanceof Folder) { + //We use FolderType instances here. + $folder = $folder->getTypedFolder(); + } + + self::addFolderToArchive( + $archive, + $folder, + $user_id, + '', + $do_user_permission_checks, + $keep_hierarchy, + $ignore_user, + $file_list + ); + } + } + + if ($archive->numFiles > 0) { + //At least one file is in the archive. + + if ($add_filelist_to_archive) { + //If a file list shall be included in the ZIP archive + //we must now make a CSV file out of file_list: + + $csv_data = array_merge( + [ + [ + _('Name'), + _('Größe'), + _('Vorname'), + _('Nachname'), + _('Downloads'), + _('Datum'), + _('Pfad') + ] + ], + $file_list + ); + + //The CSV file has been generated. + //Now we must add it to the archive: + $archive->addFromString('archive_filelist.csv', array_to_csv($csv_data)); + } + + //Now the ZIP file is really finished: + return $archive->close(); + } + + //empty archive + throw new FileArchiveManagerException( + _('Das ZIP Archiv enthält keine Dateien!') + ); + } + + /** + * Puts files (identified by their file refs) into one file archive. + * + * @param FileRef[] $file_refs Array of FileRef objects. + * @param User $user The user who wishes to pack files. + * @param string $archive_file_path The path for the archive file. + * @param bool $do_user_permission_checks Set to true if reading/downloading + * permissions shall be checked. False otherwise. Default is true. + * + * @return bool True, if the archive file was created and saved successfully + * at $archive_file_path, false otherwise. + * + * @throws Exception|FileArchiveManagerException If an error occurs + * a general exception or a more special exception is thrown. + */ + public static function createArchiveFromFileRefs( + $file_refs, + User $user, + $archive_file_path = '', + $do_user_permission_checks = true + ) + { + if (!$archive_file_path) { + throw new FileArchiveManagerException( + _('Der Zielpfad für das Archiv wurde nicht angegeben!') + ); + } + + //We must now collect all the files from these FileRefs and copy them + //into the new archive file. + + return self::createArchive( + $file_refs, + $user->id, + $archive_file_path, + $do_user_permission_checks, + false //do not keep the file hierarchy + ); + } + + /** + * Returns all children of a folder type. + * + * @param FolderType $folder + * @return array + */ + private static function getFolderChildren(FolderType $folder) + { + $children = []; + foreach ($folder->subfolders as $folder) { + $children[] = $folder; + } + foreach ($folder->file_refs as $ref) { + $children[] = $ref; + } + return $children; + } + + /** + * Creates an archive that contains all files of a course the given user + * is allowed to download. + * + * @param FolderType $folder The folder whose files shall be put inside an archive. + * @param string $user_id The ID of the user who wishes to put the course's files into an archive + * @param string $archive_file_path The path for the archive file. + * @param bool $do_user_permission_checks Set to true if reading/downloading permissions + * shall be checked. False otherwise. Default is true. + * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. + * If $keep_hierarchy is set to false you will get an archive that contains only files + * and no subdirectories. + * + * @return bool True, if the archive file was created and saved successfully + * at $archive_file_path, false otherwise. + * + * @throws Exception|FileArchiveManagerException If an error occurs + * a general exception or a more special exception is thrown. + */ + public static function createArchiveFromFolder( + FolderType $folder, + $user_id = null, + $archive_file_path = '', + $do_user_permission_checks = true, + $keep_hierarchy = true + ) + { + return self::createArchive( + self::getFolderChildren($folder), + $user_id, + $archive_file_path, + $do_user_permission_checks, + $keep_hierarchy + ); + } + + /** + * Creates an archive that contains all files of a course the given user + * is allowed to download. + * + * @param string $course_id The ID of the course whose files shall be put inside an archive. + * @param string $user_id The ID of the user who wishes to put the course's files into an archive + * @param string $archive_file_path The path for the archive file. + * @param bool $do_user_permission_checks Set to true if reading/downloading permissions + * shall be checked. False otherwise. Default is true. + * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. + * If $keep_hierarchy is set to false you will get an archive that contains only files + * and no subdirectories. + * + * @return bool True, if the archive file was created and saved successfully + * at $archive_file_path, false otherwise. + * + * @throws Exception|FileArchiveManagerException If an error occurs + * a general exception or a more special exception is thrown. + */ + public static function createArchiveFromCourse( + $course_id, + $user_id = null, + $archive_file_path = '', + $do_user_permission_checks = true, + $keep_hierarchy = true + ) + { + $folder = Folder::findTopFolder($course_id); + if (!$folder) { + return null; + } + + $folder = $folder->getTypedFolder(); + if (!$folder) { + return null; + } + + return self::createArchive( + self::getFolderChildren($folder), + $user_id, + $archive_file_path, + $do_user_permission_checks, + $keep_hierarchy + ); + } + + + /** + * Creates an archive that contains all files of an institute the given user + * is allowed to download. + * + * @param string $institute_id The ID of the institute whose files shall be put inside an archive. + * @param string $user_id The ID of the user who wishes to put the institute's files into an archive + * @param string $archive_file_path The path for the archive file. + * @param bool $do_user_permission_checks Set to true if reading/downloading permissions + * shall be checked. False otherwise. Default is true. + * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. + * If $keep_hierarchy is set to false you will get an archive that contains only files + * and no subdirectories. + * + * @return bool True, if the archive file was created and saved successfully + * at $archive_file_path, false otherwise. + * + * @throws Exception|FileArchiveManagerException If an error occurs + * a general exception or a more special exception is thrown. + */ + public static function createArchiveFromInstitute( + $institute_id, + $user_id = null, + $archive_file_path = '', + $do_user_permission_checks = true, + $keep_hierarchy = true) + { + $folder = Folder::findTopFolder($institute_id); + if(!$folder) { + return null; + } + + $folder = $folder->getTypedFolder(); + if(!$folder) { + return null; + } + + return self::createArchive( + self::getFolderChildren($folder), + $archive_file_path, + $do_user_permission_checks, + $keep_hierarchy + ); + } + + /** + * Creates an archive that contains all files of a user, if the current + * user has root permissions to do this. + * + * @param string $user_id The ID of the user whose files shall be put inside an archive. + * @param string $archive_file_path The path for the archive file. + * @param bool $do_user_permission_checks Set to true if reading/downloading permissions + * shall be checked. False otherwise. Default is true. + * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive. + * If $keep_hierarchy is set to false you will get an archive that contains only files + * and no subdirectories. + * + * @return bool True, if the archive file was created and saved successfully + * at $archive_file_path, false otherwise. + * + * @throws Exception|FileArchiveManagerException If an error occurs + * a general exception or a more special exception is thrown. + */ + public static function createArchiveFromUser( + $user_id, + $archive_file_path = '', + $do_user_permission_checks = true, + $keep_hierarchy = true + ) + { + $folder = Folder::findTopFolder($user_id); + if (!$folder) { + return null; + } + + $folder = $folder->getTypedFolder(); + if (!$folder) { + return null; + } + + return self::createArchive( + self::getFolderChildren($folder), + $archive_file_path, + $do_user_permission_checks, + $keep_hierarchy + ); + } + + + /** + * This method creates an archive with the content of a physical folder + * (A folder inside the operating system's file system). + * + * @param string $folder_path The path to the physical folder + * which content shall be added to a file archive. + * @param string $archive_file_path The path to the archive file which + * shall be created. + * + * @return True, if all files were added successfully, false otherwise. + * + * @throws Exception|FileArchiveManagerException If an error occurs + * a general exception or a more special exception is thrown. + */ + public static function createArchiveFromPhysicalFolder($folder_path, $archive_file_path) + { + if (!$folder_path || !$archive_file_path) { + //we can't work with empty paths! + return false; + } + + if (!file_exists($folder_path)) { + //path to physical folder does not exist! + throw new FileArchiveManagerException( + _('Der Ordner wurde im Dateisystem des Servers nicht gefunden!') + ); + } + + //Put all the content of the folder inside an archive: + $archive = Studip\ZipArchive::create($archive_file_path, true); + $result = $archive->addFromPath($folder_path); + $archive->close(); + return $result; + } + + //ARCHIVE EXTRACTION METHODS + + /** + * This is a helper method that builds a subfolder hierarchy inside + * a folder by looking at a string representing a file system path. + * + * The variable $path contains a hierarchy of subfolders that shall be created + * inside the given folder. If $path contains "folder1/folder2/folder3" then + * the given folder will get a subfolder named "folder1". The folder + * "folder1" itself will get a subfolder named "folder2" and so on. + * + * @param FolderType $folder The folder where a subfolder path shall be created. + * @param User $user The user who wishes to create the path. + * @param string $path The path which shall be created inside $folder. + * + * @return FolderType[] An array with FolderType objects representing + * each element of $path. + */ + public static function createFolderPath(FolderType $folder, User $user, $path = '') + { + if (!$path) { + return []; + } + + // now we strip leading and trailing slashes, whitespaces and other characters: + // then we convert path into an array of strings: + $path = trim($path, ' /'); + $path = explode('/', $path); + + //now we loop through path and build subfolders: + $folder_path = []; + + $current_folder = $folder; + foreach ($path as $new_folder_name) { + //first we check if the folder already exists: + foreach ($current_folder->getSubfolders() as $subfolder) { + if ($subfolder->name === $new_folder_name) { + //We have found a folder that has the name $new_folder_name: + //No need to create a new folder, we can use that folder + //and continue with it: + $current_folder = $subfolder; + $folder_path[] = $subfolder; + + //start next iteration of the outer foreach loop: + continue 2; + } + } + + //If code execution has reached this point we have looped + //throug all subfolders of the current folder and couldn't find + //any subfolder that matches the name given in $new_folder_name. + //Therefore we must create a new folder here, if possible: + + //Check the user's permissions first: + if ($current_folder->isSubfolderAllowed($user->id)) { + //Create a subfolder: + $result = FileManager::createSubFolder( + $current_folder, + $user, + get_class($current_folder) === RootFolder::class ? StandardFolder::class : get_class($current_folder), + $new_folder_name + ); + + if ($result instanceof FolderType) { + $folder_path[] = $result; + } + } + } + return $folder_path; + } + + /** + * Extracts one file from an opened archive and stores it in a folder. + * + * @param ZipArchive $archive The archive from which a file shall be extracted. + * @param string $archive_path The path of the file in the archive. + * @param FolderType $target_folder The folder where the file shall be stored. + * @param User $user The user who wishes to extract the file from the archive. + * + * @return FileType|null FileType instance on success, null otherwise. + */ + public static function extractFileFromArchive( + Studip\ZipArchive $archive, + $archive_path, + FolderType $target_folder, + User $user + ) + { + $file_resource = $archive->getStream($archive_path); + $file_info = $archive->statName($archive_path); + + if (!$file_resource) { + return null; + } + + $studip_file = new File(); + $studip_file->user_id = $user->id; + $studip_file->name = $archive->convertArchiveFilename(basename($archive_path)); + $studip_file->mime_type = get_mime_type($studip_file->name); + $studip_file->size = $file_info['size']; + $studip_file->id = $studip_file->getNewId(); + //$file->store(); + + // Ok, we have a file object in the database. Now we must connect + // it with the data file by extracting the data file into + // the place, where the file's content has to be placed. + $file_dir = pathinfo($studip_file->getPath(), PATHINFO_DIRNAME); + $file_path = $file_dir . '/' . $studip_file->id; + + // Create the directory for the file, if necessary: + if (!is_dir($file_dir)) { + mkdir($file_dir); + } + + // Ok, now we read all data from $file_resource and put it into + // the file's path: + if (file_put_contents($file_path, $file_resource) === false) { + //Something went wrong: abort and clean up! + //$file->delete(); + return null; + } + + // Ok, we now must create a File: + $file_ref = new FileRef(); + $file_ref->file_id = $studip_file->id; + $file_ref->folder_id = $target_folder->getId(); + $file_ref->user_id = $user->id; + $file_ref->name = $studip_file->name; + $file_ref->file = $studip_file; + $file = new StandardFile($file_ref); + if ($saved_file = $target_folder->addFile($file, $user->id)) { + return $saved_file; + } + + //Something went wrong: + return null; + } + + /** + * Extracts an archive into a folder inside the Stud.IP file area. + * + * @param FileType $archive_file The archive file which shall be extracted. + * @param FolderType $folder The folder where the archive shall be extracted. + * @param string $user_id The ID of the user who wants to extract the archive. + * + * @return FileType[] Array with extracted files, represented as FileRef objects. + */ + public static function extractArchiveFileToFolder( + FileType $archive_file, + FolderType $folder, + $user_id = null + ) + { + $user = $user_id ? User::find($user_id) : User::findCurrent(); + if (!$user) { + return []; + } + + // Determine, if the folder is writable for the user identified by $user_id: + if (!$folder->isWritable($user->id)) { + return []; + } + + // Determine if we can keep the zip archive's folder hierarchy: + $keep_hierarchy = $folder->isSubfolderAllowed($user->id); + + $archive = new Studip\ZipArchive(); + $standard_archive_file = $archive_file->convertToStandardFile(); + if (!($standard_archive_file instanceof StandardFile)) { + //Error converting the archive file. + return []; + } + $archive->open($standard_archive_file->getPath()); + + // loop over all entries in the zip archive and put each entry + // in the current folder or one of its subfolders: + $files = []; + + for ($i = 0; $i < $archive->numFiles; $i++) { + $entry_info = $archive->statIndex($i); + $entry_info_name = $archive->convertArchiveFilename($entry_info['name']); + // split the entry's path into its path and its name component: + $entry_path = ltrim(pathinfo($entry_info_name, PATHINFO_DIRNAME), '.'); + $entry_name = pathinfo($entry_info_name, PATHINFO_BASENAME); + + // check if $entry_info['name'] ends with a slash: + // In that case it is a directory entry: + $entry_is_directory = preg_match('/\/$/', $entry_info_name); + + //The folder where the extracted file/folder shall be inserted: + $extracted_entry_destination_folder = $folder; + + if ($keep_hierarchy) { + //Keep the archive's folder hierarchy: + //We may have to create subfolders. + if (basename($entry_path)) { + //The file/folder doesn't lie in the "top folder" of the archive: + //Pass the path to createFolderPath and let it generate + //a folder path before extracting the file: + $folder_path = self::createFolderPath( + $folder, + $user, + $entry_path + ); + + //Get the last element of $folder_path: + $last_folder_path_element = array_pop($folder_path); + + //Compare $extracted_entry_destination_folder's name with the name of the + //last path item in $file_archive_path. Only if they are equal + //we can use that folder to store the file. Otherwise + //we must continue with the next file entry in the archive: + if ($last_folder_path_element + && $last_folder_path_element->name === basename($entry_path)) + { + $extracted_entry_destination_folder = $last_folder_path_element; + } + } + } + + if ($entry_is_directory) { + //We have to create a subfolder if it doesn't exist yet: + self::createFolderPath( + $extracted_entry_destination_folder, + $user, + $entry_name + ); + } else { + //we extract one file: + //$entry_info['name'] is necessary because we need the full path + //to the entry inside the archive. + $file = self::extractFileFromArchive( + $archive, + $entry_info['name'], + $extracted_entry_destination_folder, + $user + ); + + if ($file instanceof FileType) { + $files[] = $file; + } + } + } + + return $files; + } +} diff --git a/lib/filesystem/FileArchiveManagerException.class.php b/lib/filesystem/FileArchiveManagerException.class.php deleted file mode 100644 index 17b5306..0000000 --- a/lib/filesystem/FileArchiveManagerException.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @copyright 2016 data-quest - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -class FileArchiveManagerException extends Exception -{ -} diff --git a/lib/filesystem/FileArchiveManagerException.php b/lib/filesystem/FileArchiveManagerException.php new file mode 100644 index 0000000..17b5306 --- /dev/null +++ b/lib/filesystem/FileArchiveManagerException.php @@ -0,0 +1,17 @@ + + * @copyright 2016 data-quest + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ +class FileArchiveManagerException extends Exception +{ +} diff --git a/lib/filesystem/LibraryFile.class.php b/lib/filesystem/LibraryFile.class.php deleted file mode 100644 index c9524e3..0000000 --- a/lib/filesystem/LibraryFile.class.php +++ /dev/null @@ -1,401 +0,0 @@ - - * @copyright 2020 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.6 - */ - - -class LibraryFile extends StandardFile -{ - public $library_document = null; - - - public function __construct($fileref, $file = null) - { - parent::__construct($fileref, $file); - - if (is_object($this->file->metadata)) { - $this->library_document = LibraryDocument::createFromArray($this->file->metadata->getArrayCopy()); - } - } - - public function getFile() - { - return $this->file; - } - - public function getFileRef() - { - return $this->fileref; - } - - public static function createFromLibraryDocument(LibraryDocument $document, $folder_id = null, $user_id = null) - { - $file = new File(); - $file->name = ''; - $file->size = '0'; - $file->mime_type = ''; - $file->metadata = $document->toJson(); - $file->user_id = $user_id ? $user_id : $GLOBALS['user']->id; - $file->filetype = get_called_class(); - if ($document->csl_data['URL'] || $document->opac_link) { - $file->metadata['url'] = $document->opac_link ?: $document->csl_data['URL']; - $file->metadata['access_type'] = 'redirect'; - } - $file->store(); - - $file_ref = new FileRef(); - $file_ref->file_id = $file->id; - $file_ref->folder_id = $folder_id ? $folder_id : ''; - $file_ref->name = $file->name; - $file_ref->downloads = 0; - $file_ref->description = $document->csl_data['description'] ? $document->csl_data['description'] : ''; - $file_ref->content_terms_of_use_id = ContentTermsOfUse::findDefault()->id; - $file_ref->user_id = $file->user_id; - $file_ref->store(); - - return new LibraryFile($file_ref); - } - - - /** - * Updates the LibraryFile by using a LibraryDocument instance. - * - * @param LibraryDocument $document The document containing the new data. - * - * @returns bool True, if the update was successful, false otherwise. - */ - public function updateFromLibraryDocument(LibraryDocument $document) : bool - { - if ($this->file instanceof File) { - $file_name = self::getFileNameFromDocument($document); - $this->file->name = $file_name; - $this->file->metadata = $document->toJson(); - if ($this->file->isDirty()) { - if (!$this->file->store()) { - return false; - } - } - $this->fileref->name = $this->file->name; - if ($this->fileref->isDirty()) { - return $this->fileref->store(); - } - return true; - } else { - return false; - } - } - - - /** - * Extracts a file name from a LibraryDocument instance. - * This is an internal helper method. - * - * @param LibraryDocument $document The document from which a file name - * shall be extracted. - * - * @returns string The extracted file name. - */ - protected static function getFileNameFromDocument(LibraryDocument $document) : string - { - $file_name = $document->getTitle(); - if (!$file_name) { - //The document hasn't got a title. We can still generate a file name - //using the search parameters of the document. - if ($document->search_params[LibrarySearch::TITLE]) { - $file_name = $document->search_params[LibrarySearch::TITLE]; - } elseif ($document->search_params[LibrarySearch::AUTHOR]) { - if ($document->search_params[LibrarySearch::YEAR]) { - $file_name = sprintf( - '%1$s (%2$s)', - $document->search_params[LibrarySearch::AUTHOR], - $document->search_params[LibrarySearch::YEAR] - ); - } else { - $file_name = $document->search_params[LibrarySearch::AUTHOR]; - } - } elseif ($document->search_params[LibrarySearch::NUMBER]) { - $file_name = $document->search_params[LibrarySearch::NUMBER]; - } elseif ($document->search_params[LibrarySearch::SIGNATURE]) { - $file_name = $document->search_params[LibrarySearch::SIGNATURE]; - } else { - $file_name = _('unbekannt'); - } - } - return $file_name; - } - - - public function getIcon($role) - { - if ($this->library_document instanceof LibraryDocument) { - $icon = $this->library_document->getIcon(); - } - return $icon && $icon->getShape() !== 'literature-request' ? $icon : Icon::create('literature', $role); - } - - - public function getFilename() - { - $file_name = ''; - if ($this->library_document instanceof LibraryDocument) { - $file_name = $this->library_document->getTitle('long'); - } - if (!$file_name && $this->library_document->search_params) { - $formatted_params = $this->library_document->getSearchDescription(); - $file_name = _('Suche in der Bibliothek'); - if ($formatted_params) { - $file_name .= ': ' . implode(', ', $formatted_params); - } - } - if (!$file_name && ($this->fileref instanceof FileRef)) { - $file_name = $this->fileref->name; - } - return $file_name; - } - - - public function getSize() - { - if ($this->fileref instanceof FileRef) { - return $this->fileref['size']; - } - return null; - } - - - public function getDownloadURL() - { - if ($this->hasFileAttached() || $this->hasURL()) { - if ($this->fileref instanceof FileRef) { - return $this->fileref->getDownloadURL(); - } - } - return ''; - } - - - public function getPath() : string - { - return $this->file instanceof File ? $this->file->getPath() : ''; - } - - - public function getDownloads() - { - if ($this->fileref instanceof FileRef) { - return $this->fileref['downloads']; - } - return 0; - } - - - public function getActionmenu() - { - $action_menu = ActionMenu::get(); - $action_menu->addLink( - URLHelper::getURL(sprintf('dispatch.php/file/details/%s/1', $this->fileref->id)), - _('Info'), - Icon::create('info-circle', Icon::ROLE_CLICKABLE, ['size' => 20]), - ['data-dialog' => ''], - 'file-display-info' - ); - if (Config::get()->LITERATURE_ENABLE && Context::get() && $GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) { - $plugin_manager = PluginManager::getInstance(); - $library_plugins = $plugin_manager->getPlugins(LibraryPlugin::class); - if (count($library_plugins)) { - $plugin = $library_plugins[0]; - $action_menu->addLink( - $plugin->getRequestURL($this->getId()), - $plugin->getRequestTitle(), - $plugin->getRequestIcon(), - ['data-dialog' => 'size=auto;reload-on-close;id=' . $plugin->getPluginId()] - ); - } - } - if (parent::isWritable($GLOBALS['user']->id)) { - //Before the edit action can be displayed, we must make sure - //the library document is one that is defined. - $known_document_type = false; - foreach ($GLOBALS['LIBRARY_DOCUMENT_TYPES'] as $defined_type) { - if ($defined_type['name'] == $this->library_document->type) { - $known_document_type = true; - break; - } - } - if ($known_document_type) { - $action_menu->addLink( - URLHelper::getURL('dispatch.php/library_file/edit/' . $this->fileref->id), - _('Bearbeiten'), - Icon::create('edit', Icon::ROLE_CLICKABLE, ['size' => 20]), - ['data-dialog' => 'size=auto'] - ); - } - $action_menu->addButton( - 'delete', - _('Datei löschen'), - Icon::create('trash', Icon::ROLE_CLICKABLE, ['size' => 20]), - [ - 'formaction' => URLHelper::getURL("dispatch.php/file/delete/{$this->fileref->id}"), - 'data-confirm' => sprintf(_('Soll die Datei "%s" wirklich gelöscht werden?'), $this->fileref->name), - ] - ); - } - return $action_menu; - } - - - public function getInfoDialogButtons(array $extra_link_params = []) : array - { - $buttons = []; - if ($this->isEditable($GLOBALS['user']->id)) { - $known_document_type = false; - foreach ($GLOBALS['LIBRARY_DOCUMENT_TYPES'] as $defined_type) { - if ($defined_type['name'] == $this->library_document->type) { - $known_document_type = true; - break; - } - } - if ($known_document_type) { - $buttons[] = Studip\LinkButton::create( - _('Bearbeiten'), - URLHelper::getURL('dispatch.php/library_file/edit/' . $this->fileref->id), - ['data-dialog' => 'size=auto'] - ); - } - } - - if ($this->isDownloadable($GLOBALS['user']->id) && $this->getDownloadURL()) { - $buttons[] = Studip\LinkButton::create( - $this->hasFileAttached() ? _('Herunterladen') : _('Öffnen'), - $this->getDownloadURL(), - ['target' => '_blank'] - ); - } - - return $buttons; - } - - - public function isEditable($user_id = null) - { - if ($this->fileref instanceof FileRef) { - return parent::isEditable($user_id); - } - return false; - } - - - public function getInfoTemplate(bool $include_downloadable_infos = false) - { - if (!$this->library_document) { - return null; - } - return $this->library_document->getInfoTemplate('full'); - } - - - /** - * This is a method special to the LibraryFile class. - * It handles the attachment of a (real) File object to this LibraryFile. - * In that case, the library metadata must be transferred from the (virtual) - * file that is attached to the LibraryFile to the specified file that - * shall be attached. - * - * @param File $file The file to attach to this LibraryFile. - * - * @throws Exception In case of an error, an exception with an error message - * is thrown. - */ - public function attachFile(File $file) : bool - { - if ($file->isNew()) { - //The file must be stored in the database. - if (!$file->store()) { - throw new Exception(_('Fehler beim Speichern der Datei!')); - } - } - if ($this->fileref instanceof FileRef) { - $old_file = $this->fileref->file; - if (!($old_file instanceof File)) { - throw new Exception(_('Es gibt kein Dateiobjekt zum Bibliothekseintrag!')); - } - if ($old_file->id == $file->id) { - //The file is already attached. - return true; - } - $metadata = $old_file->metadata; - if (!$metadata) { - //Something went wrong. - throw new Exception(_('Das Dateiobjekt zum Bibliothekseintrag hat keine Metadaten!')); - } - $file->metadata = $metadata; - $file->filetype = get_class($this); - if ($file->isDirty()) { - if (!$file->store()) { - throw new Exception(_('Die neue Datei konnte nicht gespeichert werden!')); - } - } - $this->fileref->file_id = $file->id; - $this->fileref->name = $file->name; - if ($this->fileref->isDirty()) { - if ($this->fileref->store()) { - $old_file->delete(); - } else { - throw new Exception(_('Die Verknüfpung der Datei mit dem Bibliothekseintrag konnte nicht gespeichert werden!')); - } - } - $this->file = $file; - } else { - throw new Exception(_('Der Bibliothekseintrag ist nicht mit einer virtuellen Datei verknüpft!')); - } - - return true; - } - - - /** - * This method checks if a real file is attached to this library file. - * - * @returns bool True, if a real file is attached, false otherwise. - */ - public function hasFileAttached() : bool - { - return file_exists($this->file->path); - } - - public function hasURL() - { - if (isset($this->file->metadata['csl_data']['URL']) && !isset($this->file->metadata['url'])) { - $this->file->metadata['url'] = $this->file->metadata['csl_data']['URL']; - $this->file->metadata['access_type'] = 'redirect'; - $this->file->store(); - } - return isset($this->file->metadata['url']); - } - - public function removeDataFile() - { - if ($this->hasFileAttached()) { - $this->file->deleteDataFile(); - $this->file->mime_type = ''; - $this->file->name = ''; - $this->file->size = 0; - $this->file->metadata['url'] = null; - $this->file->metadata['access_type'] = null; - $this->fileref->name = ''; - $this->fileref->store(); - return $this->file->store(); - } - } -} diff --git a/lib/filesystem/LibraryFile.php b/lib/filesystem/LibraryFile.php new file mode 100644 index 0000000..c9524e3 --- /dev/null +++ b/lib/filesystem/LibraryFile.php @@ -0,0 +1,401 @@ + + * @copyright 2020 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.6 + */ + + +class LibraryFile extends StandardFile +{ + public $library_document = null; + + + public function __construct($fileref, $file = null) + { + parent::__construct($fileref, $file); + + if (is_object($this->file->metadata)) { + $this->library_document = LibraryDocument::createFromArray($this->file->metadata->getArrayCopy()); + } + } + + public function getFile() + { + return $this->file; + } + + public function getFileRef() + { + return $this->fileref; + } + + public static function createFromLibraryDocument(LibraryDocument $document, $folder_id = null, $user_id = null) + { + $file = new File(); + $file->name = ''; + $file->size = '0'; + $file->mime_type = ''; + $file->metadata = $document->toJson(); + $file->user_id = $user_id ? $user_id : $GLOBALS['user']->id; + $file->filetype = get_called_class(); + if ($document->csl_data['URL'] || $document->opac_link) { + $file->metadata['url'] = $document->opac_link ?: $document->csl_data['URL']; + $file->metadata['access_type'] = 'redirect'; + } + $file->store(); + + $file_ref = new FileRef(); + $file_ref->file_id = $file->id; + $file_ref->folder_id = $folder_id ? $folder_id : ''; + $file_ref->name = $file->name; + $file_ref->downloads = 0; + $file_ref->description = $document->csl_data['description'] ? $document->csl_data['description'] : ''; + $file_ref->content_terms_of_use_id = ContentTermsOfUse::findDefault()->id; + $file_ref->user_id = $file->user_id; + $file_ref->store(); + + return new LibraryFile($file_ref); + } + + + /** + * Updates the LibraryFile by using a LibraryDocument instance. + * + * @param LibraryDocument $document The document containing the new data. + * + * @returns bool True, if the update was successful, false otherwise. + */ + public function updateFromLibraryDocument(LibraryDocument $document) : bool + { + if ($this->file instanceof File) { + $file_name = self::getFileNameFromDocument($document); + $this->file->name = $file_name; + $this->file->metadata = $document->toJson(); + if ($this->file->isDirty()) { + if (!$this->file->store()) { + return false; + } + } + $this->fileref->name = $this->file->name; + if ($this->fileref->isDirty()) { + return $this->fileref->store(); + } + return true; + } else { + return false; + } + } + + + /** + * Extracts a file name from a LibraryDocument instance. + * This is an internal helper method. + * + * @param LibraryDocument $document The document from which a file name + * shall be extracted. + * + * @returns string The extracted file name. + */ + protected static function getFileNameFromDocument(LibraryDocument $document) : string + { + $file_name = $document->getTitle(); + if (!$file_name) { + //The document hasn't got a title. We can still generate a file name + //using the search parameters of the document. + if ($document->search_params[LibrarySearch::TITLE]) { + $file_name = $document->search_params[LibrarySearch::TITLE]; + } elseif ($document->search_params[LibrarySearch::AUTHOR]) { + if ($document->search_params[LibrarySearch::YEAR]) { + $file_name = sprintf( + '%1$s (%2$s)', + $document->search_params[LibrarySearch::AUTHOR], + $document->search_params[LibrarySearch::YEAR] + ); + } else { + $file_name = $document->search_params[LibrarySearch::AUTHOR]; + } + } elseif ($document->search_params[LibrarySearch::NUMBER]) { + $file_name = $document->search_params[LibrarySearch::NUMBER]; + } elseif ($document->search_params[LibrarySearch::SIGNATURE]) { + $file_name = $document->search_params[LibrarySearch::SIGNATURE]; + } else { + $file_name = _('unbekannt'); + } + } + return $file_name; + } + + + public function getIcon($role) + { + if ($this->library_document instanceof LibraryDocument) { + $icon = $this->library_document->getIcon(); + } + return $icon && $icon->getShape() !== 'literature-request' ? $icon : Icon::create('literature', $role); + } + + + public function getFilename() + { + $file_name = ''; + if ($this->library_document instanceof LibraryDocument) { + $file_name = $this->library_document->getTitle('long'); + } + if (!$file_name && $this->library_document->search_params) { + $formatted_params = $this->library_document->getSearchDescription(); + $file_name = _('Suche in der Bibliothek'); + if ($formatted_params) { + $file_name .= ': ' . implode(', ', $formatted_params); + } + } + if (!$file_name && ($this->fileref instanceof FileRef)) { + $file_name = $this->fileref->name; + } + return $file_name; + } + + + public function getSize() + { + if ($this->fileref instanceof FileRef) { + return $this->fileref['size']; + } + return null; + } + + + public function getDownloadURL() + { + if ($this->hasFileAttached() || $this->hasURL()) { + if ($this->fileref instanceof FileRef) { + return $this->fileref->getDownloadURL(); + } + } + return ''; + } + + + public function getPath() : string + { + return $this->file instanceof File ? $this->file->getPath() : ''; + } + + + public function getDownloads() + { + if ($this->fileref instanceof FileRef) { + return $this->fileref['downloads']; + } + return 0; + } + + + public function getActionmenu() + { + $action_menu = ActionMenu::get(); + $action_menu->addLink( + URLHelper::getURL(sprintf('dispatch.php/file/details/%s/1', $this->fileref->id)), + _('Info'), + Icon::create('info-circle', Icon::ROLE_CLICKABLE, ['size' => 20]), + ['data-dialog' => ''], + 'file-display-info' + ); + if (Config::get()->LITERATURE_ENABLE && Context::get() && $GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) { + $plugin_manager = PluginManager::getInstance(); + $library_plugins = $plugin_manager->getPlugins(LibraryPlugin::class); + if (count($library_plugins)) { + $plugin = $library_plugins[0]; + $action_menu->addLink( + $plugin->getRequestURL($this->getId()), + $plugin->getRequestTitle(), + $plugin->getRequestIcon(), + ['data-dialog' => 'size=auto;reload-on-close;id=' . $plugin->getPluginId()] + ); + } + } + if (parent::isWritable($GLOBALS['user']->id)) { + //Before the edit action can be displayed, we must make sure + //the library document is one that is defined. + $known_document_type = false; + foreach ($GLOBALS['LIBRARY_DOCUMENT_TYPES'] as $defined_type) { + if ($defined_type['name'] == $this->library_document->type) { + $known_document_type = true; + break; + } + } + if ($known_document_type) { + $action_menu->addLink( + URLHelper::getURL('dispatch.php/library_file/edit/' . $this->fileref->id), + _('Bearbeiten'), + Icon::create('edit', Icon::ROLE_CLICKABLE, ['size' => 20]), + ['data-dialog' => 'size=auto'] + ); + } + $action_menu->addButton( + 'delete', + _('Datei löschen'), + Icon::create('trash', Icon::ROLE_CLICKABLE, ['size' => 20]), + [ + 'formaction' => URLHelper::getURL("dispatch.php/file/delete/{$this->fileref->id}"), + 'data-confirm' => sprintf(_('Soll die Datei "%s" wirklich gelöscht werden?'), $this->fileref->name), + ] + ); + } + return $action_menu; + } + + + public function getInfoDialogButtons(array $extra_link_params = []) : array + { + $buttons = []; + if ($this->isEditable($GLOBALS['user']->id)) { + $known_document_type = false; + foreach ($GLOBALS['LIBRARY_DOCUMENT_TYPES'] as $defined_type) { + if ($defined_type['name'] == $this->library_document->type) { + $known_document_type = true; + break; + } + } + if ($known_document_type) { + $buttons[] = Studip\LinkButton::create( + _('Bearbeiten'), + URLHelper::getURL('dispatch.php/library_file/edit/' . $this->fileref->id), + ['data-dialog' => 'size=auto'] + ); + } + } + + if ($this->isDownloadable($GLOBALS['user']->id) && $this->getDownloadURL()) { + $buttons[] = Studip\LinkButton::create( + $this->hasFileAttached() ? _('Herunterladen') : _('Öffnen'), + $this->getDownloadURL(), + ['target' => '_blank'] + ); + } + + return $buttons; + } + + + public function isEditable($user_id = null) + { + if ($this->fileref instanceof FileRef) { + return parent::isEditable($user_id); + } + return false; + } + + + public function getInfoTemplate(bool $include_downloadable_infos = false) + { + if (!$this->library_document) { + return null; + } + return $this->library_document->getInfoTemplate('full'); + } + + + /** + * This is a method special to the LibraryFile class. + * It handles the attachment of a (real) File object to this LibraryFile. + * In that case, the library metadata must be transferred from the (virtual) + * file that is attached to the LibraryFile to the specified file that + * shall be attached. + * + * @param File $file The file to attach to this LibraryFile. + * + * @throws Exception In case of an error, an exception with an error message + * is thrown. + */ + public function attachFile(File $file) : bool + { + if ($file->isNew()) { + //The file must be stored in the database. + if (!$file->store()) { + throw new Exception(_('Fehler beim Speichern der Datei!')); + } + } + if ($this->fileref instanceof FileRef) { + $old_file = $this->fileref->file; + if (!($old_file instanceof File)) { + throw new Exception(_('Es gibt kein Dateiobjekt zum Bibliothekseintrag!')); + } + if ($old_file->id == $file->id) { + //The file is already attached. + return true; + } + $metadata = $old_file->metadata; + if (!$metadata) { + //Something went wrong. + throw new Exception(_('Das Dateiobjekt zum Bibliothekseintrag hat keine Metadaten!')); + } + $file->metadata = $metadata; + $file->filetype = get_class($this); + if ($file->isDirty()) { + if (!$file->store()) { + throw new Exception(_('Die neue Datei konnte nicht gespeichert werden!')); + } + } + $this->fileref->file_id = $file->id; + $this->fileref->name = $file->name; + if ($this->fileref->isDirty()) { + if ($this->fileref->store()) { + $old_file->delete(); + } else { + throw new Exception(_('Die Verknüfpung der Datei mit dem Bibliothekseintrag konnte nicht gespeichert werden!')); + } + } + $this->file = $file; + } else { + throw new Exception(_('Der Bibliothekseintrag ist nicht mit einer virtuellen Datei verknüpft!')); + } + + return true; + } + + + /** + * This method checks if a real file is attached to this library file. + * + * @returns bool True, if a real file is attached, false otherwise. + */ + public function hasFileAttached() : bool + { + return file_exists($this->file->path); + } + + public function hasURL() + { + if (isset($this->file->metadata['csl_data']['URL']) && !isset($this->file->metadata['url'])) { + $this->file->metadata['url'] = $this->file->metadata['csl_data']['URL']; + $this->file->metadata['access_type'] = 'redirect'; + $this->file->store(); + } + return isset($this->file->metadata['url']); + } + + public function removeDataFile() + { + if ($this->hasFileAttached()) { + $this->file->deleteDataFile(); + $this->file->mime_type = ''; + $this->file->name = ''; + $this->file->size = 0; + $this->file->metadata['url'] = null; + $this->file->metadata['access_type'] = null; + $this->fileref->name = ''; + $this->fileref->store(); + return $this->file->store(); + } + } +} diff --git a/lib/filesystem/ResourceFolder.class.php b/lib/filesystem/ResourceFolder.class.php deleted file mode 100644 index ce7b216..0000000 --- a/lib/filesystem/ResourceFolder.class.php +++ /dev/null @@ -1,149 +0,0 @@ -range_id); - $user = User::find($user_id); - - if (($resource instanceof Resource) && ($user instanceof User)) { - return true; - } elseif ($user instanceof User) { - //Check global permissions: - return ResourceManager::userHasGlobalPermission( - $user, - 'admin' - ); - } - return false; - } - - public function isReadable($user_id) - { - if ($user_id == 'nobody') { - return false; - } - //Get the resource object: - $resource = Resource::find($this->range_id); - $user = User::find($user_id); - - if (($resource instanceof Resource) && ($user instanceof User)) { - return true; - } elseif ($user instanceof User) { - //Check global permissions: - return ResourceManager::userHasGlobalPermission( - $user, - 'admin' - ); - } - return false; - } - - public function isWritable($user_id) - { - $user = User::find($user_id); - - //Check global permissions: The user has to be - //a global resource admin or a root user. - return ResourceManager::userHasGlobalPermission( - $user, - 'admin' - ); - } - - public function isEditable($user_id) - { - //Thou shalt not edit ResourceFolder folder types! - return false; - } - - public function isSubfolderAllowed($user_id) - { - //Furthermore, thou shalt not create subfolders in a Resource folder! - return false; - } - - public function getSubfolder() - { - //No subfolders allowed, resulting in: - return []; - } - - public function getEditTemplate() - { - return ''; - } - - public function setDataFromEditTemplate($folderdata) - { - return MessageBox::error( - _('Ressourcenordner dürfen nicht geändert werden!') - ); - } - - public function createSubfolder(FolderType $foldertype) - { - //No subfolders allowed, resulting in: - return null; - } - - public function deleteSubfolder($subfolder_id) - { - //No subfolders allowed, resulting in: - return false; - } - - public function isFileDownloadable($file_ref_id, $user_id) - { - return $this->isReadable($user_id); - } - - public function isFileEditable($file_ref_id, $user_id) - { - return $this->isWritable($user_id); - } - - public function isFileWritable($file_ref_id, $user_id) - { - return $this->isWritable($user_id); - } -} diff --git a/lib/filesystem/ResourceFolder.php b/lib/filesystem/ResourceFolder.php new file mode 100644 index 0000000..ce7b216 --- /dev/null +++ b/lib/filesystem/ResourceFolder.php @@ -0,0 +1,149 @@ +range_id); + $user = User::find($user_id); + + if (($resource instanceof Resource) && ($user instanceof User)) { + return true; + } elseif ($user instanceof User) { + //Check global permissions: + return ResourceManager::userHasGlobalPermission( + $user, + 'admin' + ); + } + return false; + } + + public function isReadable($user_id) + { + if ($user_id == 'nobody') { + return false; + } + //Get the resource object: + $resource = Resource::find($this->range_id); + $user = User::find($user_id); + + if (($resource instanceof Resource) && ($user instanceof User)) { + return true; + } elseif ($user instanceof User) { + //Check global permissions: + return ResourceManager::userHasGlobalPermission( + $user, + 'admin' + ); + } + return false; + } + + public function isWritable($user_id) + { + $user = User::find($user_id); + + //Check global permissions: The user has to be + //a global resource admin or a root user. + return ResourceManager::userHasGlobalPermission( + $user, + 'admin' + ); + } + + public function isEditable($user_id) + { + //Thou shalt not edit ResourceFolder folder types! + return false; + } + + public function isSubfolderAllowed($user_id) + { + //Furthermore, thou shalt not create subfolders in a Resource folder! + return false; + } + + public function getSubfolder() + { + //No subfolders allowed, resulting in: + return []; + } + + public function getEditTemplate() + { + return ''; + } + + public function setDataFromEditTemplate($folderdata) + { + return MessageBox::error( + _('Ressourcenordner dürfen nicht geändert werden!') + ); + } + + public function createSubfolder(FolderType $foldertype) + { + //No subfolders allowed, resulting in: + return null; + } + + public function deleteSubfolder($subfolder_id) + { + //No subfolders allowed, resulting in: + return false; + } + + public function isFileDownloadable($file_ref_id, $user_id) + { + return $this->isReadable($user_id); + } + + public function isFileEditable($file_ref_id, $user_id) + { + return $this->isWritable($user_id); + } + + public function isFileWritable($file_ref_id, $user_id) + { + return $this->isWritable($user_id); + } +} diff --git a/lib/ilias_interface/ConnectedIlias.class.php b/lib/ilias_interface/ConnectedIlias.class.php deleted file mode 100644 index 5b9c1bf..0000000 --- a/lib/ilias_interface/ConnectedIlias.class.php +++ /dev/null @@ -1,1370 +0,0 @@ - -* @access public -* @modulegroup ilias_interface_modules -* @module ConnectedIlias -* @package ILIAS-Interface -*/ -class ConnectedIlias -{ - const CRS_NOTIFICATION= '1'; - const CRS_NO_NOTIFICATION= '2'; - const CRS_ADMIN_ROLE= '1'; - const CRS_MEMBER_ROLE= '2'; - const CRS_TUTOR_ROLE= '3'; - const CRS_PASSED_VALUE= '0'; - - const OPERATION_VISIBLE= 'visible'; - const OPERATION_READ= 'read'; - const OPERATION_WRITE= 'write'; - const OPERATION_COPY= 'copy'; - const OPERATION_DELETE= 'delete'; - const OPERATION_EDIT_LEARNING_PROGRESS = 'edit_learning_progress'; - const OPERATION_VIEW_TEST_STATISTICS = 'tst_statistics'; - const OPERATION_READ_LEARNING_PROGRESS = 'read_learning_progress'; - const OPERATION_EDIT_SUBMISSION_GRADES = 'edit_submissions_grades'; - const OPERATION_VIEW_TEST_RESULTS = 'tst_results'; - - public $index; - public $ilias_config; - public $ilias_interface_config; - public $ilias_int_version; - public $global_roles; - public $crs_roles; - public $error; - - public $soap_client; - public $course_modules; - public $user; - public $user_modules; - - public $operations; - public $user_operations; - public $allowed_operations; - public $tree_allowed_operations; - - /** - * constructor - * - * ILIAS connection main class - * @access - * @param string $index ilias installation index - */ - public function __construct($index) - { - // load settings - $this->index = $index; - $this->error = []; - $this->global_roles = [4,5,14]; - $this->loadSettings(); - $this->crs_roles = [ - "autor" => "member", - "tutor" => "tutor", - "dozent" => "admin", - "admin" => "admin", - "root" => "admin" - ]; - $this->user_operations = [self::OPERATION_VISIBLE, self::OPERATION_READ]; - $this->operations = []; - $this->course_modules = []; - $this->user_modules = []; - - // set ILIAS version as integer value - $this->ilias_int_version = $this->getIntVersion($this->ilias_config['version']); - - // init soap client - $this->soap_client = new IliasSoap($this->index, $this->ilias_config['url'].'/webservice/soap/server.php?wsdl', $this->ilias_config['client'], $this->ilias_int_version, $this->ilias_config['admin'], $this->ilias_config['admin_pw']); - $this->soap_client->setCachingStatus($this->ilias_interface_config['cache']); - - // init current user (only if ILIAS installation is active) - if ($this->ilias_config['is_active']) { - $this->user = new IliasUser($this->index, $this->ilias_config['version']); - // create account automatically if it doesn't exist - if (! $this->user->isConnected()) { - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - $this->newUser(); - } else { - NotificationCenter::addObserver($this, "updateUser", "UserDidUpdate"); - } - // create user category if user has ILIAS author permission - if ($GLOBALS['perm']->have_perm($this->ilias_config['author_perm']) && ! $this->ilias_config['category_create_on_add_module'] && ! $this->user->getCategory()) { - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - $this->newUserCategory(); - } - } - } - - /** - * get ILIAS version as int - * - * converts ILIAS version to int value - * @access public - * @return string messages - */ - public static function getIntVersion($version) - { - $version_array = explode('.', $version); - return ((int)$version_array[0]*10000) + ((int)$version_array[1]*100) + ((int)$version_array[2]); - } - - /** - * load ILIAS settings from config table - */ - public function loadSettings() - { - $this->ilias_interface_config = Config::get()->ILIAS_INTERFACE_BASIC_SETTINGS; - - $ilias_configs = Config::get()->ILIAS_INTERFACE_SETTINGS; - $this->ilias_config = $ilias_configs[$this->index]; - } - - /** - * store settings - * - * stores current ILIAS settings to config table. - * @access public - */ - public function storeSettings() - { - $ilias_configs = Config::get()->ILIAS_INTERFACE_SETTINGS; - $ilias_configs[$this->index] = $this->ilias_config; - Config::get()->store('ILIAS_INTERFACE_SETTINGS', $ilias_configs); - } - - - /** - * get ILIAS info - * - * checks ILIAS base settings - * @access public - * @param string $url - * @return array info - */ - public static function getIliasInfo($url) - { - $info = []; - // check if url exists - $check = @get_headers($url . 'login.php'); - if (strpos($check[0], '200') === false) { - return $info; - } else { - $info['url'] = $url; - } - $soap_client = new IliasSoap('new', $url.'/webservice/soap/server.php?wsdl'); - $soap_client->setCachingStatus(false); - if ($client_info = $soap_client->getInstallationInfoXML()) { - $info = array_merge($info, $client_info); - } - return $info; - } - - /** - * get soap methods - * - * returns array of available soap methods - * @access public - * @return array soap method names - */ - public function getSoapMethods() - { - // fetch all available SOAP methods - $soap_methods = []; - if (is_callable([$this->soap_client->soap_client, '__getfunctions'])) { - $soap_methods_raw = $this->soap_client->soap_client->__getfunctions(); - foreach ($soap_methods_raw as $method) { - $method_array = explode(' ', $method); - preg_match_all('/\${1}[^, )]*/', $method, $param_array); - preg_match('/[^(]*/', $method_array[1], $method_name); - $soap_methods[$method_name[0]] = []; - foreach ($param_array[0] as $par) { - $soap_methods[$method_name[0]][] = substr($par, 1); - } - } - } else { - $proxy = $this->soap_client->soap_client->getProxyClassCode(); - preg_match_all('/function{1}[^{]*/', $proxy, $soap_methods_raw); - foreach ($soap_methods_raw[0] as $method) { - $method_array = explode(' ', $method); - preg_match_all('/\${1}[^, )]*/', $method, $param_array); - preg_match('/[^(]*/', $method_array[1], $method_name); - $soap_methods[$method_name[0]] = []; - foreach ($param_array[0] as $par) { - $soap_methods[$method_name[0]][] = substr($par, 1);; - } - } - } - return $soap_methods; - } - - /** - * get connection status - * - * checks connection settings - * @access public - * @return string messages - */ - public function getConnectionSettingsStatus() - { - // check ILIAS version - if (($this->ilias_int_version < 30000)) { - $this->error[] = _('Die ILIAS-Version ist ungültig.'); - return false; - } - - // check if url exists - $check = @get_headers($this->ilias_config['url'] . 'webservice/soap/server.php'); - if (strpos($check[0], '200') === false) { - $this->error[] = sprintf(_('Die URL "%s" ist nicht erreichbar.'), $this->ilias_config['url']); - return false; - } - - // check soap connection - $res = $this->soap_client->loginAdmin(); - if (!$res) { - $this->error[] = sprintf(_('Anmelden mit dem Account "%s" in der %s-Installation ist fehlgeschlagen.'), $this->ilias_config['admin'], $this->ilias_config['name']); - return false; - } - - return true; - } - - /** - * get content status - * - * checks content settings - * @access public - * @return string messages - */ - public function getContentSettingsStatus() - { - if (!$this->ilias_config['root_category']) { - // check category - if (!$this->ilias_config['root_category_name']) { - $this->error[] = _("Die ILIAS-Kategorie für Stud.IP-Inhalte wurde noch nicht festgelegt."); - return false; - } - $category = $this->soap_client->getReferenceByTitle($this->ilias_config['root_category_name'], 'cat'); - if (!$category) { - $this->error[] = sprintf(_("Die Kategorie \"%s\" wurde nicht gefunden."), $this->ilias_config['root_category_name']); - return false; - } - if ($category) { - $this->ilias_config['root_category'] = $category; - - // check user data category - if (! $this->ilias_config['user_data_category']) { - $object_data["title"] = sprintf(_("User-Daten")); - $object_data["description"] = _("Hier befinden sich die persönlichen Ordner der Stud.IP-User."); - $object_data["type"] = "cat"; - $object_data["owner"] = $this->soap_client->lookupUser($this->ilias_config['admin']); - $user_cat = $this->soap_client->addObject($object_data, $this->ilias_config['root_category']); - if ($user_cat != false) { - $this->ilias_config['user_data_category'] = $user_cat; - } else { - $this->error[] = sprintf(_("Die Kategorie \"%s\" konnte nicht angelegt werden."), $object_data["title"]); - return false; - } - } - $this->storeSettings(); - } - } - - return true; - } - - /** - * get permissions status - * - * checks permissions settings - * @access public - * @return string messages - */ - public function getPermissionsSettingsStatus() - { - // check role template - if (!$this->ilias_config['author_role_name']) { - $this->error[] = _("Das Rollen-Template für die persönliche Kategorie wurde noch nicht festgelegt."); - return false; - } - $role_template = $this->soap_client->getObjectByTitle( $this->ilias_config['author_role_name'], "rolt" ); - if ($role_template == false) { - $this->error[] = sprintf(_("Das Rollen-Template mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($this->ilias_config['author_role_name']), htmlReady($this->getName())); - return false; - } - if (is_array($role_template)) - { - $this->ilias_config['author_role'] = $role_template["obj_id"]; - $this->ilias_config['author_role_name'] = $role_template["title"]; - $this->storeSettings(); - } - return true; - } - - /** - * create new user-account - * - * creates new ILIAS user account - * @access public - * @return boolean returns false - */ - public function newUser() - { - if (!$this->user->studip_login) { - return false; - } - $user_data = $this->user->getUserArray(); - $user_data["login"] = $this->ilias_config['user_prefix'].$user_data["login"]; - - $user_exists = $this->soap_client->lookupUser($user_data["login"]); - //automatische Zuordnung von bestehenden Ilias Accounts - //nur wenn ldap Modus benutzt wird und Stud.IP Nutzer passendes ldap plugin hat - if ($user_exists && - ! $this->ilias_config['user_prefix'] && - $this->ilias_config['ldap_enable'] && - ($this->user->auth_plugin != 'standard') && - ($this->user->auth_plugin == $this->ilias_config['ldap_enable'])) { - $this->user->id = $user_exists; - $this->user->login = $user_data["login"]; - $this->user->setConnection($this->user->getUserType(), true); - PageLayout::postSuccess(sprintf(_("Verbindung mit Nutzer ID %s wiederhergestellt."), $this->user->id)); - return true; - } elseif ($user_exists) { - $this->error[] = sprintf(_('Externer Account konnte nicht angelegt werden. Es existiert bereits ein User mit dem Login %s in %s'), $user_data["login"], $this->ilias_config['name']); - return false; - } elseif ($this->ilias_config['no_account_updates']) { - $this->error[] = sprintf(_('Sie haben noch keinen ILIAS-Account. Loggen Sie sich zuerst in %s ein, um ILIAS-Lernobjekte in Stud.IP nutzen zu können.'), "ilias_config['url']."\">".$this->ilias_config['name'].""); - return false; - } elseif (! $this->ilias_config['user_prefix'] && - $this->ilias_config['ldap_enable'] && - ($this->user->auth_plugin != 'standard') && - ($this->user->auth_plugin == $this->ilias_config['ldap_enable'])) { - $user_data['external_account'] = $this->user->studip_login; - $auth_plugin = StudipAuthAbstract::getInstance($this->user->auth_plugin); - if ($auth_plugin instanceof StudipAuthLdap) { - $user_data['auth_mode'] = 'ldap'; - } elseif ($auth_plugin instanceof StudipAuthCAS) { - $user_data['auth_mode'] = 'cas'; - } elseif ($auth_plugin instanceof StudipAuthShib) { - $user_data['auth_mode'] = 'shibboleth'; - } - } - - // set role according to Stud.IP perm - if (User::findCurrent()->perms === 'root') { - $role_id = 2; - } else { - $role_id = 4; - } - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - $user_id = $this->soap_client->addUser($user_data, $role_id); - if ($user_id != false) - { - $this->user->id = $user_id; - $this->user->login = $this->ilias_config['user_prefix'].$this->user->studip_login; - - $this->user->setConnection(IliasUser::USER_TYPE_CREATED); - return true; - } - return false; - } - - /** - * update given user account - * - * updates ILIAS user data - * @access public - * @param $user Stud.IP user object - * @return boolean returns false - */ - public function updateUser($user) - { - if (! is_object($user)) { - return false; - } - $update_user = new IliasUser($this->index, $this->ilias_config['version'], $user->id); - // don't update ldap user - if (! $this->ilias_config['user_prefix'] && - $this->ilias_config['ldap_enable'] && - ($update_user->auth_plugin != 'standard') && - ($update_user->auth_plugin == $this->ilias_config['ldap_enable'])) { - return true; - } elseif ($this->ilias_config['no_account_updates']) { - return true; - } - // if user is manually connected don't update user data - if ($update_user->getUserType() == IliasUser::USER_TYPE_ORIGINAL) { - return true; - } - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - if ($update_user->isConnected() && $update_user->id && $this->soap_client->lookupUser($update_user->login)) { - $user_data = $update_user->getUserArray(); - $user_data["login"] = $this->ilias_config['user_prefix'].$user_data["login"]; - - // set role according to Stud.IP perm - if ($user->perms == "root") { - $role_id = 2; - } else { - $role_id = 4; - } - - $user_id = $this->soap_client->addUser($user_data, $role_id); - if ($user_id != false) { - $update_user->login = $user_data["login"]; - $update_user->setConnection(IliasUser::USER_TYPE_CREATED); - return true; - } - } - return false; - } - - /** - * delete given user account - * - * deletes ILIAS user data - * @access public - * @param $user Stud.IP user object - * @return boolean returns false - */ - public function deleteUser($user) - { - if (! is_object($user)) { - return false; - } - $delete_user = new IliasUser($this->index, $this->ilias_config['version'], $user->id); - // if user is manually connected don't remove user - if ($delete_user->getUserType() == IliasUser::USER_TYPE_ORIGINAL) { - return true; - } - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - if ($delete_user->isConnected() && $delete_user->id && $this->soap_client->lookupUser($delete_user->login)) { - $deleted = $this->soap_client->deleteUser($delete_user->id); - if ($deleted) { - $query = "DELETE FROM auth_extern WHERE studip_user_id = ? AND external_user_system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - (string)$user->id, - (string)$this->index - ]); - return true; - } - } - return false; - } - - /** - * delete given course - * - * deletes ILIAS course data - * @access public - * @param $course Stud.IP course object - * @return boolean returns false - */ - public function deleteCourse($course) - { - if (! is_object($course)) { - return false; - } - - $crs_id = IliasObjectConnections::getConnectionModuleId($course->id, 'crs', $this->index); - if (! $crs_id) { - return false; - } - - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - $deleted = $this->soap_client->deleteObject($crs_id); - if ($deleted) { - IliasObjectConnections::DeleteAllConnections($course->id, $this->index); - return true; - } - return false; - } - - /** - * create new user category - * - * creates new ILIAS user account - * @access public - * @return boolean returns false - */ - public function newUserCategory() - { - if (!$this->user->isConnected()) { - return false; - } - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - - // data for user category in ILIAS - $object_data["title"] = sprintf(_("Eigene Daten von %s (%s)."), $this->user->getName(), $this->user->getId()); - $object_data["description"] = sprintf(_("Hier befinden sich die persönlichen Lernmodule des Benutzers %s."), $this->user->getName()); - $object_data["type"] = "cat"; - $object_data["owner"] = $this->user->getId(); - - // check if category already exists - $cat = $this->soap_client->getReferenceByTitle($object_data["title"]); - if (($cat != false) && $this->soap_client->checkReferenceById($cat) ) { - $this->user->category = $cat; - } else { - // add new user category at main user data category in ILIAS - $this->user->category = $this->soap_client->addObject($object_data, $this->ilias_config['user_data_category']); - } - if ($this->ilias_config['category_to_desktop'] && $this->user->category) { - $this->soap_client->addDesktopItems($this->user->getId(), [$this->user->category]); - } - - // store data - if ($this->user->category != false) { - $this->user->setConnection($this->user->getUserType()); - } else { - $this->error[] = _('ILIAS-User-Kategorie konnte nicht angelegt werden.'); - return false; - } - - // personal user role in ILIAS - $role_data["title"] = "studip_usr" . $this->user->getId() . "_cat" . $this->user->category; - $role_data["description"] = sprintf(_("User-Rolle von %s. Diese Rolle wurde von Stud.IP generiert."), $this->user->getName()); - $role_id = $this->soap_client->getObjectByTitle($role_data["title"], "role"); - if ($role_id == false) { - $role_id = $this->soap_client->addRoleFromTemplate($role_data, $this->user->getCategory(), $this->ilias_config['author_role']); - } - $this->soap_client->addUserRoleEntry($this->user->getId(), $role_id); - - // delete permissions for all global roles (User, Guest, Anonymous) for this category - foreach ($this->global_roles as $key => $role) { - $this->soap_client->revokePermissions($role, $this->user->category); - } - return true; - } - - /** - * get ILIAS user full name - * - * returns full name of given ILIAS user ID - * @access public - * @param $user_id ILIAS user id - * @return string full name - */ - public function getUserFullname($user_id) - { - return $this->soap_client->getUserFullname($user_id); - } - - /** - * get ILIAS path - * - * returns full path for given ILIAS ref ID - * @access public - * @param $ref_id ILIAS reference id - * @return string path - */ - public function getPath($ref_id) - { - return $this->soap_client->getPath($ref_id); - } - - /** - * get structure - * - * returns structure for given ILIAS lm ID - * @access public - * @param $ref_id ILIAS reference id - * @return string path - */ - public function getStructure($ref_id) - { - return $this->soap_client->getStructure($ref_id); - } - - /** - * get supported module types - * - * returns all active module types for current ILIAS installation - * @access public - */ - public static function getsupportedModuleTypes() - { - return [ -// 'cat' => _('Kategorie'), -// 'crs' => _('Kurs'), - 'webr' => _('Weblink'), - 'htlm' => _('HTML-Lernmodul'), - 'sahs' => _('SCORM/AICC-Lernmodul'), - 'lm' => _('ILIAS-Lernmodul'), - 'glo' => _('Glossar'), - 'tst' => _('Test'), - 'svy' => _('Umfrage'), - 'exc' => _('Übung') - ]; - } - - /** - * get active module types - * - * returns all active module types for current ILIAS installation - * @access public - */ - public function getAllowedModuleTypes() - { - return $this->ilias_config['modules']; - } - - /** - * check is module type is allowed - * - * returns true if module type is allowed for current ILIAS installation - * @access public - */ - public function isAllowedModuleType($module_type) - { - return (boolean)$this->ilias_config['modules'][$module_type]; - } - - /** - * get existing ilias indices - * - * loads existing indices of all ilias installations from database - * @access public - */ - public static function getExistingIndices() - { - $query = "SELECT DISTINCT external_user_system_type FROM auth_extern ORDER BY external_user_system_type ASC"; - return DBManager::get()->fetchGrouped($query); - } - - /** - * get user modules - * - * returns content modules from current users private category - * @access public - * @return array list of content modules - */ - public function getUserModules() - { - if (count($this->user_modules)) { - return $this->user_modules; - } - $types = []; - foreach ($this->getAllowedModuleTypes() as $type => $name) { - $types[] = $type; - } - if ($this->user->getCategory() == false) { - return []; - } - $result = $this->soap_client->getTreeChilds($this->user->getCategory(), $types, $this->user->getId()); - $obj_ids = []; - if (is_array($result)) { - foreach($result as $key => $object_data) { - $this->user_modules[$key] = new IliasModule($key, $object_data, $this->index, $this->ilias_int_version); - } - } - return $this->user_modules; - } - - - /** - * get module - * - * returns module instance by ID - * @access public - * @param string $module_id ILIAS ref id - * @return instance of IliasModule - */ - public function getModule($module_id) - { - $object_data = $this->soap_client->getObjectByReference($module_id, $this->user->getId()); - $module = new IliasModule($module_id, $object_data, $this->index, $this->ilias_int_version); - return $module; - } - - /** - * Helper function to fetch children including objects in folders - * The typo in the function name, 'childs', is intentional to reflect the name of the ILIAS-SOAP call. - * - * @access public - * @param string $parent_id - * @return array result - */ - public function getChilds($parent_id) { - $types[] = 'fold'; - if (isset($this->ilias_config['modules']) && $this->ilias_config['modules']) { - foreach ($this->ilias_config['modules'] as $type => $name) { - $types[] = $type; - } - $result = $this->soap_client->getTreeChilds($parent_id, $types); - $user_result = $this->soap_client->getTreeChilds($parent_id, $types, $this->user->getId()); - - if ($result) { - foreach($result as $ref_id => $data) { - if ($data['type'] == 'fold') { - unset($result[$ref_id]); - $result = $result + $this->getChilds($ref_id); - } else { - $result[$ref_id]['accessInfo'] = $user_result[$ref_id]['accessInfo']; - $result[$ref_id]['references'][$ref_id] = $user_result[$ref_id]['references'][$ref_id]; - } - } - } - } - - if (isset($result) && is_array($result)) { - return $result; - } else { - return []; - } - } - - /** - * check connected modules and update connections - * - * checks if there are modules in the course that are not connected to the seminar - * @access public - * @param string $course_id course-id - * @return boolean successful - */ - public function updateCourseConnections($course_id) - { - $this->soap_client->setCachingStatus(false); - // fetch childs - $result = $this->getChilds($course_id); - - if (is_array($result)) { - $check = DBManager::get()->prepare("SELECT 1 FROM object_contentmodules WHERE object_id = ? AND module_id = ? AND system_type = ? AND module_type = ?"); - $found = []; - $added = 0; - $deleted = 0; - foreach($result as $ref_id => $data) { - if (($data['accessInfo'] == 'granted') || ($this->ilias_interface_config['show_offline'] && $data['offline'])) { - $this->course_modules[$ref_id] = new IliasModule($ref_id, $data, $this->index, $this->ilias_int_version); - } - $check->execute([Context::getId(), $ref_id, $this->index, $data["type"]]); - if (!$check->fetch()) { - IliasObjectConnections::setConnection(Context::getId(), $ref_id, $data["type"], $this->index); - $added++; - } - $found[] = $ref_id . '_' . $data["type"]; - } - $to_delete = DBManager::get()->prepare("SELECT module_id,module_type FROM object_contentmodules WHERE module_type <> 'crs' AND object_id = ? AND system_type = ? AND CONCAT_WS('_', module_id,module_type) NOT IN (?)"); - $to_delete->execute([Context::getId(), $this->index, count($found) ? $found : ['']]); - while ($row = $to_delete->fetch(PDO::FETCH_ASSOC)) { - IliasObjectConnections::unsetConnection(Context::getId(), $row["module_id"], $row["module_type"], $this->index); - $deleted++; - } - return true; - } - return false; - } - - /** - * set module connection - * - * sets module connection to course - * @access public - * @param string $studip_course_id studip range id - * @param string $module_id ILIAS ref id - * @param string $module_type type of ILIAS module - * @param string $connection_mode copy or reference - * @param string $write_permission_level write permission for new module requires this perm (autor, tutor, dozent, never) - * @return boolean successful - */ - public function setCourseModuleConnection($studip_course_id, $module_id, $module_type, $connection_mode, $write_permission_level) - { - $object_data = $this->soap_client->getObjectByReference($module_id, $this->user->getId()); - $module = new IliasModule($module_id, $object_data, $this->index, $this->ilias_int_version); - if (!$module->isAllowed('start')) { - return false; - } - if (!$module->isAllowed('copy') && !$module->isAllowed('edit')) { - $this->error[] = _("Keine Berechtigung zum Kopieren des Lernobjekts!"); - return false; - } - - $crs_id = IliasObjectConnections::getConnectionModuleId($studip_course_id, "crs", $this->index); - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - - if (! $crs_id) { - // if no course entry create new course - $crs_id = $this->addCourse($studip_course_id); - } elseif ($crs_id AND ($this->soap_client->getObjectByReference($crs_id) == false)) { - // if course entry is invalid create new course - IliasObjectConnections::unsetConnection($studip_course_id, $crs_id, "crs", $this->index); - $this->error[] = sprintf(_('Der zugeordnete ILIAS-Kurs (ID %s) existiert nicht mehr. Ein neuer Kurs wurde angelegt.'), $crs_id); - $crs_id = $this->addCourse($studip_course_id); - } - - if ($crs_id == false) { - return false; - } - - if ($connection_mode == 'copy') { - $ref_id = $this->soap_client->copyObject($module_id, $crs_id); - } elseif ($connection_mode == 'reference') { - $ref_id = $this->soap_client->addReference($module_id, $crs_id); - } - if (! $ref_id) { - $this->error[] = _("Zuordnungs-Fehler: Lernobjekt konnte nicht angelegt werden."); - return false; - } - // set permissions for course roles - $local_roles = $this->soap_client->getLocalRoles($crs_id); - $member_operations = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ]); - $admin_operations = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ, self::OPERATION_WRITE, self::OPERATION_COPY, self::OPERATION_DELETE]); - $admin_operations_no_delete = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ, self::OPERATION_WRITE, self::OPERATION_COPY]); - $admin_operations_readonly = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ, self::OPERATION_DELETE]); - switch ($module_type) { - case 'tst': - $admin_operations = array_merge($admin_operations, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_STATISTICS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_RESULTS])); - $admin_operations_no_delete = array_merge($admin_operations_no_delete, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_STATISTICS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_RESULTS])); - $admin_operations_readonly = array_merge($admin_operations_readonly, $this->getOperationArray([self::OPERATION_VIEW_TEST_STATISTICS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_RESULTS])); - break; - case 'lm': - case 'sahs': - case 'htlm': - $admin_operations = array_merge($admin_operations, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS])); - $admin_operations_no_delete = array_merge($admin_operations_no_delete, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS])); - $admin_operations_readonly = array_merge($admin_operations_readonly, $this->getOperationArray([self::OPERATION_READ_LEARNING_PROGRESS])); - break; - case 'exc': - $admin_operations = array_merge($admin_operations, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_EDIT_SUBMISSION_GRADES])); - $admin_operations_no_delete = array_merge($admin_operations_no_delete, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_EDIT_SUBMISSION_GRADES])); - $admin_operations_readonly = array_merge($admin_operations_readonly, $this->getOperationArray([self::OPERATION_READ_LEARNING_PROGRESS])); - break; - } - foreach ($local_roles as $key => $role_data) { - // check only if local role is il_crs_member, -tutor or -admin - if (mb_strpos($role_data["title"], "il_crs_") === 0) { - if(mb_strpos($role_data["title"], 'il_crs_member') === 0){ - $operations = ($write_permission_level == "autor") ? $admin_operations_no_delete : $member_operations; - } elseif(mb_strpos($role_data["title"], 'il_crs_tutor') === 0){ - $operations = (($write_permission_level == "tutor") || ($write_permission_level == "autor")) ? $admin_operations : $admin_operations_readonly; - } elseif(mb_strpos($role_data["title"], 'il_crs_admin') === 0){ - $operations = (($write_permission_level == "dozent") || ($write_permission_level == "tutor") || ($write_permission_level == "autor")) ? $admin_operations : $admin_operations_readonly; - } else { - continue; - } - $this->soap_client->revokePermissions($role_data["obj_id"], $ref_id); - $this->soap_client->grantPermissions($operations, $role_data["obj_id"], $ref_id); - } - } - // store object connection - if ($ref_id) { - IliasObjectConnections::setConnection($studip_course_id, $ref_id, $module_type, $this->index); - return true; - } - return false; - } - - - /** - * unset module connection - * - * unsets ILIAS module connection with course - * @access public - * @param string $studip_course_id studip range id - * @param string $module_id ILIAS ref id - * @param string $module_type type of ILIAS module - */ - public function unsetCourseModuleConnection($studip_course_id, $module_id, $module_type) - { - $this->soap_client->setCachingStatus(false); - $this->soap_client->deleteObject($module_id); - IliasObjectConnections::unsetConnection($studip_course_id, $module_id, $module_type, $this->index); - } - - /** - * add course module - * - * adds module instance to list of course modules - * @access public - */ - public function addCourseModule($module_id, $module_data) - { - $object_data = $this->soap_client->getObjectByReference($module_id, $this->user->getId()); - $this->course_modules[$module_id] = new IliasModule($module_id, $object_data, $this->index, $this->ilias_int_version); - $this->course_modules[$module_id]->setConnectionType(true); - } - - /** - * get course modules - * - * returns all added course module instances - * @access public - */ - public function getCourseModules() - { - return $this->course_modules; - } - - /** - * create course - * - * creates new ilias course - * @access public - * @param string $studip_course_id seminar-id - * @return string|false|null - */ - public function addCourse($studip_course_id) - { - $crs_id = IliasObjectConnections::getConnectionModuleId($studip_course_id, "crs", $this->index); - $this->soap_client->setCachingStatus(false); - $this->soap_client->clearCache(); - - if (!$crs_id) { - $seminar = Seminar::getInstance($studip_course_id); - // on error use root category - $ref_id = $this->ilias_config['root_category']; - if ($this->ilias_config['cat_semester'] == 'outer') { - // category for semester above institute - $semester_ref_id = IliasObjectConnections::getConnectionModuleId($seminar->start_semester->getId(), 'cat', $this->index); - if (!$semester_ref_id) { - $object_data['title'] = $seminar->getStartSemesterName(); - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $seminar->getStartSemesterName()); - $object_data['type'] = 'cat'; - $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); - $semester_ref_id = $this->soap_client->addObject($object_data, $ref_id); - if ($semester_ref_id) { - // store institute category - IliasObjectConnections::setConnection($seminar->start_semester->getId(), $semester_ref_id, 'cat', $this->index); - } else { - $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data['title']); - } - } - if ($semester_ref_id) { - $ref_id = $semester_ref_id; - // category for home institute below semester - $home_institute = Institute::find($seminar->getInstitutId()); - if ($home_institute) { - $institute_ref_id = IliasObjectConnections::getConnectionModuleId(md5($seminar->start_semester->getId().$home_institute->getId()), "cat", $this->index); - } - if (!$institute_ref_id) { - $object_data['title'] = $home_institute->name; - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $home_institute->name); - $object_data['type'] = 'cat'; - $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); - $institute_ref_id = $this->soap_client->addObject($object_data, $ref_id); - if ($institute_ref_id) { - // store institute category - IliasObjectConnections::setConnection(md5($seminar->start_semester->getId().$home_institute->getId()), $institute_ref_id, "cat", $this->index); - } - } - if ($institute_ref_id) { - $ref_id = $institute_ref_id; - } else { - $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data['title']); - } - } - } elseif ($this->ilias_config['cat_semester'] === 'inner' || $this->ilias_config['cat_semester'] === 'none') { - // category for home institute - $home_institute = Institute::find($seminar->getInstitutId()); - if ($home_institute) { - $institute_ref_id = IliasObjectConnections::getConnectionModuleId($home_institute->getId(), "cat", $this->index); - } - if (!$institute_ref_id) { - $object_data['title'] = $home_institute->name; - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $home_institute->name); - $object_data['type'] = 'cat'; - $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); - $institute_ref_id = $this->soap_client->addObject($object_data, $ref_id); - if ($institute_ref_id) { - // store institute category - IliasObjectConnections::setConnection($home_institute->getId(), $institute_ref_id, "cat", $this->index); - } else { - $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data["title"]); - } - } - if ($institute_ref_id) { - $ref_id = $institute_ref_id; - if ($this->ilias_config['cat_semester'] === 'inner') { - // category for semester below institute - $institute_semester_ref_id = IliasObjectConnections::getConnectionModuleId(md5($home_institute->getId().$seminar->start_semester->getId()), 'cat', $this->index); - if (!$institute_semester_ref_id) { - $object_data['title'] = $seminar->getStartSemesterName(); - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $seminar->getStartSemesterName()); - $object_data['type'] = 'cat'; - $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); - $institute_semester_ref_id= $this->soap_client->addObject($object_data, $ref_id); - if ($institute_semester_ref_id) { - // store institute category - IliasObjectConnections::setConnection(md5($home_institute->getId().$seminar->start_semester->getId()), $institute_semester_ref_id, 'cat', $this->index); - } - } - if ($institute_semester_ref_id) { - $ref_id = $institute_semester_ref_id; - } else { - $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data["title"]); - } - } - } - } - - // create course - $lang_array = explode('_', Config::get()->DEFAULT_LANGUAGE); - $course_data['language'] = $lang_array[0]; - if ($this->ilias_config['course_semester'] === 'old' || $this->ilias_config['course_semester'] === 'old_bracket') { - $course_data['title'] = sprintf(_('Stud.IP-Veranstaltung "%s"'), $seminar->getName()); - } else { - $course_data['title'] = sprintf(_('%s'), $seminar->getName()); - } - if ($this->ilias_config['course_semester'] === 'old_bracket' || $this->ilias_config['course_semester'] === 'bracket') { - $course_data['title'] .= ' ('.$seminar->getStartSemesterName().')'; - } - if ($this->ilias_config['course_veranstaltungsnummer']) { - $course_data['title'] .= ' '.$seminar->VeranstaltungsNummer; - } - $course_data['description'] = sprintf(_('Dieser Kurs enthält die Lernobjekte der Stud.IP-Veranstaltung "%s".'), $seminar->getName()); - $crs_id = $this->soap_client->addCourse($course_data, $ref_id); - if (!$crs_id) { - $this->error[] = _('ILIAS-Kurs konnte nicht angelegt werden.'); - return false; - } - IliasObjectConnections::setConnection($studip_course_id, $crs_id, 'crs', $this->index); - - // Rollen zuordnen - $this->CheckUserCoursePermissions($crs_id); - return $crs_id; - } - - return null; - } - - /** - * check user - * - * checks if ILIAS user exists, creates new user if not - * @access public - * @return boolean returns user status - */ - public function checkUser() - { - if ($this->user->getId()) { - $user_exists = $this->soap_client->getUser($this->user->getId()); - if (!is_array($user_exists)) { - $admin_user_id = $this->soap_client->lookupUser($this->ilias_config['admin']); - $admin_user_exists = $this->soap_client->getUser($admin_user_id); - if (is_array($admin_user_exists)) { - $this->user->unsetConnection(true); - if ($this->newUser()) { - PageLayout::postSuccess(_("Neue Verknüpfung zu ILIAS-User angelegt.")); - } - } - } else return true; - } - return false; - } - - /** - * check user permissions - * - * checks user permissions for connected course and changes setting if necessary - * @access public - * @param string $ilias_course_id course-id - * @return boolean returns false on error - */ - public function checkUserCoursePermissions($ilias_course_id = "") - { - if (($ilias_course_id == "") || ($this->user->getId() == "")) { - return false; - } - - if ($GLOBALS['user']->perms == 'root') { - return true; - } - - // check if user still exists - $this->checkUser(); - - // get course role folder and local roles - $user_roles = $this->soap_client->getUserRoles($this->user->getId()); - $local_roles = $this->soap_client->getLocalRoles($ilias_course_id); - $active_role = ""; - $proper_role = ""; - $user_crs_role = $this->crs_roles[$GLOBALS["perm"]->get_studip_perm(Context::getId())]; - if (is_array($local_roles)) { - foreach ($local_roles as $key => $role_data) { - // check only if local role is il_crs_member, -tutor or -admin - if (! (mb_strpos($role_data["title"], "_crs_") === false)) { - if ( in_array( $role_data["obj_id"], $user_roles ) ) { - $active_role = $role_data["obj_id"]; - } - if ( mb_strpos( $role_data["title"], $user_crs_role) > 0 ) { - $proper_role = $role_data["obj_id"]; - } - } - } - } - - // is user already course-member? otherwise add member with proper role - $is_member = $this->soap_client->isMember( $this->user->getId(), $ilias_course_id); - if (!$is_member) { - $member_data["usr_id"] = $this->user->getId(); - $member_data["ref_id"] = $ilias_course_id; - $member_data["status"] = self::CRS_NO_NOTIFICATION; - $type = ""; - switch ($user_crs_role) - { - case "admin": - $member_data["role"] = self::CRS_ADMIN_ROLE; - $type = "Admin"; - break; - case "tutor": - $member_data["role"] = self::CRS_TUTOR_ROLE; - $type = "Tutor"; - break; - case "member": - $member_data["role"] = self::CRS_MEMBER_ROLE; - $type = "Member"; - break; - default: - } - $member_data["passed"] = self::CRS_PASSED_VALUE; - if ($type != "") { - $this->soap_client->addMember( $this->user->getId(), $type, $ilias_course_id); - } - } - - // check if user has proper local role - // if not, change it - if ($active_role != $proper_role) { - if ($active_role) { - $this->soap_client->deleteUserRoleEntry( $this->user->getId(), $active_role); - } - - if ($proper_role) { - $this->soap_client->addUserRoleEntry( $this->user->getId(), $proper_role); - } - } - - if (! $this->getUserModuleViewPermission($ilias_course_id)) { - return false; - } - - return true; - } - - /** - * get user permissions for ILIAS module - * - * returns allowed operations for current user and given module - * @access public - * @param string $module_id module-id - * @return boolean returns false on error - */ - public function getUserModuleViewPermission($module_id) - { - $this->allowed_operations = []; - $this->tree_allowed_operations = $this->soap_client->getObjectTreeOperations( - $module_id, - $this->user->getId() - ); - if (! is_array($this->tree_allowed_operations)) { - return false; - } - - $view_permission = false; - if ((in_array($this->operations[self::OPERATION_READ], $this->tree_allowed_operations)) && (in_array($this->operations[self::OPERATION_VISIBLE], $this->tree_allowed_operations))) { - $view_permission = true; - } - return $view_permission; - } - - /** - * get operation - * - * returns id for given operation-string - * @access public - * @param string $operation operation - * @return integer operation-id - */ - public function getOperation($operation) - { - // get operation IDs - if (!count($this->operations)) { - $this->operations = $this->soap_client->getOperations(); - } - - return $this->operations[$operation]; - } - - /** - * get operation-ids - * - * returns an array of operation-ids - * @access public - * @param string $operation operation - * @return array operation-ids - */ - public function getOperationArray($operation) - { - // get operation IDs - if (!count($this->operations)) { - $this->operations = $this->soap_client->getOperations(); - } - - $ops_array = []; - if (is_array($operation)) { - foreach ($operation as $key => $operation_name) { - if (array_key_exists($operation_name, $this->operations)) { - $ops_array[] = $this->operations[$operation_name]; - } - } - } - return $ops_array; - } - - /** - * get name of ILIAS installation - * - * returns name of cms - * @access public - * @return string name - */ - public function getName() - { - return $this->ilias_config['name']; - } - - /** - * get index of ILIAS installation - * - * returns index of ILIAS installation - * @access public - * @return string type - */ - public function getIndex() - { - return $this->index; - } - - /** - * get url of ILIAS installation - * - * returns url of ILIAS installation - * @access public - * @return string path - */ - public function getAbsolutePath() - { - return $this->ilias_config['url']; - } - - /** - * get target file of ILIAS installation - * - * returns target file of ILIAS installation - * @access public - * @return string target file - */ - public function getTargetFile() - { - return $this->ilias_config['url'].'studip_referrer.php'; - } - - /** - * get active-setting - * - * returns true, if ILIAS installation is active - * @access public - * @return boolean active-setting - */ - public function isActive() - { - return $this->ilias_config['is_active']; - } - - /** - * get client-id - * - * returns client-id - * @access public - * @return string client-id - */ - public function getClientId() - { - return $this->ilias_config['client']; - } - - /** - * get user prefix - * - * returns user prefix - * @access public - * @return string user prefix - */ - public function getUserPrefix() - { - return $this->ilias_config['user_prefix']; - } - - /** - * get errors - * - * returns array of error strings. - * @access public - * @return array of error strings - */ - public function getError() - { - return $this->error; - } - - /** - * search ILIAS modules - * - * performs search for ILIAS modules - * @access public - * @return boolean returns false - */ - public function searchModules($search_key) - { - $types = []; - foreach ($this->getAllowedModuleTypes() as $type => $name) { - $types[] = $type; - } - $search_modules = []; - - $result = $this->soap_client->searchObjects($types, $search_key, "and", $this->user->getId()); - if ($result) { - foreach($result as $key => $object_data) { - // set every single reference as part of the result - foreach ($object_data['references'] as $ref_id => $reference) { - $search_modules[$ref_id] = new IliasModule($ref_id, $object_data, $this->index, $this->ilias_int_version); - } - } - } - return $search_modules; - } - - public function deleteConnectedModules($object_id){ - return IliasObjectConnections::DeleteAllConnections($object_id, $this->index); - } - - /** - * @param string $ilias_user_id - * @return IliasUser|void - */ - public function getConnectedUser(string $ilias_user_id) - { - $user_id = DBManager::get()->fetchColumn( - "SELECT studip_user_id FROM auth_extern WHERE external_user_id = ? AND external_user_system_type = ?", - [$ilias_user_id, $this->index] - ); - if ($user_id) { - return new IliasUser($this->index, $this->ilias_config['version'], $user_id); - } - } -} diff --git a/lib/ilias_interface/ConnectedIlias.php b/lib/ilias_interface/ConnectedIlias.php new file mode 100644 index 0000000..5b9c1bf --- /dev/null +++ b/lib/ilias_interface/ConnectedIlias.php @@ -0,0 +1,1370 @@ + +* @access public +* @modulegroup ilias_interface_modules +* @module ConnectedIlias +* @package ILIAS-Interface +*/ +class ConnectedIlias +{ + const CRS_NOTIFICATION= '1'; + const CRS_NO_NOTIFICATION= '2'; + const CRS_ADMIN_ROLE= '1'; + const CRS_MEMBER_ROLE= '2'; + const CRS_TUTOR_ROLE= '3'; + const CRS_PASSED_VALUE= '0'; + + const OPERATION_VISIBLE= 'visible'; + const OPERATION_READ= 'read'; + const OPERATION_WRITE= 'write'; + const OPERATION_COPY= 'copy'; + const OPERATION_DELETE= 'delete'; + const OPERATION_EDIT_LEARNING_PROGRESS = 'edit_learning_progress'; + const OPERATION_VIEW_TEST_STATISTICS = 'tst_statistics'; + const OPERATION_READ_LEARNING_PROGRESS = 'read_learning_progress'; + const OPERATION_EDIT_SUBMISSION_GRADES = 'edit_submissions_grades'; + const OPERATION_VIEW_TEST_RESULTS = 'tst_results'; + + public $index; + public $ilias_config; + public $ilias_interface_config; + public $ilias_int_version; + public $global_roles; + public $crs_roles; + public $error; + + public $soap_client; + public $course_modules; + public $user; + public $user_modules; + + public $operations; + public $user_operations; + public $allowed_operations; + public $tree_allowed_operations; + + /** + * constructor + * + * ILIAS connection main class + * @access + * @param string $index ilias installation index + */ + public function __construct($index) + { + // load settings + $this->index = $index; + $this->error = []; + $this->global_roles = [4,5,14]; + $this->loadSettings(); + $this->crs_roles = [ + "autor" => "member", + "tutor" => "tutor", + "dozent" => "admin", + "admin" => "admin", + "root" => "admin" + ]; + $this->user_operations = [self::OPERATION_VISIBLE, self::OPERATION_READ]; + $this->operations = []; + $this->course_modules = []; + $this->user_modules = []; + + // set ILIAS version as integer value + $this->ilias_int_version = $this->getIntVersion($this->ilias_config['version']); + + // init soap client + $this->soap_client = new IliasSoap($this->index, $this->ilias_config['url'].'/webservice/soap/server.php?wsdl', $this->ilias_config['client'], $this->ilias_int_version, $this->ilias_config['admin'], $this->ilias_config['admin_pw']); + $this->soap_client->setCachingStatus($this->ilias_interface_config['cache']); + + // init current user (only if ILIAS installation is active) + if ($this->ilias_config['is_active']) { + $this->user = new IliasUser($this->index, $this->ilias_config['version']); + // create account automatically if it doesn't exist + if (! $this->user->isConnected()) { + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + $this->newUser(); + } else { + NotificationCenter::addObserver($this, "updateUser", "UserDidUpdate"); + } + // create user category if user has ILIAS author permission + if ($GLOBALS['perm']->have_perm($this->ilias_config['author_perm']) && ! $this->ilias_config['category_create_on_add_module'] && ! $this->user->getCategory()) { + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + $this->newUserCategory(); + } + } + } + + /** + * get ILIAS version as int + * + * converts ILIAS version to int value + * @access public + * @return string messages + */ + public static function getIntVersion($version) + { + $version_array = explode('.', $version); + return ((int)$version_array[0]*10000) + ((int)$version_array[1]*100) + ((int)$version_array[2]); + } + + /** + * load ILIAS settings from config table + */ + public function loadSettings() + { + $this->ilias_interface_config = Config::get()->ILIAS_INTERFACE_BASIC_SETTINGS; + + $ilias_configs = Config::get()->ILIAS_INTERFACE_SETTINGS; + $this->ilias_config = $ilias_configs[$this->index]; + } + + /** + * store settings + * + * stores current ILIAS settings to config table. + * @access public + */ + public function storeSettings() + { + $ilias_configs = Config::get()->ILIAS_INTERFACE_SETTINGS; + $ilias_configs[$this->index] = $this->ilias_config; + Config::get()->store('ILIAS_INTERFACE_SETTINGS', $ilias_configs); + } + + + /** + * get ILIAS info + * + * checks ILIAS base settings + * @access public + * @param string $url + * @return array info + */ + public static function getIliasInfo($url) + { + $info = []; + // check if url exists + $check = @get_headers($url . 'login.php'); + if (strpos($check[0], '200') === false) { + return $info; + } else { + $info['url'] = $url; + } + $soap_client = new IliasSoap('new', $url.'/webservice/soap/server.php?wsdl'); + $soap_client->setCachingStatus(false); + if ($client_info = $soap_client->getInstallationInfoXML()) { + $info = array_merge($info, $client_info); + } + return $info; + } + + /** + * get soap methods + * + * returns array of available soap methods + * @access public + * @return array soap method names + */ + public function getSoapMethods() + { + // fetch all available SOAP methods + $soap_methods = []; + if (is_callable([$this->soap_client->soap_client, '__getfunctions'])) { + $soap_methods_raw = $this->soap_client->soap_client->__getfunctions(); + foreach ($soap_methods_raw as $method) { + $method_array = explode(' ', $method); + preg_match_all('/\${1}[^, )]*/', $method, $param_array); + preg_match('/[^(]*/', $method_array[1], $method_name); + $soap_methods[$method_name[0]] = []; + foreach ($param_array[0] as $par) { + $soap_methods[$method_name[0]][] = substr($par, 1); + } + } + } else { + $proxy = $this->soap_client->soap_client->getProxyClassCode(); + preg_match_all('/function{1}[^{]*/', $proxy, $soap_methods_raw); + foreach ($soap_methods_raw[0] as $method) { + $method_array = explode(' ', $method); + preg_match_all('/\${1}[^, )]*/', $method, $param_array); + preg_match('/[^(]*/', $method_array[1], $method_name); + $soap_methods[$method_name[0]] = []; + foreach ($param_array[0] as $par) { + $soap_methods[$method_name[0]][] = substr($par, 1);; + } + } + } + return $soap_methods; + } + + /** + * get connection status + * + * checks connection settings + * @access public + * @return string messages + */ + public function getConnectionSettingsStatus() + { + // check ILIAS version + if (($this->ilias_int_version < 30000)) { + $this->error[] = _('Die ILIAS-Version ist ungültig.'); + return false; + } + + // check if url exists + $check = @get_headers($this->ilias_config['url'] . 'webservice/soap/server.php'); + if (strpos($check[0], '200') === false) { + $this->error[] = sprintf(_('Die URL "%s" ist nicht erreichbar.'), $this->ilias_config['url']); + return false; + } + + // check soap connection + $res = $this->soap_client->loginAdmin(); + if (!$res) { + $this->error[] = sprintf(_('Anmelden mit dem Account "%s" in der %s-Installation ist fehlgeschlagen.'), $this->ilias_config['admin'], $this->ilias_config['name']); + return false; + } + + return true; + } + + /** + * get content status + * + * checks content settings + * @access public + * @return string messages + */ + public function getContentSettingsStatus() + { + if (!$this->ilias_config['root_category']) { + // check category + if (!$this->ilias_config['root_category_name']) { + $this->error[] = _("Die ILIAS-Kategorie für Stud.IP-Inhalte wurde noch nicht festgelegt."); + return false; + } + $category = $this->soap_client->getReferenceByTitle($this->ilias_config['root_category_name'], 'cat'); + if (!$category) { + $this->error[] = sprintf(_("Die Kategorie \"%s\" wurde nicht gefunden."), $this->ilias_config['root_category_name']); + return false; + } + if ($category) { + $this->ilias_config['root_category'] = $category; + + // check user data category + if (! $this->ilias_config['user_data_category']) { + $object_data["title"] = sprintf(_("User-Daten")); + $object_data["description"] = _("Hier befinden sich die persönlichen Ordner der Stud.IP-User."); + $object_data["type"] = "cat"; + $object_data["owner"] = $this->soap_client->lookupUser($this->ilias_config['admin']); + $user_cat = $this->soap_client->addObject($object_data, $this->ilias_config['root_category']); + if ($user_cat != false) { + $this->ilias_config['user_data_category'] = $user_cat; + } else { + $this->error[] = sprintf(_("Die Kategorie \"%s\" konnte nicht angelegt werden."), $object_data["title"]); + return false; + } + } + $this->storeSettings(); + } + } + + return true; + } + + /** + * get permissions status + * + * checks permissions settings + * @access public + * @return string messages + */ + public function getPermissionsSettingsStatus() + { + // check role template + if (!$this->ilias_config['author_role_name']) { + $this->error[] = _("Das Rollen-Template für die persönliche Kategorie wurde noch nicht festgelegt."); + return false; + } + $role_template = $this->soap_client->getObjectByTitle( $this->ilias_config['author_role_name'], "rolt" ); + if ($role_template == false) { + $this->error[] = sprintf(_("Das Rollen-Template mit dem Namen \"%s\" wurde im System %s nicht gefunden."), htmlReady($this->ilias_config['author_role_name']), htmlReady($this->getName())); + return false; + } + if (is_array($role_template)) + { + $this->ilias_config['author_role'] = $role_template["obj_id"]; + $this->ilias_config['author_role_name'] = $role_template["title"]; + $this->storeSettings(); + } + return true; + } + + /** + * create new user-account + * + * creates new ILIAS user account + * @access public + * @return boolean returns false + */ + public function newUser() + { + if (!$this->user->studip_login) { + return false; + } + $user_data = $this->user->getUserArray(); + $user_data["login"] = $this->ilias_config['user_prefix'].$user_data["login"]; + + $user_exists = $this->soap_client->lookupUser($user_data["login"]); + //automatische Zuordnung von bestehenden Ilias Accounts + //nur wenn ldap Modus benutzt wird und Stud.IP Nutzer passendes ldap plugin hat + if ($user_exists && + ! $this->ilias_config['user_prefix'] && + $this->ilias_config['ldap_enable'] && + ($this->user->auth_plugin != 'standard') && + ($this->user->auth_plugin == $this->ilias_config['ldap_enable'])) { + $this->user->id = $user_exists; + $this->user->login = $user_data["login"]; + $this->user->setConnection($this->user->getUserType(), true); + PageLayout::postSuccess(sprintf(_("Verbindung mit Nutzer ID %s wiederhergestellt."), $this->user->id)); + return true; + } elseif ($user_exists) { + $this->error[] = sprintf(_('Externer Account konnte nicht angelegt werden. Es existiert bereits ein User mit dem Login %s in %s'), $user_data["login"], $this->ilias_config['name']); + return false; + } elseif ($this->ilias_config['no_account_updates']) { + $this->error[] = sprintf(_('Sie haben noch keinen ILIAS-Account. Loggen Sie sich zuerst in %s ein, um ILIAS-Lernobjekte in Stud.IP nutzen zu können.'), "ilias_config['url']."\">".$this->ilias_config['name'].""); + return false; + } elseif (! $this->ilias_config['user_prefix'] && + $this->ilias_config['ldap_enable'] && + ($this->user->auth_plugin != 'standard') && + ($this->user->auth_plugin == $this->ilias_config['ldap_enable'])) { + $user_data['external_account'] = $this->user->studip_login; + $auth_plugin = StudipAuthAbstract::getInstance($this->user->auth_plugin); + if ($auth_plugin instanceof StudipAuthLdap) { + $user_data['auth_mode'] = 'ldap'; + } elseif ($auth_plugin instanceof StudipAuthCAS) { + $user_data['auth_mode'] = 'cas'; + } elseif ($auth_plugin instanceof StudipAuthShib) { + $user_data['auth_mode'] = 'shibboleth'; + } + } + + // set role according to Stud.IP perm + if (User::findCurrent()->perms === 'root') { + $role_id = 2; + } else { + $role_id = 4; + } + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + $user_id = $this->soap_client->addUser($user_data, $role_id); + if ($user_id != false) + { + $this->user->id = $user_id; + $this->user->login = $this->ilias_config['user_prefix'].$this->user->studip_login; + + $this->user->setConnection(IliasUser::USER_TYPE_CREATED); + return true; + } + return false; + } + + /** + * update given user account + * + * updates ILIAS user data + * @access public + * @param $user Stud.IP user object + * @return boolean returns false + */ + public function updateUser($user) + { + if (! is_object($user)) { + return false; + } + $update_user = new IliasUser($this->index, $this->ilias_config['version'], $user->id); + // don't update ldap user + if (! $this->ilias_config['user_prefix'] && + $this->ilias_config['ldap_enable'] && + ($update_user->auth_plugin != 'standard') && + ($update_user->auth_plugin == $this->ilias_config['ldap_enable'])) { + return true; + } elseif ($this->ilias_config['no_account_updates']) { + return true; + } + // if user is manually connected don't update user data + if ($update_user->getUserType() == IliasUser::USER_TYPE_ORIGINAL) { + return true; + } + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + if ($update_user->isConnected() && $update_user->id && $this->soap_client->lookupUser($update_user->login)) { + $user_data = $update_user->getUserArray(); + $user_data["login"] = $this->ilias_config['user_prefix'].$user_data["login"]; + + // set role according to Stud.IP perm + if ($user->perms == "root") { + $role_id = 2; + } else { + $role_id = 4; + } + + $user_id = $this->soap_client->addUser($user_data, $role_id); + if ($user_id != false) { + $update_user->login = $user_data["login"]; + $update_user->setConnection(IliasUser::USER_TYPE_CREATED); + return true; + } + } + return false; + } + + /** + * delete given user account + * + * deletes ILIAS user data + * @access public + * @param $user Stud.IP user object + * @return boolean returns false + */ + public function deleteUser($user) + { + if (! is_object($user)) { + return false; + } + $delete_user = new IliasUser($this->index, $this->ilias_config['version'], $user->id); + // if user is manually connected don't remove user + if ($delete_user->getUserType() == IliasUser::USER_TYPE_ORIGINAL) { + return true; + } + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + if ($delete_user->isConnected() && $delete_user->id && $this->soap_client->lookupUser($delete_user->login)) { + $deleted = $this->soap_client->deleteUser($delete_user->id); + if ($deleted) { + $query = "DELETE FROM auth_extern WHERE studip_user_id = ? AND external_user_system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + (string)$user->id, + (string)$this->index + ]); + return true; + } + } + return false; + } + + /** + * delete given course + * + * deletes ILIAS course data + * @access public + * @param $course Stud.IP course object + * @return boolean returns false + */ + public function deleteCourse($course) + { + if (! is_object($course)) { + return false; + } + + $crs_id = IliasObjectConnections::getConnectionModuleId($course->id, 'crs', $this->index); + if (! $crs_id) { + return false; + } + + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + $deleted = $this->soap_client->deleteObject($crs_id); + if ($deleted) { + IliasObjectConnections::DeleteAllConnections($course->id, $this->index); + return true; + } + return false; + } + + /** + * create new user category + * + * creates new ILIAS user account + * @access public + * @return boolean returns false + */ + public function newUserCategory() + { + if (!$this->user->isConnected()) { + return false; + } + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + + // data for user category in ILIAS + $object_data["title"] = sprintf(_("Eigene Daten von %s (%s)."), $this->user->getName(), $this->user->getId()); + $object_data["description"] = sprintf(_("Hier befinden sich die persönlichen Lernmodule des Benutzers %s."), $this->user->getName()); + $object_data["type"] = "cat"; + $object_data["owner"] = $this->user->getId(); + + // check if category already exists + $cat = $this->soap_client->getReferenceByTitle($object_data["title"]); + if (($cat != false) && $this->soap_client->checkReferenceById($cat) ) { + $this->user->category = $cat; + } else { + // add new user category at main user data category in ILIAS + $this->user->category = $this->soap_client->addObject($object_data, $this->ilias_config['user_data_category']); + } + if ($this->ilias_config['category_to_desktop'] && $this->user->category) { + $this->soap_client->addDesktopItems($this->user->getId(), [$this->user->category]); + } + + // store data + if ($this->user->category != false) { + $this->user->setConnection($this->user->getUserType()); + } else { + $this->error[] = _('ILIAS-User-Kategorie konnte nicht angelegt werden.'); + return false; + } + + // personal user role in ILIAS + $role_data["title"] = "studip_usr" . $this->user->getId() . "_cat" . $this->user->category; + $role_data["description"] = sprintf(_("User-Rolle von %s. Diese Rolle wurde von Stud.IP generiert."), $this->user->getName()); + $role_id = $this->soap_client->getObjectByTitle($role_data["title"], "role"); + if ($role_id == false) { + $role_id = $this->soap_client->addRoleFromTemplate($role_data, $this->user->getCategory(), $this->ilias_config['author_role']); + } + $this->soap_client->addUserRoleEntry($this->user->getId(), $role_id); + + // delete permissions for all global roles (User, Guest, Anonymous) for this category + foreach ($this->global_roles as $key => $role) { + $this->soap_client->revokePermissions($role, $this->user->category); + } + return true; + } + + /** + * get ILIAS user full name + * + * returns full name of given ILIAS user ID + * @access public + * @param $user_id ILIAS user id + * @return string full name + */ + public function getUserFullname($user_id) + { + return $this->soap_client->getUserFullname($user_id); + } + + /** + * get ILIAS path + * + * returns full path for given ILIAS ref ID + * @access public + * @param $ref_id ILIAS reference id + * @return string path + */ + public function getPath($ref_id) + { + return $this->soap_client->getPath($ref_id); + } + + /** + * get structure + * + * returns structure for given ILIAS lm ID + * @access public + * @param $ref_id ILIAS reference id + * @return string path + */ + public function getStructure($ref_id) + { + return $this->soap_client->getStructure($ref_id); + } + + /** + * get supported module types + * + * returns all active module types for current ILIAS installation + * @access public + */ + public static function getsupportedModuleTypes() + { + return [ +// 'cat' => _('Kategorie'), +// 'crs' => _('Kurs'), + 'webr' => _('Weblink'), + 'htlm' => _('HTML-Lernmodul'), + 'sahs' => _('SCORM/AICC-Lernmodul'), + 'lm' => _('ILIAS-Lernmodul'), + 'glo' => _('Glossar'), + 'tst' => _('Test'), + 'svy' => _('Umfrage'), + 'exc' => _('Übung') + ]; + } + + /** + * get active module types + * + * returns all active module types for current ILIAS installation + * @access public + */ + public function getAllowedModuleTypes() + { + return $this->ilias_config['modules']; + } + + /** + * check is module type is allowed + * + * returns true if module type is allowed for current ILIAS installation + * @access public + */ + public function isAllowedModuleType($module_type) + { + return (boolean)$this->ilias_config['modules'][$module_type]; + } + + /** + * get existing ilias indices + * + * loads existing indices of all ilias installations from database + * @access public + */ + public static function getExistingIndices() + { + $query = "SELECT DISTINCT external_user_system_type FROM auth_extern ORDER BY external_user_system_type ASC"; + return DBManager::get()->fetchGrouped($query); + } + + /** + * get user modules + * + * returns content modules from current users private category + * @access public + * @return array list of content modules + */ + public function getUserModules() + { + if (count($this->user_modules)) { + return $this->user_modules; + } + $types = []; + foreach ($this->getAllowedModuleTypes() as $type => $name) { + $types[] = $type; + } + if ($this->user->getCategory() == false) { + return []; + } + $result = $this->soap_client->getTreeChilds($this->user->getCategory(), $types, $this->user->getId()); + $obj_ids = []; + if (is_array($result)) { + foreach($result as $key => $object_data) { + $this->user_modules[$key] = new IliasModule($key, $object_data, $this->index, $this->ilias_int_version); + } + } + return $this->user_modules; + } + + + /** + * get module + * + * returns module instance by ID + * @access public + * @param string $module_id ILIAS ref id + * @return instance of IliasModule + */ + public function getModule($module_id) + { + $object_data = $this->soap_client->getObjectByReference($module_id, $this->user->getId()); + $module = new IliasModule($module_id, $object_data, $this->index, $this->ilias_int_version); + return $module; + } + + /** + * Helper function to fetch children including objects in folders + * The typo in the function name, 'childs', is intentional to reflect the name of the ILIAS-SOAP call. + * + * @access public + * @param string $parent_id + * @return array result + */ + public function getChilds($parent_id) { + $types[] = 'fold'; + if (isset($this->ilias_config['modules']) && $this->ilias_config['modules']) { + foreach ($this->ilias_config['modules'] as $type => $name) { + $types[] = $type; + } + $result = $this->soap_client->getTreeChilds($parent_id, $types); + $user_result = $this->soap_client->getTreeChilds($parent_id, $types, $this->user->getId()); + + if ($result) { + foreach($result as $ref_id => $data) { + if ($data['type'] == 'fold') { + unset($result[$ref_id]); + $result = $result + $this->getChilds($ref_id); + } else { + $result[$ref_id]['accessInfo'] = $user_result[$ref_id]['accessInfo']; + $result[$ref_id]['references'][$ref_id] = $user_result[$ref_id]['references'][$ref_id]; + } + } + } + } + + if (isset($result) && is_array($result)) { + return $result; + } else { + return []; + } + } + + /** + * check connected modules and update connections + * + * checks if there are modules in the course that are not connected to the seminar + * @access public + * @param string $course_id course-id + * @return boolean successful + */ + public function updateCourseConnections($course_id) + { + $this->soap_client->setCachingStatus(false); + // fetch childs + $result = $this->getChilds($course_id); + + if (is_array($result)) { + $check = DBManager::get()->prepare("SELECT 1 FROM object_contentmodules WHERE object_id = ? AND module_id = ? AND system_type = ? AND module_type = ?"); + $found = []; + $added = 0; + $deleted = 0; + foreach($result as $ref_id => $data) { + if (($data['accessInfo'] == 'granted') || ($this->ilias_interface_config['show_offline'] && $data['offline'])) { + $this->course_modules[$ref_id] = new IliasModule($ref_id, $data, $this->index, $this->ilias_int_version); + } + $check->execute([Context::getId(), $ref_id, $this->index, $data["type"]]); + if (!$check->fetch()) { + IliasObjectConnections::setConnection(Context::getId(), $ref_id, $data["type"], $this->index); + $added++; + } + $found[] = $ref_id . '_' . $data["type"]; + } + $to_delete = DBManager::get()->prepare("SELECT module_id,module_type FROM object_contentmodules WHERE module_type <> 'crs' AND object_id = ? AND system_type = ? AND CONCAT_WS('_', module_id,module_type) NOT IN (?)"); + $to_delete->execute([Context::getId(), $this->index, count($found) ? $found : ['']]); + while ($row = $to_delete->fetch(PDO::FETCH_ASSOC)) { + IliasObjectConnections::unsetConnection(Context::getId(), $row["module_id"], $row["module_type"], $this->index); + $deleted++; + } + return true; + } + return false; + } + + /** + * set module connection + * + * sets module connection to course + * @access public + * @param string $studip_course_id studip range id + * @param string $module_id ILIAS ref id + * @param string $module_type type of ILIAS module + * @param string $connection_mode copy or reference + * @param string $write_permission_level write permission for new module requires this perm (autor, tutor, dozent, never) + * @return boolean successful + */ + public function setCourseModuleConnection($studip_course_id, $module_id, $module_type, $connection_mode, $write_permission_level) + { + $object_data = $this->soap_client->getObjectByReference($module_id, $this->user->getId()); + $module = new IliasModule($module_id, $object_data, $this->index, $this->ilias_int_version); + if (!$module->isAllowed('start')) { + return false; + } + if (!$module->isAllowed('copy') && !$module->isAllowed('edit')) { + $this->error[] = _("Keine Berechtigung zum Kopieren des Lernobjekts!"); + return false; + } + + $crs_id = IliasObjectConnections::getConnectionModuleId($studip_course_id, "crs", $this->index); + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + + if (! $crs_id) { + // if no course entry create new course + $crs_id = $this->addCourse($studip_course_id); + } elseif ($crs_id AND ($this->soap_client->getObjectByReference($crs_id) == false)) { + // if course entry is invalid create new course + IliasObjectConnections::unsetConnection($studip_course_id, $crs_id, "crs", $this->index); + $this->error[] = sprintf(_('Der zugeordnete ILIAS-Kurs (ID %s) existiert nicht mehr. Ein neuer Kurs wurde angelegt.'), $crs_id); + $crs_id = $this->addCourse($studip_course_id); + } + + if ($crs_id == false) { + return false; + } + + if ($connection_mode == 'copy') { + $ref_id = $this->soap_client->copyObject($module_id, $crs_id); + } elseif ($connection_mode == 'reference') { + $ref_id = $this->soap_client->addReference($module_id, $crs_id); + } + if (! $ref_id) { + $this->error[] = _("Zuordnungs-Fehler: Lernobjekt konnte nicht angelegt werden."); + return false; + } + // set permissions for course roles + $local_roles = $this->soap_client->getLocalRoles($crs_id); + $member_operations = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ]); + $admin_operations = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ, self::OPERATION_WRITE, self::OPERATION_COPY, self::OPERATION_DELETE]); + $admin_operations_no_delete = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ, self::OPERATION_WRITE, self::OPERATION_COPY]); + $admin_operations_readonly = $this->getOperationArray([self::OPERATION_VISIBLE, self::OPERATION_READ, self::OPERATION_DELETE]); + switch ($module_type) { + case 'tst': + $admin_operations = array_merge($admin_operations, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_STATISTICS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_RESULTS])); + $admin_operations_no_delete = array_merge($admin_operations_no_delete, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_STATISTICS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_RESULTS])); + $admin_operations_readonly = array_merge($admin_operations_readonly, $this->getOperationArray([self::OPERATION_VIEW_TEST_STATISTICS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_VIEW_TEST_RESULTS])); + break; + case 'lm': + case 'sahs': + case 'htlm': + $admin_operations = array_merge($admin_operations, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS])); + $admin_operations_no_delete = array_merge($admin_operations_no_delete, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS])); + $admin_operations_readonly = array_merge($admin_operations_readonly, $this->getOperationArray([self::OPERATION_READ_LEARNING_PROGRESS])); + break; + case 'exc': + $admin_operations = array_merge($admin_operations, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_EDIT_SUBMISSION_GRADES])); + $admin_operations_no_delete = array_merge($admin_operations_no_delete, $this->getOperationArray([self::OPERATION_EDIT_LEARNING_PROGRESS, self::OPERATION_READ_LEARNING_PROGRESS, self::OPERATION_EDIT_SUBMISSION_GRADES])); + $admin_operations_readonly = array_merge($admin_operations_readonly, $this->getOperationArray([self::OPERATION_READ_LEARNING_PROGRESS])); + break; + } + foreach ($local_roles as $key => $role_data) { + // check only if local role is il_crs_member, -tutor or -admin + if (mb_strpos($role_data["title"], "il_crs_") === 0) { + if(mb_strpos($role_data["title"], 'il_crs_member') === 0){ + $operations = ($write_permission_level == "autor") ? $admin_operations_no_delete : $member_operations; + } elseif(mb_strpos($role_data["title"], 'il_crs_tutor') === 0){ + $operations = (($write_permission_level == "tutor") || ($write_permission_level == "autor")) ? $admin_operations : $admin_operations_readonly; + } elseif(mb_strpos($role_data["title"], 'il_crs_admin') === 0){ + $operations = (($write_permission_level == "dozent") || ($write_permission_level == "tutor") || ($write_permission_level == "autor")) ? $admin_operations : $admin_operations_readonly; + } else { + continue; + } + $this->soap_client->revokePermissions($role_data["obj_id"], $ref_id); + $this->soap_client->grantPermissions($operations, $role_data["obj_id"], $ref_id); + } + } + // store object connection + if ($ref_id) { + IliasObjectConnections::setConnection($studip_course_id, $ref_id, $module_type, $this->index); + return true; + } + return false; + } + + + /** + * unset module connection + * + * unsets ILIAS module connection with course + * @access public + * @param string $studip_course_id studip range id + * @param string $module_id ILIAS ref id + * @param string $module_type type of ILIAS module + */ + public function unsetCourseModuleConnection($studip_course_id, $module_id, $module_type) + { + $this->soap_client->setCachingStatus(false); + $this->soap_client->deleteObject($module_id); + IliasObjectConnections::unsetConnection($studip_course_id, $module_id, $module_type, $this->index); + } + + /** + * add course module + * + * adds module instance to list of course modules + * @access public + */ + public function addCourseModule($module_id, $module_data) + { + $object_data = $this->soap_client->getObjectByReference($module_id, $this->user->getId()); + $this->course_modules[$module_id] = new IliasModule($module_id, $object_data, $this->index, $this->ilias_int_version); + $this->course_modules[$module_id]->setConnectionType(true); + } + + /** + * get course modules + * + * returns all added course module instances + * @access public + */ + public function getCourseModules() + { + return $this->course_modules; + } + + /** + * create course + * + * creates new ilias course + * @access public + * @param string $studip_course_id seminar-id + * @return string|false|null + */ + public function addCourse($studip_course_id) + { + $crs_id = IliasObjectConnections::getConnectionModuleId($studip_course_id, "crs", $this->index); + $this->soap_client->setCachingStatus(false); + $this->soap_client->clearCache(); + + if (!$crs_id) { + $seminar = Seminar::getInstance($studip_course_id); + // on error use root category + $ref_id = $this->ilias_config['root_category']; + if ($this->ilias_config['cat_semester'] == 'outer') { + // category for semester above institute + $semester_ref_id = IliasObjectConnections::getConnectionModuleId($seminar->start_semester->getId(), 'cat', $this->index); + if (!$semester_ref_id) { + $object_data['title'] = $seminar->getStartSemesterName(); + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $seminar->getStartSemesterName()); + $object_data['type'] = 'cat'; + $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); + $semester_ref_id = $this->soap_client->addObject($object_data, $ref_id); + if ($semester_ref_id) { + // store institute category + IliasObjectConnections::setConnection($seminar->start_semester->getId(), $semester_ref_id, 'cat', $this->index); + } else { + $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data['title']); + } + } + if ($semester_ref_id) { + $ref_id = $semester_ref_id; + // category for home institute below semester + $home_institute = Institute::find($seminar->getInstitutId()); + if ($home_institute) { + $institute_ref_id = IliasObjectConnections::getConnectionModuleId(md5($seminar->start_semester->getId().$home_institute->getId()), "cat", $this->index); + } + if (!$institute_ref_id) { + $object_data['title'] = $home_institute->name; + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $home_institute->name); + $object_data['type'] = 'cat'; + $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); + $institute_ref_id = $this->soap_client->addObject($object_data, $ref_id); + if ($institute_ref_id) { + // store institute category + IliasObjectConnections::setConnection(md5($seminar->start_semester->getId().$home_institute->getId()), $institute_ref_id, "cat", $this->index); + } + } + if ($institute_ref_id) { + $ref_id = $institute_ref_id; + } else { + $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data['title']); + } + } + } elseif ($this->ilias_config['cat_semester'] === 'inner' || $this->ilias_config['cat_semester'] === 'none') { + // category for home institute + $home_institute = Institute::find($seminar->getInstitutId()); + if ($home_institute) { + $institute_ref_id = IliasObjectConnections::getConnectionModuleId($home_institute->getId(), "cat", $this->index); + } + if (!$institute_ref_id) { + $object_data['title'] = $home_institute->name; + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $home_institute->name); + $object_data['type'] = 'cat'; + $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); + $institute_ref_id = $this->soap_client->addObject($object_data, $ref_id); + if ($institute_ref_id) { + // store institute category + IliasObjectConnections::setConnection($home_institute->getId(), $institute_ref_id, "cat", $this->index); + } else { + $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data["title"]); + } + } + if ($institute_ref_id) { + $ref_id = $institute_ref_id; + if ($this->ilias_config['cat_semester'] === 'inner') { + // category for semester below institute + $institute_semester_ref_id = IliasObjectConnections::getConnectionModuleId(md5($home_institute->getId().$seminar->start_semester->getId()), 'cat', $this->index); + if (!$institute_semester_ref_id) { + $object_data['title'] = $seminar->getStartSemesterName(); + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $seminar->getStartSemesterName()); + $object_data['type'] = 'cat'; + $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); + $institute_semester_ref_id= $this->soap_client->addObject($object_data, $ref_id); + if ($institute_semester_ref_id) { + // store institute category + IliasObjectConnections::setConnection(md5($home_institute->getId().$seminar->start_semester->getId()), $institute_semester_ref_id, 'cat', $this->index); + } + } + if ($institute_semester_ref_id) { + $ref_id = $institute_semester_ref_id; + } else { + $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data["title"]); + } + } + } + } + + // create course + $lang_array = explode('_', Config::get()->DEFAULT_LANGUAGE); + $course_data['language'] = $lang_array[0]; + if ($this->ilias_config['course_semester'] === 'old' || $this->ilias_config['course_semester'] === 'old_bracket') { + $course_data['title'] = sprintf(_('Stud.IP-Veranstaltung "%s"'), $seminar->getName()); + } else { + $course_data['title'] = sprintf(_('%s'), $seminar->getName()); + } + if ($this->ilias_config['course_semester'] === 'old_bracket' || $this->ilias_config['course_semester'] === 'bracket') { + $course_data['title'] .= ' ('.$seminar->getStartSemesterName().')'; + } + if ($this->ilias_config['course_veranstaltungsnummer']) { + $course_data['title'] .= ' '.$seminar->VeranstaltungsNummer; + } + $course_data['description'] = sprintf(_('Dieser Kurs enthält die Lernobjekte der Stud.IP-Veranstaltung "%s".'), $seminar->getName()); + $crs_id = $this->soap_client->addCourse($course_data, $ref_id); + if (!$crs_id) { + $this->error[] = _('ILIAS-Kurs konnte nicht angelegt werden.'); + return false; + } + IliasObjectConnections::setConnection($studip_course_id, $crs_id, 'crs', $this->index); + + // Rollen zuordnen + $this->CheckUserCoursePermissions($crs_id); + return $crs_id; + } + + return null; + } + + /** + * check user + * + * checks if ILIAS user exists, creates new user if not + * @access public + * @return boolean returns user status + */ + public function checkUser() + { + if ($this->user->getId()) { + $user_exists = $this->soap_client->getUser($this->user->getId()); + if (!is_array($user_exists)) { + $admin_user_id = $this->soap_client->lookupUser($this->ilias_config['admin']); + $admin_user_exists = $this->soap_client->getUser($admin_user_id); + if (is_array($admin_user_exists)) { + $this->user->unsetConnection(true); + if ($this->newUser()) { + PageLayout::postSuccess(_("Neue Verknüpfung zu ILIAS-User angelegt.")); + } + } + } else return true; + } + return false; + } + + /** + * check user permissions + * + * checks user permissions for connected course and changes setting if necessary + * @access public + * @param string $ilias_course_id course-id + * @return boolean returns false on error + */ + public function checkUserCoursePermissions($ilias_course_id = "") + { + if (($ilias_course_id == "") || ($this->user->getId() == "")) { + return false; + } + + if ($GLOBALS['user']->perms == 'root') { + return true; + } + + // check if user still exists + $this->checkUser(); + + // get course role folder and local roles + $user_roles = $this->soap_client->getUserRoles($this->user->getId()); + $local_roles = $this->soap_client->getLocalRoles($ilias_course_id); + $active_role = ""; + $proper_role = ""; + $user_crs_role = $this->crs_roles[$GLOBALS["perm"]->get_studip_perm(Context::getId())]; + if (is_array($local_roles)) { + foreach ($local_roles as $key => $role_data) { + // check only if local role is il_crs_member, -tutor or -admin + if (! (mb_strpos($role_data["title"], "_crs_") === false)) { + if ( in_array( $role_data["obj_id"], $user_roles ) ) { + $active_role = $role_data["obj_id"]; + } + if ( mb_strpos( $role_data["title"], $user_crs_role) > 0 ) { + $proper_role = $role_data["obj_id"]; + } + } + } + } + + // is user already course-member? otherwise add member with proper role + $is_member = $this->soap_client->isMember( $this->user->getId(), $ilias_course_id); + if (!$is_member) { + $member_data["usr_id"] = $this->user->getId(); + $member_data["ref_id"] = $ilias_course_id; + $member_data["status"] = self::CRS_NO_NOTIFICATION; + $type = ""; + switch ($user_crs_role) + { + case "admin": + $member_data["role"] = self::CRS_ADMIN_ROLE; + $type = "Admin"; + break; + case "tutor": + $member_data["role"] = self::CRS_TUTOR_ROLE; + $type = "Tutor"; + break; + case "member": + $member_data["role"] = self::CRS_MEMBER_ROLE; + $type = "Member"; + break; + default: + } + $member_data["passed"] = self::CRS_PASSED_VALUE; + if ($type != "") { + $this->soap_client->addMember( $this->user->getId(), $type, $ilias_course_id); + } + } + + // check if user has proper local role + // if not, change it + if ($active_role != $proper_role) { + if ($active_role) { + $this->soap_client->deleteUserRoleEntry( $this->user->getId(), $active_role); + } + + if ($proper_role) { + $this->soap_client->addUserRoleEntry( $this->user->getId(), $proper_role); + } + } + + if (! $this->getUserModuleViewPermission($ilias_course_id)) { + return false; + } + + return true; + } + + /** + * get user permissions for ILIAS module + * + * returns allowed operations for current user and given module + * @access public + * @param string $module_id module-id + * @return boolean returns false on error + */ + public function getUserModuleViewPermission($module_id) + { + $this->allowed_operations = []; + $this->tree_allowed_operations = $this->soap_client->getObjectTreeOperations( + $module_id, + $this->user->getId() + ); + if (! is_array($this->tree_allowed_operations)) { + return false; + } + + $view_permission = false; + if ((in_array($this->operations[self::OPERATION_READ], $this->tree_allowed_operations)) && (in_array($this->operations[self::OPERATION_VISIBLE], $this->tree_allowed_operations))) { + $view_permission = true; + } + return $view_permission; + } + + /** + * get operation + * + * returns id for given operation-string + * @access public + * @param string $operation operation + * @return integer operation-id + */ + public function getOperation($operation) + { + // get operation IDs + if (!count($this->operations)) { + $this->operations = $this->soap_client->getOperations(); + } + + return $this->operations[$operation]; + } + + /** + * get operation-ids + * + * returns an array of operation-ids + * @access public + * @param string $operation operation + * @return array operation-ids + */ + public function getOperationArray($operation) + { + // get operation IDs + if (!count($this->operations)) { + $this->operations = $this->soap_client->getOperations(); + } + + $ops_array = []; + if (is_array($operation)) { + foreach ($operation as $key => $operation_name) { + if (array_key_exists($operation_name, $this->operations)) { + $ops_array[] = $this->operations[$operation_name]; + } + } + } + return $ops_array; + } + + /** + * get name of ILIAS installation + * + * returns name of cms + * @access public + * @return string name + */ + public function getName() + { + return $this->ilias_config['name']; + } + + /** + * get index of ILIAS installation + * + * returns index of ILIAS installation + * @access public + * @return string type + */ + public function getIndex() + { + return $this->index; + } + + /** + * get url of ILIAS installation + * + * returns url of ILIAS installation + * @access public + * @return string path + */ + public function getAbsolutePath() + { + return $this->ilias_config['url']; + } + + /** + * get target file of ILIAS installation + * + * returns target file of ILIAS installation + * @access public + * @return string target file + */ + public function getTargetFile() + { + return $this->ilias_config['url'].'studip_referrer.php'; + } + + /** + * get active-setting + * + * returns true, if ILIAS installation is active + * @access public + * @return boolean active-setting + */ + public function isActive() + { + return $this->ilias_config['is_active']; + } + + /** + * get client-id + * + * returns client-id + * @access public + * @return string client-id + */ + public function getClientId() + { + return $this->ilias_config['client']; + } + + /** + * get user prefix + * + * returns user prefix + * @access public + * @return string user prefix + */ + public function getUserPrefix() + { + return $this->ilias_config['user_prefix']; + } + + /** + * get errors + * + * returns array of error strings. + * @access public + * @return array of error strings + */ + public function getError() + { + return $this->error; + } + + /** + * search ILIAS modules + * + * performs search for ILIAS modules + * @access public + * @return boolean returns false + */ + public function searchModules($search_key) + { + $types = []; + foreach ($this->getAllowedModuleTypes() as $type => $name) { + $types[] = $type; + } + $search_modules = []; + + $result = $this->soap_client->searchObjects($types, $search_key, "and", $this->user->getId()); + if ($result) { + foreach($result as $key => $object_data) { + // set every single reference as part of the result + foreach ($object_data['references'] as $ref_id => $reference) { + $search_modules[$ref_id] = new IliasModule($ref_id, $object_data, $this->index, $this->ilias_int_version); + } + } + } + return $search_modules; + } + + public function deleteConnectedModules($object_id){ + return IliasObjectConnections::DeleteAllConnections($object_id, $this->index); + } + + /** + * @param string $ilias_user_id + * @return IliasUser|void + */ + public function getConnectedUser(string $ilias_user_id) + { + $user_id = DBManager::get()->fetchColumn( + "SELECT studip_user_id FROM auth_extern WHERE external_user_id = ? AND external_user_system_type = ?", + [$ilias_user_id, $this->index] + ); + if ($user_id) { + return new IliasUser($this->index, $this->ilias_config['version'], $user_id); + } + } +} diff --git a/lib/ilias_interface/IliasModule.class.php b/lib/ilias_interface/IliasModule.class.php deleted file mode 100644 index f9bf958..0000000 --- a/lib/ilias_interface/IliasModule.class.php +++ /dev/null @@ -1,371 +0,0 @@ - -* @access public -* @modulegroup ilias_interface_modules -* @module IliasModule -* @package ILIAS-Interface -*/ - -class IliasModule -{ - public $id; - public $title; - public $description; - public $module_type; - public $module_type_name; - public $author; - public $make_date; - public $change_date; - public $path; - public $ilias_index; - public $allowed_operations; - public $is_offline; - public $is_connected; - public $owner; - public $author_studip; - public $siblings_count; - public $icon_file; - - /** - * constructor - * - * init class - * @access public - * @param string $module_id module-id - * @param string $module_type module-type - * @param string $ilias_index ilias installation index - */ - function __construct($module_id, $module_data, $ilias_index, $ilias_version) - { - $this->id = $module_id; - $this->title = $module_data['title']; - $this->description = $module_data['description']; - $this->ilias_index = $ilias_index; - $this->module_type = $module_data['type']; - $this->make_date = $module_data['create_date']; - $this->change_date = $module_data['last_update']; - $supported_modules = ConnectedIlias::getSupportedModuleTypes(); - $this->module_type_name = $supported_modules[$this->module_type]; - $this->owner = $module_data['owner']; - $this->author_studip = false; - if (is_array($module_data['references'][$module_id]['operations'])) { - $this->allowed_operations = $module_data['references'][$module_id]['operations']; - } else { - $this->allowed_operations = []; - } - $this->is_offline = $module_data['offline']; - $this->siblings_count = $module_data['ref_count']; - } - - /** - * get id - * - * returns id - * @access public - * @return string id - */ - function getId() - { - return $this->id; - } - - /** - * get ILIAS installation index - * - * returns ILIAS installation index - * @access public - * @return string cms-type - */ - function getIndex() - { - return $this->ilias_index; - } - - /** - * get module-type - * - * returns module-type - * @access public - * @return string module-type - */ - function getModuleType() - { - return $this->module_type; - } - - /** - * get module-type name - * - * returns module-type name - * @access public - * @return string module-type name - */ - function getModuleTypeName() - { - return $this->module_type_name; - } - - /** - * set title - * - * sets title - * @access public - * @param string $module_title title - */ - function setTitle($module_title) - { - $this->title = $module_title; - } - - /** - * get title - * - * returns title - * @access public - * @return string title - */ - function getTitle() - { - return $this->title; - } - - /** - * set description - * - * sets description - * @access public - * @param string $module_description description - */ - function setDescription($module_description) - { - $this->description = $module_description; - } - - /** - * get description - * - * returns description - * @access public - * @return string description - */ - function getDescription() - { - return $this->description; - } - - /** - * get make date - * - * returns make date - * @access public - * @return string make date - */ - function getMakeDate() - { - return $this->make_date; - } - - /** - * get change date - * - * returns change date - * @access public - * @return string change date - */ - function getChangeDate() - { - return $this->change_date; - } - - /** - * get ILIAS path - * - * returns ILIAS path - * @access public - * @return string ILIAS path - */ - function getPath() - { - return $this->path; - } - - /** - * get route - * - * returns route for given action - * @access public - * @param string action start, edit, view_tools, view_course, add, remove - * @return string url - */ - function getRoute($action = '') - { - switch ($action) { - case 'start' : return 'my_ilias_accounts/redirect/'.$this->ilias_index.'/start/'.$this->id.'/'.$this->module_type; - case 'edit' : return 'my_ilias_accounts/redirect/'.$this->ilias_index.'/edit/'.$this->id.'/'.$this->module_type; - case 'view_tools' : return 'my_ilias_accounts/view_object/'.$this->ilias_index.'/'.$this->id; - case 'view_course' : return 'course/ilias_interface/view_object/'.$this->ilias_index.'/'.$this->id; - case 'add' : return 'course/ilias_interface/edit_object_assignment/'.$this->ilias_index.'?add_module=1&ilias_module_id='.$this->id; - case 'remove' : return 'course/ilias_interface/edit_object_assignment/'.$this->ilias_index.'?remove_module&ilias_module_id='.$this->id; - } - - throw new InvalidArgumentException("Unknown action {$action}"); - } - - - /** - * get permission status - * - * returns true if given action is allowed - * @access public - * @param string action start, edit - * @return boolean permission status - */ - function isAllowed($action = '') - { - switch ($action) { - case 'start' : return in_array('read', $this->allowed_operations); - case 'edit' : return in_array('write', $this->allowed_operations); - case 'delete' : return in_array('delete', $this->allowed_operations); - case 'copy' : return in_array('copy', $this->allowed_operations); - } - return true; - } - - /** - * set ILIAS author ID - * - * sets author - * @access public - * @param array $module_authors authors - */ - function setAuthorIlias($module_author) - { - $this->owner = $module_author; - } - - /** - * get ILIAS author ID - * - * returns author - * @access public - * @return string author id - */ - function getAuthorIlias() - { - return $this->owner; - } - - /** - * get author Stud.IP-User - * - * returns author User object - * @access public - * @return object User - */ - function getAuthorStudip() - { - if (is_object($this->author_studip)) { - return $this->author_studip; - } - - $query = "SELECT studip_user_id - FROM auth_extern - WHERE external_user_id = ? AND external_user_system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->owner, $this->ilias_index]); - $data = $statement->fetch(PDO::FETCH_ASSOC); - if ($data) { - $this->author_studip = User::find($data['studip_user_id']); - } - - return $this->author_studip; - } - - /** - * set connection - * - * sets connection with seminar - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function setConnection($seminar_id) - { - $this->is_connected = true; - return IliasObjectConnections::setConnection($seminar_id, $this->id, $this->module_type, $this->ilias_index); - } - - /** - * unset connection - * - * unsets connection with seminar - * @access public - * @param string $seminar_id seminar-id - * @return boolean successful - */ - function unsetConnection($seminar_id) - { - $this->is_connected = false; - return IliasObjectConnections::unsetConnection($seminar_id, $this->id, $this->module_type, $this->ilias_index); - } - - /** - * set connection-status - * - * sets connection-status - * @access public - * @param boolean $is_connected connection-status - */ - function setConnectionType($is_connected) - { - $this->is_connected = $is_connected; - } - - /** - * get connection-status - * - * returns true, if module is connected to seminar - * @access public - * @return boolean connection-status - */ - function isConnected() - { - return $this->is_connected; - } - - /** - * get reference string - * - * returns reference string for content-module - * @access public - * @return string reference string - */ - function getReferenceString() - { - return $this->ilias_index."_".$this->module_type."_".$this->id; - } - - /** - * get icon-image - * - * returns icon-image - * @access public - * @return string icon-image - */ - function getIcon($mode = 'inactive') - { - if (!$this->icon_file) { - $this->icon_file = 'learnmodule'; - } - return Icon::create($this->icon_file, 'inactive', [])->asImg(); - } -} diff --git a/lib/ilias_interface/IliasModule.php b/lib/ilias_interface/IliasModule.php new file mode 100644 index 0000000..f9bf958 --- /dev/null +++ b/lib/ilias_interface/IliasModule.php @@ -0,0 +1,371 @@ + +* @access public +* @modulegroup ilias_interface_modules +* @module IliasModule +* @package ILIAS-Interface +*/ + +class IliasModule +{ + public $id; + public $title; + public $description; + public $module_type; + public $module_type_name; + public $author; + public $make_date; + public $change_date; + public $path; + public $ilias_index; + public $allowed_operations; + public $is_offline; + public $is_connected; + public $owner; + public $author_studip; + public $siblings_count; + public $icon_file; + + /** + * constructor + * + * init class + * @access public + * @param string $module_id module-id + * @param string $module_type module-type + * @param string $ilias_index ilias installation index + */ + function __construct($module_id, $module_data, $ilias_index, $ilias_version) + { + $this->id = $module_id; + $this->title = $module_data['title']; + $this->description = $module_data['description']; + $this->ilias_index = $ilias_index; + $this->module_type = $module_data['type']; + $this->make_date = $module_data['create_date']; + $this->change_date = $module_data['last_update']; + $supported_modules = ConnectedIlias::getSupportedModuleTypes(); + $this->module_type_name = $supported_modules[$this->module_type]; + $this->owner = $module_data['owner']; + $this->author_studip = false; + if (is_array($module_data['references'][$module_id]['operations'])) { + $this->allowed_operations = $module_data['references'][$module_id]['operations']; + } else { + $this->allowed_operations = []; + } + $this->is_offline = $module_data['offline']; + $this->siblings_count = $module_data['ref_count']; + } + + /** + * get id + * + * returns id + * @access public + * @return string id + */ + function getId() + { + return $this->id; + } + + /** + * get ILIAS installation index + * + * returns ILIAS installation index + * @access public + * @return string cms-type + */ + function getIndex() + { + return $this->ilias_index; + } + + /** + * get module-type + * + * returns module-type + * @access public + * @return string module-type + */ + function getModuleType() + { + return $this->module_type; + } + + /** + * get module-type name + * + * returns module-type name + * @access public + * @return string module-type name + */ + function getModuleTypeName() + { + return $this->module_type_name; + } + + /** + * set title + * + * sets title + * @access public + * @param string $module_title title + */ + function setTitle($module_title) + { + $this->title = $module_title; + } + + /** + * get title + * + * returns title + * @access public + * @return string title + */ + function getTitle() + { + return $this->title; + } + + /** + * set description + * + * sets description + * @access public + * @param string $module_description description + */ + function setDescription($module_description) + { + $this->description = $module_description; + } + + /** + * get description + * + * returns description + * @access public + * @return string description + */ + function getDescription() + { + return $this->description; + } + + /** + * get make date + * + * returns make date + * @access public + * @return string make date + */ + function getMakeDate() + { + return $this->make_date; + } + + /** + * get change date + * + * returns change date + * @access public + * @return string change date + */ + function getChangeDate() + { + return $this->change_date; + } + + /** + * get ILIAS path + * + * returns ILIAS path + * @access public + * @return string ILIAS path + */ + function getPath() + { + return $this->path; + } + + /** + * get route + * + * returns route for given action + * @access public + * @param string action start, edit, view_tools, view_course, add, remove + * @return string url + */ + function getRoute($action = '') + { + switch ($action) { + case 'start' : return 'my_ilias_accounts/redirect/'.$this->ilias_index.'/start/'.$this->id.'/'.$this->module_type; + case 'edit' : return 'my_ilias_accounts/redirect/'.$this->ilias_index.'/edit/'.$this->id.'/'.$this->module_type; + case 'view_tools' : return 'my_ilias_accounts/view_object/'.$this->ilias_index.'/'.$this->id; + case 'view_course' : return 'course/ilias_interface/view_object/'.$this->ilias_index.'/'.$this->id; + case 'add' : return 'course/ilias_interface/edit_object_assignment/'.$this->ilias_index.'?add_module=1&ilias_module_id='.$this->id; + case 'remove' : return 'course/ilias_interface/edit_object_assignment/'.$this->ilias_index.'?remove_module&ilias_module_id='.$this->id; + } + + throw new InvalidArgumentException("Unknown action {$action}"); + } + + + /** + * get permission status + * + * returns true if given action is allowed + * @access public + * @param string action start, edit + * @return boolean permission status + */ + function isAllowed($action = '') + { + switch ($action) { + case 'start' : return in_array('read', $this->allowed_operations); + case 'edit' : return in_array('write', $this->allowed_operations); + case 'delete' : return in_array('delete', $this->allowed_operations); + case 'copy' : return in_array('copy', $this->allowed_operations); + } + return true; + } + + /** + * set ILIAS author ID + * + * sets author + * @access public + * @param array $module_authors authors + */ + function setAuthorIlias($module_author) + { + $this->owner = $module_author; + } + + /** + * get ILIAS author ID + * + * returns author + * @access public + * @return string author id + */ + function getAuthorIlias() + { + return $this->owner; + } + + /** + * get author Stud.IP-User + * + * returns author User object + * @access public + * @return object User + */ + function getAuthorStudip() + { + if (is_object($this->author_studip)) { + return $this->author_studip; + } + + $query = "SELECT studip_user_id + FROM auth_extern + WHERE external_user_id = ? AND external_user_system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->owner, $this->ilias_index]); + $data = $statement->fetch(PDO::FETCH_ASSOC); + if ($data) { + $this->author_studip = User::find($data['studip_user_id']); + } + + return $this->author_studip; + } + + /** + * set connection + * + * sets connection with seminar + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function setConnection($seminar_id) + { + $this->is_connected = true; + return IliasObjectConnections::setConnection($seminar_id, $this->id, $this->module_type, $this->ilias_index); + } + + /** + * unset connection + * + * unsets connection with seminar + * @access public + * @param string $seminar_id seminar-id + * @return boolean successful + */ + function unsetConnection($seminar_id) + { + $this->is_connected = false; + return IliasObjectConnections::unsetConnection($seminar_id, $this->id, $this->module_type, $this->ilias_index); + } + + /** + * set connection-status + * + * sets connection-status + * @access public + * @param boolean $is_connected connection-status + */ + function setConnectionType($is_connected) + { + $this->is_connected = $is_connected; + } + + /** + * get connection-status + * + * returns true, if module is connected to seminar + * @access public + * @return boolean connection-status + */ + function isConnected() + { + return $this->is_connected; + } + + /** + * get reference string + * + * returns reference string for content-module + * @access public + * @return string reference string + */ + function getReferenceString() + { + return $this->ilias_index."_".$this->module_type."_".$this->id; + } + + /** + * get icon-image + * + * returns icon-image + * @access public + * @return string icon-image + */ + function getIcon($mode = 'inactive') + { + if (!$this->icon_file) { + $this->icon_file = 'learnmodule'; + } + return Icon::create($this->icon_file, 'inactive', [])->asImg(); + } +} diff --git a/lib/ilias_interface/IliasObjectConnections.class.php b/lib/ilias_interface/IliasObjectConnections.class.php deleted file mode 100644 index 0c6a8a9..0000000 --- a/lib/ilias_interface/IliasObjectConnections.class.php +++ /dev/null @@ -1,311 +0,0 @@ - -* @access public -* @modulegroup ilias_interface_modules -* @module IliasObjectConnections -* @package Ilias-Interface -*/ -class IliasObjectConnections -{ - public $id; - public $object_connections; - /** - * constructor - * - * init class. - * @access public - * @param string $object_id object-id - */ - function __construct($object_id = "") - { - $this->id = $object_id; - if ($object_id != "") - $this->readData(); - } - - /** - * read object connections - * - * gets object connections from database - * @access public - */ - function readData() - { - $this->object_connections = []; - - $query = "SELECT system_type, module_type, module_id, chdate - FROM object_contentmodules - WHERE object_id = ? - ORDER BY chdate DESC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->id]); - - $module_count = 0; - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $module_count += 1; - $d_system_type = $row['system_type']; - $d_module_type = $row['module_type']; - $d_module_id = $row['module_id']; - - $reference = $d_system_type . '_' . $d_module_type . '_' . $d_module_id; - $reference = $d_module_id; - $this->object_connections[$d_system_type][$reference]['index'] = $d_system_type; - $this->object_connections[$d_system_type][$reference]['type'] = $d_module_type; - $this->object_connections[$d_system_type][$reference]['id'] = $d_module_id; - $this->object_connections[$d_system_type][$reference]['chdate'] = $row['chdate']; - } - - if ($module_count == 0) { - $this->object_connections = false; - } - } - - /** - * get object connections - * - * returns object connections - * @access public - * @return array object connections - */ - function getConnections() - { - return $this->object_connections; - } - - /** - * get connection-status - * - * returns true, if object has connections - * @access public - * @return boolean connection-status - */ - function isConnected($ilias_index = '') - { - if ($ilias_index) { - return (boolean) $this->object_connections[$ilias_index]; - } else { - return (boolean) $this->object_connections; - } - } - - /** - * get connection-status - * - * returns true, if object has connections - * @access public - * @param string $object_id object-id (optional) - * @return boolean connection-status - */ - public static function isObjectConnected($index, $object_id) - { - if ($object_id && $index) { - $query = "SELECT 1 FROM object_contentmodules WHERE object_id = ? AND system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$object_id, $index]); - return (bool) $statement->fetchColumn(); - } - - return false; - } - - /** - * get connection-status - * - * returns true, if course has connections to ILIAS courses - * @access public - * @param string $object_id object-id (optional) - * @return boolean connection-status - */ - public static function isCourseConnected($object_id) - { - if ($object_id) { - $query = "SELECT 1 FROM object_contentmodules WHERE object_id = ? AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$object_id, 'crs']); - return (bool)$statement->fetchColumn(); - } else { - return false; - } - } - - /** - * get module-id - * - * returns module-id of given connection - * @access public - * @param string $connection_object_id object-id - * @param string $connection_module_type module-type - * @param string $connection_cms system-type - * @return string module-id - */ - public static function getConnectionModuleId($connection_object_id, $connection_module_type, $connection_cms) - { - $query = "SELECT module_id - FROM object_contentmodules - WHERE object_id = ? AND system_type = ? AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_cms, - $connection_module_type - ]); - return $statement->fetchColumn() ?: false; - } - - /** - * set connection - * - * sets connection with object - * @access public - * @param string $connection_object_id object-id - * @param string $connection_module_id module-id - * @param string $connection_module_type module-type - * @param string $connection_cms system-type - * @return boolean successful - */ - public static function setConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) - { - $query = "SELECT 1 - FROM object_contentmodules - WHERE object_id = ? AND module_id = ? AND system_type = ? - AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - $check = $statement->fetchColumn(); - - if ($check) { - $query = "UPDATE object_contentmodules - SET module_type = ?, chdate = UNIX_TIMESTAMP() - WHERE object_id = ? AND module_id = ? AND system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_module_type, - $connection_object_id, - $connection_module_id, - $connection_cms - ]); - } else { - $query = "INSERT INTO object_contentmodules - (object_id, module_id, system_type, module_type, mkdate, chdate) - VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - } - return true; - } - - /** - * unset connection - * - * deletes connection with object - * @access public - * @param string $connection_object_id object-id - * @param string $connection_module_id module-id - * @param string $connection_module_type module-type - * @param string $connection_cms system-type - * @return boolean successful - */ - public static function unsetConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) - { - $query = "SELECT 1 - FROM object_contentmodules - WHERE object_id = ? AND module_id = ? AND system_type = ? - AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - $check = $statement->fetchColumn(); - - - if ($check) { - $query = "DELETE FROM object_contentmodules - WHERE object_id = ? AND module_id = ? AND system_type = ? - AND module_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $connection_object_id, - $connection_module_id, - $connection_cms, - $connection_module_type - ]); - return true; - } - return false; - } - - public static function GetConnectedSystems($object_id) - { - $query = "SELECT DISTINCT system_type - FROM object_contentmodules - WHERE object_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$object_id]); - return $statement->fetchAll(PDO::FETCH_COLUMN); - } - - public static function DeleteAllConnections($object_id, $cms_type) - { - $query = "DELETE FROM object_contentmodules - WHERE object_id = ? AND system_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$object_id, $cms_type]); - return $statement->rowCount(); - } - - /** - * @param Course $course - * @return int - */ - public static function importIliasResultsForCourse(Course $course): int - { - $connected_ilias = []; - $students = new SimpleCollection($course->getMembersWithStatus('autor')); - $num = 0; - foreach (Grading\Definition::findBySQL("course_id = ? AND tool='ILIAS'", [$course->id]) as $definition) { - [$index, $module_id, $import_type] = explode('-', $definition->item); - if (!isset($connected_ilias[$index])) { - $connected_ilias[$index] = new ConnectedIlias($index); - } - $test_result = $connected_ilias[$index]->soap_client->getTestResults($module_id); - foreach ($test_result as $result) { - $ilias_user = $connected_ilias[$index]->getConnectedUser($result['user_id']); - if ($ilias_user) { - $member = $students->findOneBy('user_id', $ilias_user->getStudipId()); - if ($member) { - $grade = Grading\Instance::import([ - 'definition_id' => $definition->id, - 'user_id' => $member->user_id, - 'rawgrade' => $import_type & 1 && $result['maximum_points'] ? $result['received_points'] / $result['maximum_points'] : 0, - 'passed' => $import_type & 2 ? $result['passed'] : 0 - ] - ); - $num += $grade->store(); - } - } - } - } - return $num; - } -} diff --git a/lib/ilias_interface/IliasObjectConnections.php b/lib/ilias_interface/IliasObjectConnections.php new file mode 100644 index 0000000..0c6a8a9 --- /dev/null +++ b/lib/ilias_interface/IliasObjectConnections.php @@ -0,0 +1,311 @@ + +* @access public +* @modulegroup ilias_interface_modules +* @module IliasObjectConnections +* @package Ilias-Interface +*/ +class IliasObjectConnections +{ + public $id; + public $object_connections; + /** + * constructor + * + * init class. + * @access public + * @param string $object_id object-id + */ + function __construct($object_id = "") + { + $this->id = $object_id; + if ($object_id != "") + $this->readData(); + } + + /** + * read object connections + * + * gets object connections from database + * @access public + */ + function readData() + { + $this->object_connections = []; + + $query = "SELECT system_type, module_type, module_id, chdate + FROM object_contentmodules + WHERE object_id = ? + ORDER BY chdate DESC"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->id]); + + $module_count = 0; + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $module_count += 1; + $d_system_type = $row['system_type']; + $d_module_type = $row['module_type']; + $d_module_id = $row['module_id']; + + $reference = $d_system_type . '_' . $d_module_type . '_' . $d_module_id; + $reference = $d_module_id; + $this->object_connections[$d_system_type][$reference]['index'] = $d_system_type; + $this->object_connections[$d_system_type][$reference]['type'] = $d_module_type; + $this->object_connections[$d_system_type][$reference]['id'] = $d_module_id; + $this->object_connections[$d_system_type][$reference]['chdate'] = $row['chdate']; + } + + if ($module_count == 0) { + $this->object_connections = false; + } + } + + /** + * get object connections + * + * returns object connections + * @access public + * @return array object connections + */ + function getConnections() + { + return $this->object_connections; + } + + /** + * get connection-status + * + * returns true, if object has connections + * @access public + * @return boolean connection-status + */ + function isConnected($ilias_index = '') + { + if ($ilias_index) { + return (boolean) $this->object_connections[$ilias_index]; + } else { + return (boolean) $this->object_connections; + } + } + + /** + * get connection-status + * + * returns true, if object has connections + * @access public + * @param string $object_id object-id (optional) + * @return boolean connection-status + */ + public static function isObjectConnected($index, $object_id) + { + if ($object_id && $index) { + $query = "SELECT 1 FROM object_contentmodules WHERE object_id = ? AND system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$object_id, $index]); + return (bool) $statement->fetchColumn(); + } + + return false; + } + + /** + * get connection-status + * + * returns true, if course has connections to ILIAS courses + * @access public + * @param string $object_id object-id (optional) + * @return boolean connection-status + */ + public static function isCourseConnected($object_id) + { + if ($object_id) { + $query = "SELECT 1 FROM object_contentmodules WHERE object_id = ? AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$object_id, 'crs']); + return (bool)$statement->fetchColumn(); + } else { + return false; + } + } + + /** + * get module-id + * + * returns module-id of given connection + * @access public + * @param string $connection_object_id object-id + * @param string $connection_module_type module-type + * @param string $connection_cms system-type + * @return string module-id + */ + public static function getConnectionModuleId($connection_object_id, $connection_module_type, $connection_cms) + { + $query = "SELECT module_id + FROM object_contentmodules + WHERE object_id = ? AND system_type = ? AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_cms, + $connection_module_type + ]); + return $statement->fetchColumn() ?: false; + } + + /** + * set connection + * + * sets connection with object + * @access public + * @param string $connection_object_id object-id + * @param string $connection_module_id module-id + * @param string $connection_module_type module-type + * @param string $connection_cms system-type + * @return boolean successful + */ + public static function setConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) + { + $query = "SELECT 1 + FROM object_contentmodules + WHERE object_id = ? AND module_id = ? AND system_type = ? + AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + $check = $statement->fetchColumn(); + + if ($check) { + $query = "UPDATE object_contentmodules + SET module_type = ?, chdate = UNIX_TIMESTAMP() + WHERE object_id = ? AND module_id = ? AND system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_module_type, + $connection_object_id, + $connection_module_id, + $connection_cms + ]); + } else { + $query = "INSERT INTO object_contentmodules + (object_id, module_id, system_type, module_type, mkdate, chdate) + VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + } + return true; + } + + /** + * unset connection + * + * deletes connection with object + * @access public + * @param string $connection_object_id object-id + * @param string $connection_module_id module-id + * @param string $connection_module_type module-type + * @param string $connection_cms system-type + * @return boolean successful + */ + public static function unsetConnection($connection_object_id, $connection_module_id, $connection_module_type, $connection_cms) + { + $query = "SELECT 1 + FROM object_contentmodules + WHERE object_id = ? AND module_id = ? AND system_type = ? + AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + $check = $statement->fetchColumn(); + + + if ($check) { + $query = "DELETE FROM object_contentmodules + WHERE object_id = ? AND module_id = ? AND system_type = ? + AND module_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $connection_object_id, + $connection_module_id, + $connection_cms, + $connection_module_type + ]); + return true; + } + return false; + } + + public static function GetConnectedSystems($object_id) + { + $query = "SELECT DISTINCT system_type + FROM object_contentmodules + WHERE object_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$object_id]); + return $statement->fetchAll(PDO::FETCH_COLUMN); + } + + public static function DeleteAllConnections($object_id, $cms_type) + { + $query = "DELETE FROM object_contentmodules + WHERE object_id = ? AND system_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$object_id, $cms_type]); + return $statement->rowCount(); + } + + /** + * @param Course $course + * @return int + */ + public static function importIliasResultsForCourse(Course $course): int + { + $connected_ilias = []; + $students = new SimpleCollection($course->getMembersWithStatus('autor')); + $num = 0; + foreach (Grading\Definition::findBySQL("course_id = ? AND tool='ILIAS'", [$course->id]) as $definition) { + [$index, $module_id, $import_type] = explode('-', $definition->item); + if (!isset($connected_ilias[$index])) { + $connected_ilias[$index] = new ConnectedIlias($index); + } + $test_result = $connected_ilias[$index]->soap_client->getTestResults($module_id); + foreach ($test_result as $result) { + $ilias_user = $connected_ilias[$index]->getConnectedUser($result['user_id']); + if ($ilias_user) { + $member = $students->findOneBy('user_id', $ilias_user->getStudipId()); + if ($member) { + $grade = Grading\Instance::import([ + 'definition_id' => $definition->id, + 'user_id' => $member->user_id, + 'rawgrade' => $import_type & 1 && $result['maximum_points'] ? $result['received_points'] / $result['maximum_points'] : 0, + 'passed' => $import_type & 2 ? $result['passed'] : 0 + ] + ); + $num += $grade->store(); + } + } + } + } + return $num; + } +} diff --git a/lib/ilias_interface/IliasSoap.class.php b/lib/ilias_interface/IliasSoap.class.php deleted file mode 100644 index 7911e9e..0000000 --- a/lib/ilias_interface/IliasSoap.class.php +++ /dev/null @@ -1,1709 +0,0 @@ - -* @access public -* @modulegroup ilias_interface_modules -* @module IliasSoap -* @package ILIAS-Interface -*/ -class IliasSoap extends StudipSoapClient -{ - private $index; - private $ilias_client; - private $ilias_version; - private $admin_login; - private $admin_password; - private $admin_sid; - private $user_sid; - private $user_type; - private $soap_cache; - private $separator_string; - private $caching_active = false; - - - /** - * constructor - * - * init class. - * @access public - * @param string $index ILIAS installation index - * @param string $soap_path SOAP url - * @param string $ilias_client ILIAS client - * @param string $ilias_version ILIAS int client - * @param string $admin_login ILIAS admin account login - * @param string $admin_password ILIAS admin account password - */ - public function __construct($index, $soap_path, $ilias_client = '', $ilias_version = '', $admin_login = '', $admin_password = '') - { - $this->index = $index; - $this->ilias_client = $ilias_client; - $this->ilias_version= $ilias_version; - $this->admin_login = $admin_login; - $this->admin_password = $admin_password; - $this->separator_string = " / "; - - parent::__construct($soap_path); - - $this->user_type = "admin"; - - $this->loadCacheData(); - } - - /** - * set usertype - * - * sets usertype for soap-calls - * @access public - * @param string user_type usertype (admin or user) - */ - function setUserType($user_type) - { - $this->user_type = $user_type; - } - - /** - * get sid - * - * returns soap-session-id - * @access public - * @return string session-id - */ - function getSID() - { - if ($this->user_type == "admin") { - if ($this->admin_sid == false) - $this->loginAdmin(); - return $this->admin_sid; - } - if ($this->user_type == "user") { - if ($this->user_sid == false) { - throw new Exception('Not implemented'); - } - return $this->user_sid; - } - return false; - } - - /** - * call soap-function - * - * calls soap-function with given parameters - * @access public - * @param string method method-name - * @param string params parameters - * @return mixed result - */ - function call($method, $params) - { - // return false if no session_id is given - if ($method !== 'login' && $method !== 'getInstallationInfoXML' && $method !== 'getClientInfoXML' && $params['sid'] == '') { - return false; - } - - $cache_index = md5($method . ':' . implode('-', $params)); - if ($this->caching_active && isset($this->soap_cache[$cache_index]) && $method !== 'login') { - $result = $this->soap_cache[$cache_index]; - } else { - $result = $this->_call($method, $params); - // if Session is expired, re-login and try again - if ($method !== 'login' && $this->soap_client->fault && in_array(mb_strtolower($this->faultstring), ['session not valid', 'session invalid', 'session idled'])) { - $caching_status = $this->caching_active; - $this->caching_active = false; - $params["sid"] = $this->getSID(); - $result = $this->_call($method, $params); - $this->caching_active = $caching_status; - } elseif (! $this->soap_client->fault) { - $this->soap_cache[$cache_index] = $result; - if ($this->caching_active == true) { - $this->saveCacheData(); - } - } - } - return $result; - } - - /** - * load cache - * - * load soap-cache - * @access public - * @param string cms cms-type - */ - function loadCacheData() - { - $this->soap_cache = (array)$_SESSION["cache_data"][$this->index]; - } - - /** - * get caching status - * - * gets caching-status - * @access public - * @return boolean status - */ - function getCachingStatus() - { - return $this->caching_active; - } - - /** - * set caching status - * - * sets caching-status - * @access public - * @param boolean bool_value status - */ - function setCachingStatus($bool_value) - { - $this->caching_active = $bool_value; - } - - /** - * clear cache - * - * clears cache - * @access public - */ - function clearCache() - { - $this->soap_cache = []; - $_SESSION["cache_data"][$this->index] = []; - - } - - /** - * save cache - * - * saves soap-cache in session-variable - * @access public - */ - function saveCacheData() - { - $_SESSION["cache_data"][$this->index] = $this->soap_cache; - } - - /** - * parse xml - * - * use xml-parser - * @access public - * @param string data xml-data - * @return array object - */ - function ParseXML($data) - { - //$xml_parser = new Ilias3ObjectXMLParser($data); - $xml_parser = new ilObjectXMLParser($data); - $xml_parser->startParsing(); - return $xml_parser->getObjectData(); - } - - /** - * login with admin account - * - * login to ILIAS soap webservice with admin account - * @access public - * @return string result - */ - function loginAdmin() - { - $param = [ - 'client' => $this->ilias_client, - 'username' => $this->admin_login, - 'password' => $this->admin_password - ]; - $result = $this->call('login', $param); - $this->admin_sid = $result; - return $result; - } - - /** - * login with admin account - * - * login to ILIAS soap webservice with current user - * @access public - * @return string result - */ - function loginUser($username, $password) - { - if ($this->ilias_version < 50305) { - // ILIAS-Versions below 5.3.5 (use LoginStudipUser) - $param = [ - 'client' => $this->ilias_client, - 'username' => $username, - 'password' => $password - ]; - $result = $this->call('loginStudipUser', $param); - $this->user_sid = $result; - return $result; - } else { - // ILIAS-Versions 5.3.5 and above (use StudipAuthPlugin) - $param = [ - 'client' => $this->ilias_client, - 'username' => $username, - 'password' => $password - ]; - $result = $this->call('login', $param); - $this->user_sid = $result; - return $result; - } - } - - /** - * logout - * - * logout from soap-webservice - * @access public - * @return boolean result - */ - function logout() - { - $param = [ - 'sid' => $this->getSID() - ]; - return $this->call('logout', $param); - } - - /** - * Check Auth - * - * login to soap-webservice - * @access public - * @return string result - */ - function checkPassword($username, $password) - { - $param = [ - 'client' => $this->ilias_client, - 'username' => $username, - 'password' => $password - ]; - $result = $this->call('login', $param); - return $result; - } - -/////////////////////////// -// OBJECT-FUNCTIONS // -////////////////////////// - - /** - * parse ILIAS object - * - * parse XML and return ilias object(s) - * @access public - * @param string xml xml data - * @param string parent_id get data for child references of parent_id - * @return array objects - */ - function parseIliasObject($xml, $condition_field = '', $condition_value = '') - { - $s = simplexml_load_string($xml); - - $objects = []; - if (is_object($s->Object)) { - foreach ($s->Object as $object) { - $single_object = []; - $single_object['type'] = (string)$object[0]['type']; - $single_object['offline'] = (string)$object[0]['offline']; - $single_object['obj_id'] = (string)$object[0]['obj_id']; - $single_object['title'] = (string)$object->Title; - $single_object['description'] = (string)$object->Description; - $single_object['owner'] = (string)$object->Owner; - $single_object['create_date'] = (string)$object->CreateDate; - $single_object['last_update'] = (string)$object->LastUpdate; - $single_object['ref_count'] = count($object->References); - foreach ($object->References as $reference) { - //$single_object['references'][(string)$reference[0]['ref_id']]['ref_id'] = (string)$reference[0]['ref_id']; - if ($condition_field && ($reference[0][$condition_field] == $condition_value)) { - $single_object['ref_id'] = (string)$reference[0]['ref_id']; - $single_object['parent_id'] = (string)$reference[0]['parent_id']; - $single_object['accessInfo'] = (string)$reference[0]['accessInfo']; - foreach ($reference->Operation as $operation) { - $single_object['operations'][] = (string)$operation; - } - } - $single_object['references'][(string)$reference[0]['ref_id']]['parent_id'] = (string)$reference[0]['parent_id']; - $single_object['references'][(string)$reference[0]['ref_id']]['accessInfo'] = (string)$reference[0]['accessInfo']; - foreach ($reference->Operation as $operation) { - $single_object['references'][(string)$reference[0]['ref_id']]['operations'][] = (string)$operation; - } - foreach ($reference->Path->Element as $element) { - $single_object['references'][(string)$reference[0]['ref_id']]['path_names'][] = (string)$element; - $single_object['references'][(string)$reference[0]['ref_id']]['path_ids'][] = (string)$element[0]['ref_id']; - $single_object['references'][(string)$reference[0]['ref_id']]['path_types'][] = (string)$element[0]['type']; - } - } - if ($single_object['ref_id']) { - $objects[$single_object['ref_id']] = $single_object; - } elseif (!$condition_field) { - $objects[] = $single_object; - } - } - } - return $objects; - } - - - /** - * search objects - * - * search for ilias-objects - * @access public - * @param array types types - * @param string key keyword - * @param string combination search-combination - * @param string user_id ilias-user-id - * @return array objects - */ - function searchObjects($types, $key, $combination, $user_id = "") - { - $param = [ - 'sid' => $this->getSID(), - 'types' => $types, - 'key' => $key, - 'combination' => $combination, - 'user_id' => (int)$user_id - ]; - - $result = $this->call('searchObjects', $param); - if ($result) - { - return $this->parseIliasObject($result); - } - return false; - - } - - /** - * get object by reference - * - * gets object by reference-id - * @access public - * @param ref reference_id - * @param string user_id ilias-user-id - * @return array object - */ - function getObjectByReference($ref, $user_id = "") - { - $param = [ - 'sid' => $this->getSID(), - 'reference_id' => $ref, - 'user_id' => (int)$user_id - ]; - - $result = $this->call('getObjectByReference', $param); - if ($result != false) - { - - $objects = $this->parseIliasObject($result, 'ref_id', $ref); - return $objects[$ref]; - } - return false; - } - - /** - * get object by title - * - * gets object by title - * @access public - * @param string key keyword - * @param string type object-type - * @return array object - */ - function getObjectByTitle($key, $type = "") - { - $param = [ - 'sid' => $this->getSID(), - 'title' => $key, - 'user_id' => 0 - ]; - $result = $this->call('getObjectsByTitle', $param); - if ($result != false) - { - $objects = $this->parseIliasObject($result); - //$objects = $this->parseXML($result); - foreach($objects as $index => $object_data) - { - if (($type != "") AND ($object_data["type"] != $type)) - unset($objects[$index]); - elseif (! (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === 0)) - unset($objects[$index]); - } - reset($objects); - if (sizeof($objects) > 0) - return current($objects); - } - return false; - } - - /** - * get reference by title - * - * gets reference-id by object-title - * @access public - * @param string key keyword - * @param string type object-type - * @return string reference-id - */ - function getReferenceByTitle($key, $type = "") - { - $param = [ - 'sid' => $this->getSID(), - 'title' => $key, - 'user_id' => 0 - ]; - $result = $this->call('getObjectsByTitle', $param); - - if ($result != false) - { - $objects = $this->parseIliasObject($result); - foreach($objects as $index => $object_data) - { - if (($type != "") AND ($object_data["type"] != $type)) - unset($objects[$index]); - elseif (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === false) - unset($objects[$index]); - } - if (sizeof($objects) > 0) - foreach($objects as $object_data) - if (sizeof($object_data["references"]) > 0) - { - return key($object_data["references"]); - //return $object_data["references"][0]["ref_id"]; - } - } - return false; - } - - /** - * add object - * - * adds new ilias-object - * @access public - * @param array object_data object-data - * @param string ref_id reference-id - * @return string result - */ - function addObject($object_data, $ref_id) - { - $this->clearCache(); - $type = $object_data["type"]; - $title = htmlReady($object_data["title"]); - $description = htmlReady($object_data["description"]); - - $xml = " - - - - $title - - - $description - - -"; - - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'object_xml' => $xml - ]; - return $this->call('addObject', $param); - } - - /** - * delete object - * - * deletes ilias-object - * @access public - * @param string ref_id reference-id - * @return boolean result - */ - function deleteObject($reference_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'reference_id' => $reference_id - ]; - return $this->call('deleteObject', $param); - } - - /** - * add reference - * - * add a new reference to an existing ilias-object - * @access public - * @param string object_id source-object-id - * @param string ref_id target-id - * @return string created reference-id - */ - function addReference($object_id, $ref_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'source_id' => $object_id, - 'target_id' => $ref_id - ]; - return $this->call('addReference', $param); - } - - /** - * add references to desktop - * - * adds references to personal desktop - * @access public - * @param string object_id source-object-id - * @param string ref_id target-id - * @return string created reference-id - */ - function addDesktopItems($user_id, $ref_ids) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id, - 'reference_ids' => $ref_ids - ]; - return $this->call('addDesktopItems', $param); - } - - /** - * get tree childs - * - * gets child-objects of the given tree node - * @access public - * @param string ref_id reference-id - * @param array types show only childs with these types - * @param string user_id user-id for permissions - * @return array objects - */ - function getTreeChilds($ref_id, $types = "", $user_id = "") - { - if ($types == "") - $types = []; - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'types' => $types, - 'user_id' => (int)$user_id - ]; - - $result = $this->call('getTreeChilds', $param); - $tree_childs = []; - if ($result != false) { - $tree_childs = $this->parseIliasObject($result, 'parent_id', $ref_id); - } - return $tree_childs; - } - -///////////////////////// -// RBAC-FUNCTIONS // -/////////////////////// - /** - * get operation - * - * gets all ilias operations - * @access public - * @return array operations - */ - function getOperations() - { - $param = [ - 'sid' => $this->getSID() - ]; - $result = $this->call('getOperations', $param); - if (is_array($result)) - foreach ($result as $operation_set) - $operations[$operation_set["operation"]] = $operation_set["ops_id"]; - return $operations; - } - - /** - * get object tree operations - * - * gets permissions for object at given tree-node - * @access public - * @param string ref_id reference-id - * @param string user_id user-id for permissions - * @return array operation-ids - */ - function getObjectTreeOperations($ref_id, $user_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'user_id' => $user_id - ]; - $result = $this->call('getObjectTreeOperations', $param); - if ($result != false) - { - $ops_ids = []; - foreach ($result as $operation_set) - $ops_ids[] = $operation_set["ops_id"]; - return $ops_ids; - } - return false; - } - - /** - * get user roles - * - * gets user roles - * @access public - * @param string user_id user-id - * @return array role-ids - */ - function getUserRoles($user_id) - { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id - ]; - $result = $this->call('getUserRoles', $param); - if ($result != false) - { - // TODO: change to simple xml - $objects = $this->parseXML($result); - $roles = []; - foreach ($objects as $count => $role) - $roles[$count] = $role["obj_id"]; - return $roles; - } - return false; - } - - /** - * get local roles - * - * gets local roles for given object - * @access public - * @param string course_id object-id - * @return array role-objects - */ - function getLocalRoles($course_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $course_id - ]; - $result = $this->call('getLocalRoles', $param); - if ($result != false) - { - // TODO: change to simple xml - $objects = $this->parseXML($result); - return $objects; - } - return false; - } - - /** - * add role - * - * adds a new role - * @access public - * @param array role_data data for role-object - * @param string ref_id reference-id - * @return string role-id - */ - function addRole($role_data, $ref_id) - { - $this->clearCache(); - $type = "role"; - $title = htmlReady($role_data["title"]); - $description = htmlReady($role_data["description"]); - - $xml = " - - - - $title - - - $description - - -"; - - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'obj_xml' => $xml - ]; - $result = $this->call('addRole', $param); - if (is_array($result)) - return current($result); - else - return false; - } - - /** - * add role from tremplate - * - * adds a new role and adopts properties of the given role template - * @access public - * @param array role_data data for role-object - * @param string ref_id reference-id - * @param string role_id role-template-id - * @return string role-id - */ - function addRoleFromTemplate($role_data, $ref_id, $role_id) - { - $this->clearCache(); - $type = "role"; - $title = htmlReady($role_data["title"]); - $description = htmlReady($role_data["description"]); - - $xml = " - - - - $title - - - $description - - -"; - - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'obj_xml' => $xml, - 'role_template_id' => $role_id - ]; - $result = $this->call('addRoleFromTemplate', $param); - if (is_array($result)) - return current($result); - else - return false; - } - - /** - * delete user role entry - * - * deletes a role entry from the given user - * @access public - * @param string user_id user-id - * @param string role_id role-id - * @return boolean result - */ - function deleteUserRoleEntry($user_id, $role_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id, - 'role_id' => $role_id - ]; - return $this->call('deleteUserRoleEntry', $param); - } - - /** - * add user role entry - * - * adds a role entry for the given user - * @access public - * @param string user_id user-id - * @param string role_id role-id - * @return boolean result - */ - function addUserRoleEntry($user_id, $role_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id, - 'role_id' => $role_id - ]; - return $this->call('addUserRoleEntry', $param); - } - - /** - * grant permissions - * - * grants permissions for given operations at role-id and ref-id - * @access public - * @param array operations operation-array - * @param string role_id role-id - * @param string ref_id reference-id - * @return boolean result - */ - function grantPermissions($operations, $role_id, $ref_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'role_id' => $role_id, - 'operations' => $operations, - ]; - return $this->call('grantPermissions', $param); - } - - /** - * revoke permissions - * - * revokes all permissions role-id and ref-id - * @access public - * @param string role_id role-id - * @param string ref_id reference-id - * @return boolean result - */ - function revokePermissions($role_id, $ref_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'role_id' => $role_id, - ]; - return $this->call('revokePermissions', $param); - } - -///////////////////////// -// USER-FUNCTIONS // -/////////////////////// - - /** - * lookup user - * - * gets user-id for given username - * @access public - * @param string username username - * @return string user-id - */ - function lookupUser($username) - { - $param = [ - 'sid' => $this->getSID(), - 'user_name' => $username, - ]; - return $this->call('lookupUser', $param); // returns user_id - } - - /** - * get user - * - * gets user-data for given user-id - * @access public - * @param string $user_id user-id - * @return array user-data - */ - function getUser($user_id) - { - if ($this->ilias_version < 80000) { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id - ]; - $result = $this->call('getUser', $param); // returns user data array - return $result; - } else { - $param = [ - 'sid' => $this->getSID(), - 'user_ids' => [$user_id], - 'attach_roles' => 0, - ]; - $result = $this->call('getUserXML', $param); // returns user xml data - if ($result) { - $s = simplexml_load_string($result); - $user_array = []; - - foreach ($s->User as $user) { - $id_parts = explode('usr_', $user->attributes()->Id); - if ($id_parts[1] == $user_id) { - $user_array['usr_id'] = $user_id; - $user_array['user_language'] = (string)$user->attributes()->Language; - $user_array['login'] = (string)$user->Login; - $user_array['firstname'] = (string)$user->Firstname; - $user_array['lastname'] = (string)$user->Lastname; - $user_array['title'] = (string)$user->Title; - $user_array['email'] = (string)$user->Email; - $user_array['active'] = (string)$user->Active; - $user_array['authmode'] = (string)$user->AuthMode->attributes()->type; - return $user_array; - } - } - } - return false; - } - } - - /** - * get user fullname - * - * gets user-data for given user-id - * @access public - * @param string user_id user-id - * @return string full name - */ - function getUserFullname($user_id) - { - $result = $this->getUser($user_id); - - return !empty($result) ? trim(sprintf('%s %s %s', $result['title'], $result['firstname'], $result['lastname'])) : ''; - } - - /** - * search users - * - * search for ilias users - * @access public - * @param array types types - * @param string key keyword - * @param string combination search-combination - * @param string user_id ilias-user-id - * @return array objects - */ - function searchUser($user_id) - { - if ($user_id != "") { - $param = [ - 'sid' => $this->getSID(), - 'user_ids' => [$user_id], - 'attach_roles' => 0 - ]; - $result = $this->call('getUserXML', $param); - if ($result != false) - { - // TODO: change to simple xml - $objects = $this->parseXML($result); - $all_objects = []; - foreach($objects as $count => $object_data){ - if (is_array($object_data["references"])) - { - foreach($object_data["references"] as $ref_data) - if ($ref_data["accessInfo"] == "granted" - && (count($all_objects[$object_data["obj_id"]]["operations"]) < count($ref_data["operations"]))) - { - $all_objects[$object_data["obj_id"]] = $object_data; - unset($all_objects[$object_data["obj_id"]]["references"]); - $all_objects[$object_data["obj_id"]]["ref_id"] = $ref_data["ref_id"]; - $all_objects[$object_data["obj_id"]]["accessInfo"] = $ref_data["accessInfo"]; - $all_objects[$object_data["obj_id"]]["operations"] = $ref_data["operations"]; - } - } - } - if (count($all_objects)){ - foreach($all_objects as $one_object){ - $ret[$one_object['ref_id']] = $one_object; - } - return $ret; - } - } - } - return false; - } - - /** - * add user by importUsers - * - * adds new user and sets role-id - * @access public - * @param array user_data user-data - * @param string role_id global role-id for new user - * @return string user-id - */ - function addUser($user_data, $role_id) - { - $this->clearCache(); - foreach($user_data as $key => $value) { - $user_data[$key] = htmlReady($user_data[$key]); - } - $update = $user_data["id"]; - - $usr_xml = " - - -".$user_data["login"]." -".$user_data["passwd"]." -".$user_data["firstname"]." -".$user_data["lastname"]." -".$user_data["title"]." -".$user_data["gender"]." -".$user_data["email"]." -".$user_data["street"]." -".$user_data["phone_home"].""; - if ($user_data["matriculation"] !== '') { - $usr_xml .= "".(int)$user_data["matriculation"].""; - } - $usr_xml .= " -true -".$user_data["time_limit_unlimited"]." -0 -".$user_data["approve_date"]." -".$user_data["agree_date"].""; - if (($user_data["user_skin"] != "") OR ($user_data["user_style"] != "")) { - $usr_xml .= ""; - } - $usr_xml .= " -".$user_data["external_account"]." - -"; - - $param = [ - 'sid' => $this->getSID(), - 'folder_id' => -1, - 'usr_xml' => $usr_xml, - 'conflict_rule' => 1, - 'send_account_mail' => 0 - ]; - $result = $this->call('importUsers', $param); - - $s = simplexml_load_string($result); - - if ((string)$s->rows->row->column[3] == "successful") - return (string)$s->rows->row->column[0]; - else - return false; - } - - /////////////////////////////////////////////////// - /** - * copy object - * - * copy ilias-object - * @access public - * @param string source_id reference-id - * @param string target_id reference-id - * @return string result - */ - function copyObject($source_id, $target_id) - { - $this->clearCache(); - - $xml = ""; - - $param = [ - 'sid' => $this->getSID(), - 'xml' => $xml - ]; - return $this->call('copyObject', $param); - } - - /** - * get structure - * - * returns structure for ilias content object - * @access public - * @param string ref_id reference id - * @return array result - */ - function getStructure($ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id - ]; - $result = $this->call('getStructureObjects', $param); - - $structure = []; - if ($result) { - $s = simplexml_load_string($result); - - foreach ($s->StructureObjects->StructureObject as $object) { - $structure[] = (string)$object->Title; - } - } - return $structure; - } - - /** - * get path - * - * returns repository-path to ilias-object - * @access public - * @param string source_id reference-id - * @param string target_id reference-id - * @return string result - */ - function getPath($ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id - ]; - $result = $this->call('getPathForRefId', $param); - - if ($result) { - $s = simplexml_load_string($result); - - foreach ($s->rows->row as $row) { - $path[] = (string)$row->column[2]; - } - } - - if (is_array($path)) { - return implode($this->separator_string, $path); - } else { - return false; - } - } - - /** - * - * returns repository-path to ilias-object - * - * @access public - * @param string source_id reference-id - * @param string target_id reference-id - * @return string result - */ - function getRawPath($ref_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id - ]; - $result = $this->call('getPathForRefId', $param); - - if ($result) { - $s = simplexml_load_string($result); - - foreach ($s->rows->row as $row) { - $path[] = (string)$row->column[0]; - } - } - - if (is_array($path)) { - return implode('_', $path); - } else { - return false; - } - } - - /** - * - * returns ILIAS-Server-Info - * - * @access public - * @return string result - */ - function getInstallationInfoXML() - { - $this->clearCache(); - $param = [ - ]; - $result = $this->call('getInstallationInfoXML', $param); - if ($result) { - $s = simplexml_load_string($result); - $version_info = (string)$s[0]['version']; - $version_parts = explode(' ', $version_info); - $data['version'] = $version_parts[0]; - $data['version_date'] = $version_parts[1]; - foreach($s->Clients->Client as $client) { - $data['clients'][] = (string)$client[0]['id']; - } - } - return $data; - } - ////////////////////////////////////////// - - /** - * update user - * - * update user-data - * @access public - * @param array user_data user-data - * @return string result - */ -/* function updateUser($user_data) - { - $this->clearCache(); - $param = array( - 'sid' => $this->getSID(), - 'user_data' => $user_data - ); - return $this->call('updateUser', $param); // returns boolean - } -/**/ - /** - * update password - * - * update password with given string and write it uncrypted to the ilias-database - * @access public - * @param string user_id user-id - * @param string password password - * @return string result - */ -/* function updatePassword($user_id, $password) - { - $this->clearCache(); - $param = array( - 'sid' => $this->getSID(), - 'user_id' => $user_id, - 'new_password' => $password - ); - return $this->call('updatePassword', $param); // returns boolean - } -/**/ - /** - * delete user - * - * deletes user-account - * @access public - * @param string $user_id user-id - * @return string result - */ - function deleteUser($user_id) - { - $this->clearCache(); - if ($this->ilias_version < 80000) { - $param = [ - 'sid' => $this->getSID(), - 'user_id' => $user_id - ]; - return $this->call('deleteUser', $param); // returns boolean - } else { - $user_data = $this->getUser($user_id); - if (!$user_data['login']) { - return false; - } - $usr_xml = ' - - ' . $user_data['login'] . ' - - '; - - $param = [ - 'sid' => $this->getSID(), - 'folder_id' => -1, - 'usr_xml' => $usr_xml, - 'conflict_rule' => 1, - 'send_account_mail' => 0 - ]; - $result = $this->call('importUsers', $param); - - $s = simplexml_load_string($result); - - if ((string)$s->rows->row->column[3] == "successful") { - return (string)$s->rows->row->column[0]; - } - return false; - } - } - -//////////////////////////// -// COURSE-FUNCTIONS // -////////////////////////// - - /** - * is course member - * - * checks if user is course-member - * @access public - * @param string user_id user-id - * @param string course_id course-id - * @return boolean result - */ - function isMember($user_id, $course_id) - { - $param = [ - 'sid' => $this->getSID(), - 'course_id' => $course_id, - 'user_id' => $user_id - ]; - $status = $this->call('isAssignedToCourse', $param); // returns 0 if not assigned, 1 => course admin, 2 => course member or 3 => course tutor - if ($status == 0) - return false; - else - return true; - } - - /** - * add course member - * - * adds user to course - * @access public - * @param string user_id user-id - * @param string type member-type (Admin, Tutor or Member) - * @param string course_id course-id - * @return boolean result - */ - function addMember($user_id, $type, $course_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'course_id' => $course_id, - 'user_id' => $user_id, - 'type' => $type - ]; - return $this->call('assignCourseMember', $param); - } - - /** - * add course - * - * adds course - * @access public - * @param array course_data course-data - * @param string ref_id target-id - * @return string course-id - */ - function addCourse($course_data, $ref_id) - { - $this->clearCache(); - foreach($course_data as $key => $value) { - $course_data[$key] = htmlReady($course_data[$key]); - } - - $xml = $this->getCourseXML($course_data); - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'crs_xml' => $xml - ]; - $crs_id = $this->call('addCourse', $param); - return $crs_id; - } - - /** - * add group - * - * adds group - * @access public - * @param array group_data group data - * @param string ref_id target id - * @return string group id - */ - function addGroup($group_data, $ref_id) - { - $this->clearCache(); - foreach($group_data as $key => $value) { - $group_data[$key] = htmlReady($group_data[$key]); - } - - $xml = $this->getGroupXML($group_data); - $param = [ - 'sid' => $this->getSID(), - 'target_id' => $ref_id, - 'group_xml' => $xml - ]; - $group_id = $this->call('addGroup', $param); - return $group_id; - } - - /** - * update group - * - * updates group - * @access public - * @param array group_data group data - * @param string ref_id group id - * @return string result - */ - function updateGroup($group_data, $ref_id) - { - $this->clearCache(); - foreach($group_data as $key => $value) { - $group_data[$key] = htmlReady($group_data[$key]); - } - - $xml = $this->getGroupXML($group_data); - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'xml' => $xml - ]; - $result = $this->call('updateGroup', $param); - return $result; - } - - /** - * assign group member - * - * assigns user to group - * @access public - * @param string group_id group id - * @param string user_id user id - * @param string type type - */ - function assignGroupMember($group_id, $user_id, $type = "Member") - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'group_id' => $group_id, - 'user_id' => $user_id, - 'type' => $type - ]; - return $this->call('assignGroupMember', $param); - } - - /** - * exclude group member - * - * removes user from group - * @access public - * @param string group_id group id - * @param string user_id user id - */ - function excludeGroupMember($group_id, $user_id) - { - $this->clearCache(); - $param = [ - 'sid' => $this->getSID(), - 'group_id' => $group_id, - 'user_id' => $user_id - ]; - return $this->call('excludeGroupMember', $param); - } - - /** - * get group - * - * returns group xml - * @access public - * @param string group_id group id - * @return string group xml - */ - function getGroup($group_id) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $group_id - ]; - $result = $this->call('getGroup', $param); - if ($result) { - $s = simplexml_load_string($result); - $data['title'] = (string)$s->title; - $data['members'] = []; - foreach($s->member as $member) { - $member_parts = explode('_usr_', (string)$member[0]['id']); - $data['members'][] = $member_parts[1]; - } - } - return $data; - } - - /** - * get course-xml - * - * gets course xml-object for given course-data - * @access public - * @param array course_data course-data - * @return string course-xml - */ - function getCourseXML($course_data) - { - $crs_language = $course_data["language"]; - $crs_admin_id = $course_data["admin_id"]; - $crs_title = $course_data["title"]; - $crs_desc = $course_data["description"]; - - $xml = " - - - - - - $crs_title - - - - $crs_desc - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - return $xml; - } - - - /** - * get group xml - * - * gets group xml object for given group data - * @access public - * @param array group_data group data - * @return string group xml - */ - function getGroupXML($group_data) - { - $xml = ' - '.$group_data['title'].' - - - - 0 - 0 - 0 - 1 - - - 0 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - - '; - return $xml; - } - /**/ - - /** - * get courses for given user - * - * gets course xml-object for given course-data - * @access public - * @param array course_data course-data - * @return string course-xml - */ - function getCoursesForUser($user_id, $status = 1) - { - $xmlrs = ' - - - - - - - - '.$user_id.' - '.$status.' - - - '; - $param = array( - 'sid' => $this->getSID(), - 'parameters' => $xmlrs - ); - $result = $this->call('getCoursesForUser', $param); - - if ($result) { - $s = simplexml_load_string($result); - foreach ($s->rows->row as $row) { - $ref_id = (string)$row->column[0]; - $xml = $this->parseXML((string)$row->column[1]); - $course = array_pop($xml); - $ret[$ref_id] = $course['title']; - } - } - if (is_array($ret)) { - return $ret; - } else { - return false; - } - } - - /** - * check reference by title - * - * gets reference id by object id - * @access public - * @param string key keyword - * @param string type object-type - * @return string reference-id - */ - function checkReferenceById($id) - { - $param = [ - 'sid' => $this->getSID(), - 'reference_id' => $id, - 'user_id' => 0 - ]; - - $result = $this->call('getObjectByReference', $param); - if ($result != false) - { - // TODO: change to simple xml - $objects = $this->parseXML($result); - if(is_array($objects)){ - foreach($objects as $index => $object_data){ - if(is_array($object_data['references'])){ - foreach($object_data['references'] as $reference){ - if($reference['ref_id'] == $id && $reference['accessInfo'] != 'object_deleted') return $object_data['obj_id']; - } - } - } - } - } - return false; - } - - /** - * @param string $ref_id - * @param bool $sum_only - * @return array|false - * @throws Exception - */ - public function getTestResults($ref_id, $sum_only = true) - { - $param = [ - 'sid' => $this->getSID(), - 'ref_id' => $ref_id, - 'sum_only' => $sum_only - ]; - $result = $this->call('getTestResults', $param); - if ($result !== false) { - $columns = []; - $data = []; - $xml = simplexml_load_string($result); - foreach ($xml->colspecs->colspec as $colspec) { - $columns[] = (string)$colspec['name']; - } - foreach ($xml->rows->row as $row) { - $data_row = []; - $i = 0; - foreach ($row->column as $column) { - $data_row[$columns[$i++]] = (string)$column; - } - if (isset($data_row['user_id'])) { - - } - $data[] = $data_row; - } - return $data; - } - return false; - } -} diff --git a/lib/ilias_interface/IliasSoap.php b/lib/ilias_interface/IliasSoap.php new file mode 100644 index 0000000..7911e9e --- /dev/null +++ b/lib/ilias_interface/IliasSoap.php @@ -0,0 +1,1709 @@ + +* @access public +* @modulegroup ilias_interface_modules +* @module IliasSoap +* @package ILIAS-Interface +*/ +class IliasSoap extends StudipSoapClient +{ + private $index; + private $ilias_client; + private $ilias_version; + private $admin_login; + private $admin_password; + private $admin_sid; + private $user_sid; + private $user_type; + private $soap_cache; + private $separator_string; + private $caching_active = false; + + + /** + * constructor + * + * init class. + * @access public + * @param string $index ILIAS installation index + * @param string $soap_path SOAP url + * @param string $ilias_client ILIAS client + * @param string $ilias_version ILIAS int client + * @param string $admin_login ILIAS admin account login + * @param string $admin_password ILIAS admin account password + */ + public function __construct($index, $soap_path, $ilias_client = '', $ilias_version = '', $admin_login = '', $admin_password = '') + { + $this->index = $index; + $this->ilias_client = $ilias_client; + $this->ilias_version= $ilias_version; + $this->admin_login = $admin_login; + $this->admin_password = $admin_password; + $this->separator_string = " / "; + + parent::__construct($soap_path); + + $this->user_type = "admin"; + + $this->loadCacheData(); + } + + /** + * set usertype + * + * sets usertype for soap-calls + * @access public + * @param string user_type usertype (admin or user) + */ + function setUserType($user_type) + { + $this->user_type = $user_type; + } + + /** + * get sid + * + * returns soap-session-id + * @access public + * @return string session-id + */ + function getSID() + { + if ($this->user_type == "admin") { + if ($this->admin_sid == false) + $this->loginAdmin(); + return $this->admin_sid; + } + if ($this->user_type == "user") { + if ($this->user_sid == false) { + throw new Exception('Not implemented'); + } + return $this->user_sid; + } + return false; + } + + /** + * call soap-function + * + * calls soap-function with given parameters + * @access public + * @param string method method-name + * @param string params parameters + * @return mixed result + */ + function call($method, $params) + { + // return false if no session_id is given + if ($method !== 'login' && $method !== 'getInstallationInfoXML' && $method !== 'getClientInfoXML' && $params['sid'] == '') { + return false; + } + + $cache_index = md5($method . ':' . implode('-', $params)); + if ($this->caching_active && isset($this->soap_cache[$cache_index]) && $method !== 'login') { + $result = $this->soap_cache[$cache_index]; + } else { + $result = $this->_call($method, $params); + // if Session is expired, re-login and try again + if ($method !== 'login' && $this->soap_client->fault && in_array(mb_strtolower($this->faultstring), ['session not valid', 'session invalid', 'session idled'])) { + $caching_status = $this->caching_active; + $this->caching_active = false; + $params["sid"] = $this->getSID(); + $result = $this->_call($method, $params); + $this->caching_active = $caching_status; + } elseif (! $this->soap_client->fault) { + $this->soap_cache[$cache_index] = $result; + if ($this->caching_active == true) { + $this->saveCacheData(); + } + } + } + return $result; + } + + /** + * load cache + * + * load soap-cache + * @access public + * @param string cms cms-type + */ + function loadCacheData() + { + $this->soap_cache = (array)$_SESSION["cache_data"][$this->index]; + } + + /** + * get caching status + * + * gets caching-status + * @access public + * @return boolean status + */ + function getCachingStatus() + { + return $this->caching_active; + } + + /** + * set caching status + * + * sets caching-status + * @access public + * @param boolean bool_value status + */ + function setCachingStatus($bool_value) + { + $this->caching_active = $bool_value; + } + + /** + * clear cache + * + * clears cache + * @access public + */ + function clearCache() + { + $this->soap_cache = []; + $_SESSION["cache_data"][$this->index] = []; + + } + + /** + * save cache + * + * saves soap-cache in session-variable + * @access public + */ + function saveCacheData() + { + $_SESSION["cache_data"][$this->index] = $this->soap_cache; + } + + /** + * parse xml + * + * use xml-parser + * @access public + * @param string data xml-data + * @return array object + */ + function ParseXML($data) + { + //$xml_parser = new Ilias3ObjectXMLParser($data); + $xml_parser = new ilObjectXMLParser($data); + $xml_parser->startParsing(); + return $xml_parser->getObjectData(); + } + + /** + * login with admin account + * + * login to ILIAS soap webservice with admin account + * @access public + * @return string result + */ + function loginAdmin() + { + $param = [ + 'client' => $this->ilias_client, + 'username' => $this->admin_login, + 'password' => $this->admin_password + ]; + $result = $this->call('login', $param); + $this->admin_sid = $result; + return $result; + } + + /** + * login with admin account + * + * login to ILIAS soap webservice with current user + * @access public + * @return string result + */ + function loginUser($username, $password) + { + if ($this->ilias_version < 50305) { + // ILIAS-Versions below 5.3.5 (use LoginStudipUser) + $param = [ + 'client' => $this->ilias_client, + 'username' => $username, + 'password' => $password + ]; + $result = $this->call('loginStudipUser', $param); + $this->user_sid = $result; + return $result; + } else { + // ILIAS-Versions 5.3.5 and above (use StudipAuthPlugin) + $param = [ + 'client' => $this->ilias_client, + 'username' => $username, + 'password' => $password + ]; + $result = $this->call('login', $param); + $this->user_sid = $result; + return $result; + } + } + + /** + * logout + * + * logout from soap-webservice + * @access public + * @return boolean result + */ + function logout() + { + $param = [ + 'sid' => $this->getSID() + ]; + return $this->call('logout', $param); + } + + /** + * Check Auth + * + * login to soap-webservice + * @access public + * @return string result + */ + function checkPassword($username, $password) + { + $param = [ + 'client' => $this->ilias_client, + 'username' => $username, + 'password' => $password + ]; + $result = $this->call('login', $param); + return $result; + } + +/////////////////////////// +// OBJECT-FUNCTIONS // +////////////////////////// + + /** + * parse ILIAS object + * + * parse XML and return ilias object(s) + * @access public + * @param string xml xml data + * @param string parent_id get data for child references of parent_id + * @return array objects + */ + function parseIliasObject($xml, $condition_field = '', $condition_value = '') + { + $s = simplexml_load_string($xml); + + $objects = []; + if (is_object($s->Object)) { + foreach ($s->Object as $object) { + $single_object = []; + $single_object['type'] = (string)$object[0]['type']; + $single_object['offline'] = (string)$object[0]['offline']; + $single_object['obj_id'] = (string)$object[0]['obj_id']; + $single_object['title'] = (string)$object->Title; + $single_object['description'] = (string)$object->Description; + $single_object['owner'] = (string)$object->Owner; + $single_object['create_date'] = (string)$object->CreateDate; + $single_object['last_update'] = (string)$object->LastUpdate; + $single_object['ref_count'] = count($object->References); + foreach ($object->References as $reference) { + //$single_object['references'][(string)$reference[0]['ref_id']]['ref_id'] = (string)$reference[0]['ref_id']; + if ($condition_field && ($reference[0][$condition_field] == $condition_value)) { + $single_object['ref_id'] = (string)$reference[0]['ref_id']; + $single_object['parent_id'] = (string)$reference[0]['parent_id']; + $single_object['accessInfo'] = (string)$reference[0]['accessInfo']; + foreach ($reference->Operation as $operation) { + $single_object['operations'][] = (string)$operation; + } + } + $single_object['references'][(string)$reference[0]['ref_id']]['parent_id'] = (string)$reference[0]['parent_id']; + $single_object['references'][(string)$reference[0]['ref_id']]['accessInfo'] = (string)$reference[0]['accessInfo']; + foreach ($reference->Operation as $operation) { + $single_object['references'][(string)$reference[0]['ref_id']]['operations'][] = (string)$operation; + } + foreach ($reference->Path->Element as $element) { + $single_object['references'][(string)$reference[0]['ref_id']]['path_names'][] = (string)$element; + $single_object['references'][(string)$reference[0]['ref_id']]['path_ids'][] = (string)$element[0]['ref_id']; + $single_object['references'][(string)$reference[0]['ref_id']]['path_types'][] = (string)$element[0]['type']; + } + } + if ($single_object['ref_id']) { + $objects[$single_object['ref_id']] = $single_object; + } elseif (!$condition_field) { + $objects[] = $single_object; + } + } + } + return $objects; + } + + + /** + * search objects + * + * search for ilias-objects + * @access public + * @param array types types + * @param string key keyword + * @param string combination search-combination + * @param string user_id ilias-user-id + * @return array objects + */ + function searchObjects($types, $key, $combination, $user_id = "") + { + $param = [ + 'sid' => $this->getSID(), + 'types' => $types, + 'key' => $key, + 'combination' => $combination, + 'user_id' => (int)$user_id + ]; + + $result = $this->call('searchObjects', $param); + if ($result) + { + return $this->parseIliasObject($result); + } + return false; + + } + + /** + * get object by reference + * + * gets object by reference-id + * @access public + * @param ref reference_id + * @param string user_id ilias-user-id + * @return array object + */ + function getObjectByReference($ref, $user_id = "") + { + $param = [ + 'sid' => $this->getSID(), + 'reference_id' => $ref, + 'user_id' => (int)$user_id + ]; + + $result = $this->call('getObjectByReference', $param); + if ($result != false) + { + + $objects = $this->parseIliasObject($result, 'ref_id', $ref); + return $objects[$ref]; + } + return false; + } + + /** + * get object by title + * + * gets object by title + * @access public + * @param string key keyword + * @param string type object-type + * @return array object + */ + function getObjectByTitle($key, $type = "") + { + $param = [ + 'sid' => $this->getSID(), + 'title' => $key, + 'user_id' => 0 + ]; + $result = $this->call('getObjectsByTitle', $param); + if ($result != false) + { + $objects = $this->parseIliasObject($result); + //$objects = $this->parseXML($result); + foreach($objects as $index => $object_data) + { + if (($type != "") AND ($object_data["type"] != $type)) + unset($objects[$index]); + elseif (! (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === 0)) + unset($objects[$index]); + } + reset($objects); + if (sizeof($objects) > 0) + return current($objects); + } + return false; + } + + /** + * get reference by title + * + * gets reference-id by object-title + * @access public + * @param string key keyword + * @param string type object-type + * @return string reference-id + */ + function getReferenceByTitle($key, $type = "") + { + $param = [ + 'sid' => $this->getSID(), + 'title' => $key, + 'user_id' => 0 + ]; + $result = $this->call('getObjectsByTitle', $param); + + if ($result != false) + { + $objects = $this->parseIliasObject($result); + foreach($objects as $index => $object_data) + { + if (($type != "") AND ($object_data["type"] != $type)) + unset($objects[$index]); + elseif (mb_strpos(mb_strtolower($object_data["title"]), mb_strtolower(trim($key)) ) === false) + unset($objects[$index]); + } + if (sizeof($objects) > 0) + foreach($objects as $object_data) + if (sizeof($object_data["references"]) > 0) + { + return key($object_data["references"]); + //return $object_data["references"][0]["ref_id"]; + } + } + return false; + } + + /** + * add object + * + * adds new ilias-object + * @access public + * @param array object_data object-data + * @param string ref_id reference-id + * @return string result + */ + function addObject($object_data, $ref_id) + { + $this->clearCache(); + $type = $object_data["type"]; + $title = htmlReady($object_data["title"]); + $description = htmlReady($object_data["description"]); + + $xml = " + + + + $title + + + $description + + +"; + + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'object_xml' => $xml + ]; + return $this->call('addObject', $param); + } + + /** + * delete object + * + * deletes ilias-object + * @access public + * @param string ref_id reference-id + * @return boolean result + */ + function deleteObject($reference_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'reference_id' => $reference_id + ]; + return $this->call('deleteObject', $param); + } + + /** + * add reference + * + * add a new reference to an existing ilias-object + * @access public + * @param string object_id source-object-id + * @param string ref_id target-id + * @return string created reference-id + */ + function addReference($object_id, $ref_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'source_id' => $object_id, + 'target_id' => $ref_id + ]; + return $this->call('addReference', $param); + } + + /** + * add references to desktop + * + * adds references to personal desktop + * @access public + * @param string object_id source-object-id + * @param string ref_id target-id + * @return string created reference-id + */ + function addDesktopItems($user_id, $ref_ids) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id, + 'reference_ids' => $ref_ids + ]; + return $this->call('addDesktopItems', $param); + } + + /** + * get tree childs + * + * gets child-objects of the given tree node + * @access public + * @param string ref_id reference-id + * @param array types show only childs with these types + * @param string user_id user-id for permissions + * @return array objects + */ + function getTreeChilds($ref_id, $types = "", $user_id = "") + { + if ($types == "") + $types = []; + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'types' => $types, + 'user_id' => (int)$user_id + ]; + + $result = $this->call('getTreeChilds', $param); + $tree_childs = []; + if ($result != false) { + $tree_childs = $this->parseIliasObject($result, 'parent_id', $ref_id); + } + return $tree_childs; + } + +///////////////////////// +// RBAC-FUNCTIONS // +/////////////////////// + /** + * get operation + * + * gets all ilias operations + * @access public + * @return array operations + */ + function getOperations() + { + $param = [ + 'sid' => $this->getSID() + ]; + $result = $this->call('getOperations', $param); + if (is_array($result)) + foreach ($result as $operation_set) + $operations[$operation_set["operation"]] = $operation_set["ops_id"]; + return $operations; + } + + /** + * get object tree operations + * + * gets permissions for object at given tree-node + * @access public + * @param string ref_id reference-id + * @param string user_id user-id for permissions + * @return array operation-ids + */ + function getObjectTreeOperations($ref_id, $user_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'user_id' => $user_id + ]; + $result = $this->call('getObjectTreeOperations', $param); + if ($result != false) + { + $ops_ids = []; + foreach ($result as $operation_set) + $ops_ids[] = $operation_set["ops_id"]; + return $ops_ids; + } + return false; + } + + /** + * get user roles + * + * gets user roles + * @access public + * @param string user_id user-id + * @return array role-ids + */ + function getUserRoles($user_id) + { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id + ]; + $result = $this->call('getUserRoles', $param); + if ($result != false) + { + // TODO: change to simple xml + $objects = $this->parseXML($result); + $roles = []; + foreach ($objects as $count => $role) + $roles[$count] = $role["obj_id"]; + return $roles; + } + return false; + } + + /** + * get local roles + * + * gets local roles for given object + * @access public + * @param string course_id object-id + * @return array role-objects + */ + function getLocalRoles($course_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $course_id + ]; + $result = $this->call('getLocalRoles', $param); + if ($result != false) + { + // TODO: change to simple xml + $objects = $this->parseXML($result); + return $objects; + } + return false; + } + + /** + * add role + * + * adds a new role + * @access public + * @param array role_data data for role-object + * @param string ref_id reference-id + * @return string role-id + */ + function addRole($role_data, $ref_id) + { + $this->clearCache(); + $type = "role"; + $title = htmlReady($role_data["title"]); + $description = htmlReady($role_data["description"]); + + $xml = " + + + + $title + + + $description + + +"; + + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'obj_xml' => $xml + ]; + $result = $this->call('addRole', $param); + if (is_array($result)) + return current($result); + else + return false; + } + + /** + * add role from tremplate + * + * adds a new role and adopts properties of the given role template + * @access public + * @param array role_data data for role-object + * @param string ref_id reference-id + * @param string role_id role-template-id + * @return string role-id + */ + function addRoleFromTemplate($role_data, $ref_id, $role_id) + { + $this->clearCache(); + $type = "role"; + $title = htmlReady($role_data["title"]); + $description = htmlReady($role_data["description"]); + + $xml = " + + + + $title + + + $description + + +"; + + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'obj_xml' => $xml, + 'role_template_id' => $role_id + ]; + $result = $this->call('addRoleFromTemplate', $param); + if (is_array($result)) + return current($result); + else + return false; + } + + /** + * delete user role entry + * + * deletes a role entry from the given user + * @access public + * @param string user_id user-id + * @param string role_id role-id + * @return boolean result + */ + function deleteUserRoleEntry($user_id, $role_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id, + 'role_id' => $role_id + ]; + return $this->call('deleteUserRoleEntry', $param); + } + + /** + * add user role entry + * + * adds a role entry for the given user + * @access public + * @param string user_id user-id + * @param string role_id role-id + * @return boolean result + */ + function addUserRoleEntry($user_id, $role_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id, + 'role_id' => $role_id + ]; + return $this->call('addUserRoleEntry', $param); + } + + /** + * grant permissions + * + * grants permissions for given operations at role-id and ref-id + * @access public + * @param array operations operation-array + * @param string role_id role-id + * @param string ref_id reference-id + * @return boolean result + */ + function grantPermissions($operations, $role_id, $ref_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'role_id' => $role_id, + 'operations' => $operations, + ]; + return $this->call('grantPermissions', $param); + } + + /** + * revoke permissions + * + * revokes all permissions role-id and ref-id + * @access public + * @param string role_id role-id + * @param string ref_id reference-id + * @return boolean result + */ + function revokePermissions($role_id, $ref_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'role_id' => $role_id, + ]; + return $this->call('revokePermissions', $param); + } + +///////////////////////// +// USER-FUNCTIONS // +/////////////////////// + + /** + * lookup user + * + * gets user-id for given username + * @access public + * @param string username username + * @return string user-id + */ + function lookupUser($username) + { + $param = [ + 'sid' => $this->getSID(), + 'user_name' => $username, + ]; + return $this->call('lookupUser', $param); // returns user_id + } + + /** + * get user + * + * gets user-data for given user-id + * @access public + * @param string $user_id user-id + * @return array user-data + */ + function getUser($user_id) + { + if ($this->ilias_version < 80000) { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id + ]; + $result = $this->call('getUser', $param); // returns user data array + return $result; + } else { + $param = [ + 'sid' => $this->getSID(), + 'user_ids' => [$user_id], + 'attach_roles' => 0, + ]; + $result = $this->call('getUserXML', $param); // returns user xml data + if ($result) { + $s = simplexml_load_string($result); + $user_array = []; + + foreach ($s->User as $user) { + $id_parts = explode('usr_', $user->attributes()->Id); + if ($id_parts[1] == $user_id) { + $user_array['usr_id'] = $user_id; + $user_array['user_language'] = (string)$user->attributes()->Language; + $user_array['login'] = (string)$user->Login; + $user_array['firstname'] = (string)$user->Firstname; + $user_array['lastname'] = (string)$user->Lastname; + $user_array['title'] = (string)$user->Title; + $user_array['email'] = (string)$user->Email; + $user_array['active'] = (string)$user->Active; + $user_array['authmode'] = (string)$user->AuthMode->attributes()->type; + return $user_array; + } + } + } + return false; + } + } + + /** + * get user fullname + * + * gets user-data for given user-id + * @access public + * @param string user_id user-id + * @return string full name + */ + function getUserFullname($user_id) + { + $result = $this->getUser($user_id); + + return !empty($result) ? trim(sprintf('%s %s %s', $result['title'], $result['firstname'], $result['lastname'])) : ''; + } + + /** + * search users + * + * search for ilias users + * @access public + * @param array types types + * @param string key keyword + * @param string combination search-combination + * @param string user_id ilias-user-id + * @return array objects + */ + function searchUser($user_id) + { + if ($user_id != "") { + $param = [ + 'sid' => $this->getSID(), + 'user_ids' => [$user_id], + 'attach_roles' => 0 + ]; + $result = $this->call('getUserXML', $param); + if ($result != false) + { + // TODO: change to simple xml + $objects = $this->parseXML($result); + $all_objects = []; + foreach($objects as $count => $object_data){ + if (is_array($object_data["references"])) + { + foreach($object_data["references"] as $ref_data) + if ($ref_data["accessInfo"] == "granted" + && (count($all_objects[$object_data["obj_id"]]["operations"]) < count($ref_data["operations"]))) + { + $all_objects[$object_data["obj_id"]] = $object_data; + unset($all_objects[$object_data["obj_id"]]["references"]); + $all_objects[$object_data["obj_id"]]["ref_id"] = $ref_data["ref_id"]; + $all_objects[$object_data["obj_id"]]["accessInfo"] = $ref_data["accessInfo"]; + $all_objects[$object_data["obj_id"]]["operations"] = $ref_data["operations"]; + } + } + } + if (count($all_objects)){ + foreach($all_objects as $one_object){ + $ret[$one_object['ref_id']] = $one_object; + } + return $ret; + } + } + } + return false; + } + + /** + * add user by importUsers + * + * adds new user and sets role-id + * @access public + * @param array user_data user-data + * @param string role_id global role-id for new user + * @return string user-id + */ + function addUser($user_data, $role_id) + { + $this->clearCache(); + foreach($user_data as $key => $value) { + $user_data[$key] = htmlReady($user_data[$key]); + } + $update = $user_data["id"]; + + $usr_xml = " + + +".$user_data["login"]." +".$user_data["passwd"]." +".$user_data["firstname"]." +".$user_data["lastname"]." +".$user_data["title"]." +".$user_data["gender"]." +".$user_data["email"]." +".$user_data["street"]." +".$user_data["phone_home"].""; + if ($user_data["matriculation"] !== '') { + $usr_xml .= "".(int)$user_data["matriculation"].""; + } + $usr_xml .= " +true +".$user_data["time_limit_unlimited"]." +0 +".$user_data["approve_date"]." +".$user_data["agree_date"].""; + if (($user_data["user_skin"] != "") OR ($user_data["user_style"] != "")) { + $usr_xml .= ""; + } + $usr_xml .= " +".$user_data["external_account"]." + +"; + + $param = [ + 'sid' => $this->getSID(), + 'folder_id' => -1, + 'usr_xml' => $usr_xml, + 'conflict_rule' => 1, + 'send_account_mail' => 0 + ]; + $result = $this->call('importUsers', $param); + + $s = simplexml_load_string($result); + + if ((string)$s->rows->row->column[3] == "successful") + return (string)$s->rows->row->column[0]; + else + return false; + } + + /////////////////////////////////////////////////// + /** + * copy object + * + * copy ilias-object + * @access public + * @param string source_id reference-id + * @param string target_id reference-id + * @return string result + */ + function copyObject($source_id, $target_id) + { + $this->clearCache(); + + $xml = ""; + + $param = [ + 'sid' => $this->getSID(), + 'xml' => $xml + ]; + return $this->call('copyObject', $param); + } + + /** + * get structure + * + * returns structure for ilias content object + * @access public + * @param string ref_id reference id + * @return array result + */ + function getStructure($ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id + ]; + $result = $this->call('getStructureObjects', $param); + + $structure = []; + if ($result) { + $s = simplexml_load_string($result); + + foreach ($s->StructureObjects->StructureObject as $object) { + $structure[] = (string)$object->Title; + } + } + return $structure; + } + + /** + * get path + * + * returns repository-path to ilias-object + * @access public + * @param string source_id reference-id + * @param string target_id reference-id + * @return string result + */ + function getPath($ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id + ]; + $result = $this->call('getPathForRefId', $param); + + if ($result) { + $s = simplexml_load_string($result); + + foreach ($s->rows->row as $row) { + $path[] = (string)$row->column[2]; + } + } + + if (is_array($path)) { + return implode($this->separator_string, $path); + } else { + return false; + } + } + + /** + * + * returns repository-path to ilias-object + * + * @access public + * @param string source_id reference-id + * @param string target_id reference-id + * @return string result + */ + function getRawPath($ref_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id + ]; + $result = $this->call('getPathForRefId', $param); + + if ($result) { + $s = simplexml_load_string($result); + + foreach ($s->rows->row as $row) { + $path[] = (string)$row->column[0]; + } + } + + if (is_array($path)) { + return implode('_', $path); + } else { + return false; + } + } + + /** + * + * returns ILIAS-Server-Info + * + * @access public + * @return string result + */ + function getInstallationInfoXML() + { + $this->clearCache(); + $param = [ + ]; + $result = $this->call('getInstallationInfoXML', $param); + if ($result) { + $s = simplexml_load_string($result); + $version_info = (string)$s[0]['version']; + $version_parts = explode(' ', $version_info); + $data['version'] = $version_parts[0]; + $data['version_date'] = $version_parts[1]; + foreach($s->Clients->Client as $client) { + $data['clients'][] = (string)$client[0]['id']; + } + } + return $data; + } + ////////////////////////////////////////// + + /** + * update user + * + * update user-data + * @access public + * @param array user_data user-data + * @return string result + */ +/* function updateUser($user_data) + { + $this->clearCache(); + $param = array( + 'sid' => $this->getSID(), + 'user_data' => $user_data + ); + return $this->call('updateUser', $param); // returns boolean + } +/**/ + /** + * update password + * + * update password with given string and write it uncrypted to the ilias-database + * @access public + * @param string user_id user-id + * @param string password password + * @return string result + */ +/* function updatePassword($user_id, $password) + { + $this->clearCache(); + $param = array( + 'sid' => $this->getSID(), + 'user_id' => $user_id, + 'new_password' => $password + ); + return $this->call('updatePassword', $param); // returns boolean + } +/**/ + /** + * delete user + * + * deletes user-account + * @access public + * @param string $user_id user-id + * @return string result + */ + function deleteUser($user_id) + { + $this->clearCache(); + if ($this->ilias_version < 80000) { + $param = [ + 'sid' => $this->getSID(), + 'user_id' => $user_id + ]; + return $this->call('deleteUser', $param); // returns boolean + } else { + $user_data = $this->getUser($user_id); + if (!$user_data['login']) { + return false; + } + $usr_xml = ' + + ' . $user_data['login'] . ' + + '; + + $param = [ + 'sid' => $this->getSID(), + 'folder_id' => -1, + 'usr_xml' => $usr_xml, + 'conflict_rule' => 1, + 'send_account_mail' => 0 + ]; + $result = $this->call('importUsers', $param); + + $s = simplexml_load_string($result); + + if ((string)$s->rows->row->column[3] == "successful") { + return (string)$s->rows->row->column[0]; + } + return false; + } + } + +//////////////////////////// +// COURSE-FUNCTIONS // +////////////////////////// + + /** + * is course member + * + * checks if user is course-member + * @access public + * @param string user_id user-id + * @param string course_id course-id + * @return boolean result + */ + function isMember($user_id, $course_id) + { + $param = [ + 'sid' => $this->getSID(), + 'course_id' => $course_id, + 'user_id' => $user_id + ]; + $status = $this->call('isAssignedToCourse', $param); // returns 0 if not assigned, 1 => course admin, 2 => course member or 3 => course tutor + if ($status == 0) + return false; + else + return true; + } + + /** + * add course member + * + * adds user to course + * @access public + * @param string user_id user-id + * @param string type member-type (Admin, Tutor or Member) + * @param string course_id course-id + * @return boolean result + */ + function addMember($user_id, $type, $course_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'course_id' => $course_id, + 'user_id' => $user_id, + 'type' => $type + ]; + return $this->call('assignCourseMember', $param); + } + + /** + * add course + * + * adds course + * @access public + * @param array course_data course-data + * @param string ref_id target-id + * @return string course-id + */ + function addCourse($course_data, $ref_id) + { + $this->clearCache(); + foreach($course_data as $key => $value) { + $course_data[$key] = htmlReady($course_data[$key]); + } + + $xml = $this->getCourseXML($course_data); + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'crs_xml' => $xml + ]; + $crs_id = $this->call('addCourse', $param); + return $crs_id; + } + + /** + * add group + * + * adds group + * @access public + * @param array group_data group data + * @param string ref_id target id + * @return string group id + */ + function addGroup($group_data, $ref_id) + { + $this->clearCache(); + foreach($group_data as $key => $value) { + $group_data[$key] = htmlReady($group_data[$key]); + } + + $xml = $this->getGroupXML($group_data); + $param = [ + 'sid' => $this->getSID(), + 'target_id' => $ref_id, + 'group_xml' => $xml + ]; + $group_id = $this->call('addGroup', $param); + return $group_id; + } + + /** + * update group + * + * updates group + * @access public + * @param array group_data group data + * @param string ref_id group id + * @return string result + */ + function updateGroup($group_data, $ref_id) + { + $this->clearCache(); + foreach($group_data as $key => $value) { + $group_data[$key] = htmlReady($group_data[$key]); + } + + $xml = $this->getGroupXML($group_data); + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'xml' => $xml + ]; + $result = $this->call('updateGroup', $param); + return $result; + } + + /** + * assign group member + * + * assigns user to group + * @access public + * @param string group_id group id + * @param string user_id user id + * @param string type type + */ + function assignGroupMember($group_id, $user_id, $type = "Member") + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'group_id' => $group_id, + 'user_id' => $user_id, + 'type' => $type + ]; + return $this->call('assignGroupMember', $param); + } + + /** + * exclude group member + * + * removes user from group + * @access public + * @param string group_id group id + * @param string user_id user id + */ + function excludeGroupMember($group_id, $user_id) + { + $this->clearCache(); + $param = [ + 'sid' => $this->getSID(), + 'group_id' => $group_id, + 'user_id' => $user_id + ]; + return $this->call('excludeGroupMember', $param); + } + + /** + * get group + * + * returns group xml + * @access public + * @param string group_id group id + * @return string group xml + */ + function getGroup($group_id) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $group_id + ]; + $result = $this->call('getGroup', $param); + if ($result) { + $s = simplexml_load_string($result); + $data['title'] = (string)$s->title; + $data['members'] = []; + foreach($s->member as $member) { + $member_parts = explode('_usr_', (string)$member[0]['id']); + $data['members'][] = $member_parts[1]; + } + } + return $data; + } + + /** + * get course-xml + * + * gets course xml-object for given course-data + * @access public + * @param array course_data course-data + * @return string course-xml + */ + function getCourseXML($course_data) + { + $crs_language = $course_data["language"]; + $crs_admin_id = $course_data["admin_id"]; + $crs_title = $course_data["title"]; + $crs_desc = $course_data["description"]; + + $xml = " + + + + + + $crs_title + + + + $crs_desc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + return $xml; + } + + + /** + * get group xml + * + * gets group xml object for given group data + * @access public + * @param array group_data group data + * @return string group xml + */ + function getGroupXML($group_data) + { + $xml = ' + '.$group_data['title'].' + + + + 0 + 0 + 0 + 1 + + + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + '; + return $xml; + } + /**/ + + /** + * get courses for given user + * + * gets course xml-object for given course-data + * @access public + * @param array course_data course-data + * @return string course-xml + */ + function getCoursesForUser($user_id, $status = 1) + { + $xmlrs = ' + + + + + + + + '.$user_id.' + '.$status.' + + + '; + $param = array( + 'sid' => $this->getSID(), + 'parameters' => $xmlrs + ); + $result = $this->call('getCoursesForUser', $param); + + if ($result) { + $s = simplexml_load_string($result); + foreach ($s->rows->row as $row) { + $ref_id = (string)$row->column[0]; + $xml = $this->parseXML((string)$row->column[1]); + $course = array_pop($xml); + $ret[$ref_id] = $course['title']; + } + } + if (is_array($ret)) { + return $ret; + } else { + return false; + } + } + + /** + * check reference by title + * + * gets reference id by object id + * @access public + * @param string key keyword + * @param string type object-type + * @return string reference-id + */ + function checkReferenceById($id) + { + $param = [ + 'sid' => $this->getSID(), + 'reference_id' => $id, + 'user_id' => 0 + ]; + + $result = $this->call('getObjectByReference', $param); + if ($result != false) + { + // TODO: change to simple xml + $objects = $this->parseXML($result); + if(is_array($objects)){ + foreach($objects as $index => $object_data){ + if(is_array($object_data['references'])){ + foreach($object_data['references'] as $reference){ + if($reference['ref_id'] == $id && $reference['accessInfo'] != 'object_deleted') return $object_data['obj_id']; + } + } + } + } + } + return false; + } + + /** + * @param string $ref_id + * @param bool $sum_only + * @return array|false + * @throws Exception + */ + public function getTestResults($ref_id, $sum_only = true) + { + $param = [ + 'sid' => $this->getSID(), + 'ref_id' => $ref_id, + 'sum_only' => $sum_only + ]; + $result = $this->call('getTestResults', $param); + if ($result !== false) { + $columns = []; + $data = []; + $xml = simplexml_load_string($result); + foreach ($xml->colspecs->colspec as $colspec) { + $columns[] = (string)$colspec['name']; + } + foreach ($xml->rows->row as $row) { + $data_row = []; + $i = 0; + foreach ($row->column as $column) { + $data_row[$columns[$i++]] = (string)$column; + } + if (isset($data_row['user_id'])) { + + } + $data[] = $data_row; + } + return $data; + } + return false; + } +} diff --git a/lib/ilias_interface/IliasUser.class.php b/lib/ilias_interface/IliasUser.class.php deleted file mode 100644 index 426de80..0000000 --- a/lib/ilias_interface/IliasUser.class.php +++ /dev/null @@ -1,528 +0,0 @@ - -* @access public -* @modulegroup elearning_interface_modules -* @module ConnectedUser -* @package ELearning-Interface -*/ -class IliasUser -{ - const USER_TYPE_ORIGINAL= '1'; - const USER_TYPE_CREATED= '0'; - - public $index; - private $ilias_config; - public $version; - public $id; - public $studip_id; - public $studip_login; - public $studip_password; - public $login; - public $external_password; - public $category; - public $gender; - public $title_front; - public $title_rear; - public $title; - public $firstname; - public $lastname; - public $institution; - public $street; - public $country; - public $phone_home; - public $fax; - public $matriculation; - public $email; - public $type; - public $is_connected; - public $auth_plugin; - - /** - * constructor - * - * init class. don't call directly, class is loaded by ConnectedIlias. - * @access public - * @param string $index ILIAS installation index - */ - function __construct($index, $version, $user_id = false) - { - global $auth; - - $this->studip_id = $user_id ? $user_id : $GLOBALS['user']->id; - $this->auth_plugin = DBManager::get()->query("SELECT IFNULL(auth_plugin, 'standard') FROM auth_user_md5 WHERE user_id = '" . $this->studip_id. "'")->fetchColumn(); - $this->index = $index; - $this->version = $version; - $ilias_configs = Config::get()->ILIAS_INTERFACE_SETTINGS; - $this->ilias_config = $ilias_configs[$this->index]; - - $this->readData(); - $this->getStudipUserData(); - } - - /** - * get data - * - * gets data from database - * @access public - * @return boolean returns false, if no data was found - */ - function readData() - { - $query = "SELECT external_user_id, external_user_name, external_user_password, external_user_category, external_user_type - FROM auth_extern - WHERE studip_user_id = ? AND external_user_system_type = ? ORDER BY external_user_type DESC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->studip_id, $this->index]); - $data = $statement->fetch(PDO::FETCH_ASSOC); - - if (!$data) { - $this->id = ''; - $this->is_connected = false; - return false; - } - - $this->id = $data['external_user_id']; - $this->login = $data['external_user_name']; - $this->external_password = $data['external_user_password']; - $this->category = $data['external_user_category']; - $this->type = $data['external_user_type']; - $this->is_connected = true; - - return true; - } - - /** - * get stud.ip-user-data - * - * gets stud.ip-user-data from database - * @access public - * @return boolean returns false, if no data was found - */ - function getStudipUserData() - { - $query = "SELECT username, password, title_front, title_rear, Vorname, - Nachname, Email, privatnr, privadr, geschlecht - FROM auth_user_md5 - LEFT JOIN user_info USING (user_id) - WHERE user_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->studip_id]); - $data = $statement->fetch(PDO::FETCH_ASSOC); - - if (!$data) { - return false; - } - - $this->studip_login = $data['username']; - $this->studip_password = $data['password']; - $this->title_front = $data['title_front']; - $this->title_rear = $data['title_rear']; - $this->firstname = $data['Vorname']; - $this->lastname = $data['Nachname']; - $this->email = $data['Email']; - $this->phone_home = $data['privatnr']; - $this->street = $data['privadr']; - switch($data['geschlecht']) { - case 1: $this->gender = 'm'; break; - case 2: $this->gender = 'f'; break; - default: $this->gender = 'f'; - } - - $this->matriculation = ''; - if (array_key_exists('matriculation', $this->ilias_config) && $this->ilias_config['matriculation']) { - $this->matriculation = 0; - foreach (DataFieldEntry::getDataFieldEntries($this->studip_id, 'user') as $entry) { - if ($entry->getName() == $this->ilias_config['matriculation']) { - $this->matriculation = $entry->getDisplayValue(); - } - } - } - - if ($this->title_front != '') { - $this->title = $this->title_front; - } - if ($this->title_front != '' && $this->title_rear != '') { - $this->title .= ' '; - } - if ($this->title_rear != '') { - $this->title .= $this->title_rear; - } - return true; - } - - /** - * get array of user account data - * - * returns array of user account data - * @access public - * @return array user account data - */ - function getUserArray() - { - // data for user-account in ILIAS - $user_data['id'] = $this->id; - $user_data['login'] = $this->studip_login; - $user_data['passwd'] = md5(uniqid()); //never gets used, only to ensure correct entry in ilias db - $user_data['firstname'] = $this->firstname; - $user_data['lastname'] = $this->lastname; - $user_data['title'] = $this->title; - $user_data['gender'] = $this->gender; - $user_data['email'] = $this->email; - $user_data['street'] = $this->street; - $user_data['phone_home'] = $this->phone_home; - $user_data['matriculation'] = $this->matriculation; - $user_data['time_limit_unlimited'] = 1; - $user_data['active'] = 1; - $user_data['approve_date'] = date('Y-m-d H:i:s'); - $user_data['accepted_agreement'] = true; - $user_data['agree_date'] = date('Y-m-d H:i:s'); - $user_data['auth_mode'] = 'default'; - $user_data['external_account'] = ''; - return $user_data; - } - - /** - * get id - * - * returns id - * @access public - * @return string id - */ - function getId() - { - return $this->id; - } - - /** - * set id - * - * returns id - * @return string id - */ - public function setId($ilias_user_id) - { - return $this->id = $ilias_user_id; - } - - /** - * get stud.ip user-id - * - * returns id - * @access public - * @return string stud.ip user-id - */ - function getStudipId() - { - return $this->studip_id; - } - - /** - * get username - * - * returns username - * @access public - * @return string username - */ - function getUsername() - { - return $this->login; - } - - /** - * set username - * - * sets username - * @access public - * @param string $user_login username - */ - function setUsername($user_login) - { - $this->login = $user_login; - } - - /** - * get password - * - * returns password - * @access public - * @return string password - */ - function getPassword() - { - return $this->external_password; - } - - /** - * set password - * - * sets password - * @access public - * @param string $user_password password - */ - function setPassword($user_password) - { - $this->external_password = $user_password; - } - - /** - * get user category - * - * returns id - * @access public - * @return string id - */ - function getCategory() - { - return $this->category; - } - - /** - * set user category - * - * sets user category - * @access public - * @param string $user_category category - */ - function setCategory($user_category) - { - $this->category = $user_category; - } - - /** - * get gender - * - * returns gender-setting - * @access public - * @return string gender-setting - */ - function getGender() - { - return $this->gender; - } - - /** - * set gender - * - * sets gender - * @access public - * @param string $user_gender gender-setting - */ - function setGender($user_gender) - { - $this->gender = $user_gender; - } - - /** - * get full name - * - * returns full name - * @access public - * @return string name - */ - function getName() - { - if ($this->title != "") - return $this->title . ' ' . $this->firstname . ' ' . $this->lastname; - else - return $this->firstname . ' ' . $this->lastname; - } - - /** - * get firstname - * - * returns firstname - * @access public - * @return string firstname - */ - function getFirstname() - { - return $this->firstname; - } - - /** - * set firstname - * - * sets firstname - * @access public - * @param string $user_firstname firstname - */ - function setFirstname($user_firstname) - { - $this->firstname = $user_firstname; - } - - /** - * get lastname - * - * returns lastname - * @access public - * @return string lastname - */ - function getLastname() - { - return $this->lastname; - } - - /** - * set lastname - * - * sets lastname - * @access public - * @param string $user_lastname lastname - */ - function setLastname($user_lastname) - { - $this->lastname = $user_lastname; - } - - /** - * get email-adress - * - * returns email-adress - * @access public - * @return string email-adress - */ - function getEmail() - { - return $this->email; - } - - /** - * set email-adress - * - * sets email-adress - * @access public - * @param string $user_email email-adress - */ - function setEmail($user_email) - { - $this->email = $user_email; - } - - /** - * get user-type - * - * returns user-type - * @access public - * @return string user-type - */ - function getUserType() - { - return $this->type; - } - - /** - * set user-type - * - * sets user-type - * @access public - * @param string $user_type user-type - */ - function setUserType($user_type) - { - $this->type = $user_type; - } - - /** - * save connection for user-account - * - * saves user-connection to database and sets type for actual user - * @access public - * @param string $user_type user-type - */ - function setConnection($user_type) - { - $this->setUserType($user_type); - - $query = "INSERT INTO auth_extern (studip_user_id, external_user_id, external_user_name, - external_user_password, external_user_category, - external_user_system_type, external_user_type) - VALUES (?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY - UPDATE external_user_name = VALUES(external_user_name), - external_user_password = VALUES(external_user_password), - external_user_category = VALUES(external_user_category), - external_user_id = VALUES(external_user_id)"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - (string)$this->studip_id, - (string)$this->id, - (string)$this->login, - (string)$this->external_password, - (string)$this->category, - (string)$this->index, - (int)$this->type, - ]); - - $this->is_connected = true; - $this->readData(); - } - - /** - * remove connection for user-account - * - * deletes user-connection from database (only for manually connected user) - * @access public - */ - function unsetConnection($ignore_usertype = false) - { - if (!$ignore_usertype && ($this->getUserType() != self::USER_TYPE_ORIGINAL)) { - return; - } - - $query = "DELETE FROM auth_extern WHERE studip_user_id = ? AND external_user_system_type = ? AND external_user_type = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - (string)$this->studip_id, - (string)$this->index, - (int)$this->type - ]); - - $this->is_connected = false; - $this->readData(); - } - - /** - * get connection-status - * - * returns true, if there is a connected user - * @access public - * @return boolean connection-status - */ - function isConnected() - { - return $this->is_connected; - } - - /** - * get authentication token - * - * generates authentication token and updates auth_extern - * @access public - */ - function getToken() - { - $token = md5(uniqid("iliastoken538")); - $query = "UPDATE `auth_extern` SET `external_user_token` = ?, `external_user_token_valid_until` = ? - WHERE `auth_extern`.`studip_user_id` = ? AND `auth_extern`.`external_user_system_type` = ? AND `auth_extern`.`external_user_type` = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $token, - time() + 600, - (string)$this->studip_id, - (string)$this->index, - (int)$this->type - ]); - return $token; - } -} -?> diff --git a/lib/ilias_interface/IliasUser.php b/lib/ilias_interface/IliasUser.php new file mode 100644 index 0000000..426de80 --- /dev/null +++ b/lib/ilias_interface/IliasUser.php @@ -0,0 +1,528 @@ + +* @access public +* @modulegroup elearning_interface_modules +* @module ConnectedUser +* @package ELearning-Interface +*/ +class IliasUser +{ + const USER_TYPE_ORIGINAL= '1'; + const USER_TYPE_CREATED= '0'; + + public $index; + private $ilias_config; + public $version; + public $id; + public $studip_id; + public $studip_login; + public $studip_password; + public $login; + public $external_password; + public $category; + public $gender; + public $title_front; + public $title_rear; + public $title; + public $firstname; + public $lastname; + public $institution; + public $street; + public $country; + public $phone_home; + public $fax; + public $matriculation; + public $email; + public $type; + public $is_connected; + public $auth_plugin; + + /** + * constructor + * + * init class. don't call directly, class is loaded by ConnectedIlias. + * @access public + * @param string $index ILIAS installation index + */ + function __construct($index, $version, $user_id = false) + { + global $auth; + + $this->studip_id = $user_id ? $user_id : $GLOBALS['user']->id; + $this->auth_plugin = DBManager::get()->query("SELECT IFNULL(auth_plugin, 'standard') FROM auth_user_md5 WHERE user_id = '" . $this->studip_id. "'")->fetchColumn(); + $this->index = $index; + $this->version = $version; + $ilias_configs = Config::get()->ILIAS_INTERFACE_SETTINGS; + $this->ilias_config = $ilias_configs[$this->index]; + + $this->readData(); + $this->getStudipUserData(); + } + + /** + * get data + * + * gets data from database + * @access public + * @return boolean returns false, if no data was found + */ + function readData() + { + $query = "SELECT external_user_id, external_user_name, external_user_password, external_user_category, external_user_type + FROM auth_extern + WHERE studip_user_id = ? AND external_user_system_type = ? ORDER BY external_user_type DESC"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->studip_id, $this->index]); + $data = $statement->fetch(PDO::FETCH_ASSOC); + + if (!$data) { + $this->id = ''; + $this->is_connected = false; + return false; + } + + $this->id = $data['external_user_id']; + $this->login = $data['external_user_name']; + $this->external_password = $data['external_user_password']; + $this->category = $data['external_user_category']; + $this->type = $data['external_user_type']; + $this->is_connected = true; + + return true; + } + + /** + * get stud.ip-user-data + * + * gets stud.ip-user-data from database + * @access public + * @return boolean returns false, if no data was found + */ + function getStudipUserData() + { + $query = "SELECT username, password, title_front, title_rear, Vorname, + Nachname, Email, privatnr, privadr, geschlecht + FROM auth_user_md5 + LEFT JOIN user_info USING (user_id) + WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->studip_id]); + $data = $statement->fetch(PDO::FETCH_ASSOC); + + if (!$data) { + return false; + } + + $this->studip_login = $data['username']; + $this->studip_password = $data['password']; + $this->title_front = $data['title_front']; + $this->title_rear = $data['title_rear']; + $this->firstname = $data['Vorname']; + $this->lastname = $data['Nachname']; + $this->email = $data['Email']; + $this->phone_home = $data['privatnr']; + $this->street = $data['privadr']; + switch($data['geschlecht']) { + case 1: $this->gender = 'm'; break; + case 2: $this->gender = 'f'; break; + default: $this->gender = 'f'; + } + + $this->matriculation = ''; + if (array_key_exists('matriculation', $this->ilias_config) && $this->ilias_config['matriculation']) { + $this->matriculation = 0; + foreach (DataFieldEntry::getDataFieldEntries($this->studip_id, 'user') as $entry) { + if ($entry->getName() == $this->ilias_config['matriculation']) { + $this->matriculation = $entry->getDisplayValue(); + } + } + } + + if ($this->title_front != '') { + $this->title = $this->title_front; + } + if ($this->title_front != '' && $this->title_rear != '') { + $this->title .= ' '; + } + if ($this->title_rear != '') { + $this->title .= $this->title_rear; + } + return true; + } + + /** + * get array of user account data + * + * returns array of user account data + * @access public + * @return array user account data + */ + function getUserArray() + { + // data for user-account in ILIAS + $user_data['id'] = $this->id; + $user_data['login'] = $this->studip_login; + $user_data['passwd'] = md5(uniqid()); //never gets used, only to ensure correct entry in ilias db + $user_data['firstname'] = $this->firstname; + $user_data['lastname'] = $this->lastname; + $user_data['title'] = $this->title; + $user_data['gender'] = $this->gender; + $user_data['email'] = $this->email; + $user_data['street'] = $this->street; + $user_data['phone_home'] = $this->phone_home; + $user_data['matriculation'] = $this->matriculation; + $user_data['time_limit_unlimited'] = 1; + $user_data['active'] = 1; + $user_data['approve_date'] = date('Y-m-d H:i:s'); + $user_data['accepted_agreement'] = true; + $user_data['agree_date'] = date('Y-m-d H:i:s'); + $user_data['auth_mode'] = 'default'; + $user_data['external_account'] = ''; + return $user_data; + } + + /** + * get id + * + * returns id + * @access public + * @return string id + */ + function getId() + { + return $this->id; + } + + /** + * set id + * + * returns id + * @return string id + */ + public function setId($ilias_user_id) + { + return $this->id = $ilias_user_id; + } + + /** + * get stud.ip user-id + * + * returns id + * @access public + * @return string stud.ip user-id + */ + function getStudipId() + { + return $this->studip_id; + } + + /** + * get username + * + * returns username + * @access public + * @return string username + */ + function getUsername() + { + return $this->login; + } + + /** + * set username + * + * sets username + * @access public + * @param string $user_login username + */ + function setUsername($user_login) + { + $this->login = $user_login; + } + + /** + * get password + * + * returns password + * @access public + * @return string password + */ + function getPassword() + { + return $this->external_password; + } + + /** + * set password + * + * sets password + * @access public + * @param string $user_password password + */ + function setPassword($user_password) + { + $this->external_password = $user_password; + } + + /** + * get user category + * + * returns id + * @access public + * @return string id + */ + function getCategory() + { + return $this->category; + } + + /** + * set user category + * + * sets user category + * @access public + * @param string $user_category category + */ + function setCategory($user_category) + { + $this->category = $user_category; + } + + /** + * get gender + * + * returns gender-setting + * @access public + * @return string gender-setting + */ + function getGender() + { + return $this->gender; + } + + /** + * set gender + * + * sets gender + * @access public + * @param string $user_gender gender-setting + */ + function setGender($user_gender) + { + $this->gender = $user_gender; + } + + /** + * get full name + * + * returns full name + * @access public + * @return string name + */ + function getName() + { + if ($this->title != "") + return $this->title . ' ' . $this->firstname . ' ' . $this->lastname; + else + return $this->firstname . ' ' . $this->lastname; + } + + /** + * get firstname + * + * returns firstname + * @access public + * @return string firstname + */ + function getFirstname() + { + return $this->firstname; + } + + /** + * set firstname + * + * sets firstname + * @access public + * @param string $user_firstname firstname + */ + function setFirstname($user_firstname) + { + $this->firstname = $user_firstname; + } + + /** + * get lastname + * + * returns lastname + * @access public + * @return string lastname + */ + function getLastname() + { + return $this->lastname; + } + + /** + * set lastname + * + * sets lastname + * @access public + * @param string $user_lastname lastname + */ + function setLastname($user_lastname) + { + $this->lastname = $user_lastname; + } + + /** + * get email-adress + * + * returns email-adress + * @access public + * @return string email-adress + */ + function getEmail() + { + return $this->email; + } + + /** + * set email-adress + * + * sets email-adress + * @access public + * @param string $user_email email-adress + */ + function setEmail($user_email) + { + $this->email = $user_email; + } + + /** + * get user-type + * + * returns user-type + * @access public + * @return string user-type + */ + function getUserType() + { + return $this->type; + } + + /** + * set user-type + * + * sets user-type + * @access public + * @param string $user_type user-type + */ + function setUserType($user_type) + { + $this->type = $user_type; + } + + /** + * save connection for user-account + * + * saves user-connection to database and sets type for actual user + * @access public + * @param string $user_type user-type + */ + function setConnection($user_type) + { + $this->setUserType($user_type); + + $query = "INSERT INTO auth_extern (studip_user_id, external_user_id, external_user_name, + external_user_password, external_user_category, + external_user_system_type, external_user_type) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY + UPDATE external_user_name = VALUES(external_user_name), + external_user_password = VALUES(external_user_password), + external_user_category = VALUES(external_user_category), + external_user_id = VALUES(external_user_id)"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + (string)$this->studip_id, + (string)$this->id, + (string)$this->login, + (string)$this->external_password, + (string)$this->category, + (string)$this->index, + (int)$this->type, + ]); + + $this->is_connected = true; + $this->readData(); + } + + /** + * remove connection for user-account + * + * deletes user-connection from database (only for manually connected user) + * @access public + */ + function unsetConnection($ignore_usertype = false) + { + if (!$ignore_usertype && ($this->getUserType() != self::USER_TYPE_ORIGINAL)) { + return; + } + + $query = "DELETE FROM auth_extern WHERE studip_user_id = ? AND external_user_system_type = ? AND external_user_type = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + (string)$this->studip_id, + (string)$this->index, + (int)$this->type + ]); + + $this->is_connected = false; + $this->readData(); + } + + /** + * get connection-status + * + * returns true, if there is a connected user + * @access public + * @return boolean connection-status + */ + function isConnected() + { + return $this->is_connected; + } + + /** + * get authentication token + * + * generates authentication token and updates auth_extern + * @access public + */ + function getToken() + { + $token = md5(uniqid("iliastoken538")); + $query = "UPDATE `auth_extern` SET `external_user_token` = ?, `external_user_token_valid_until` = ? + WHERE `auth_extern`.`studip_user_id` = ? AND `auth_extern`.`external_user_system_type` = ? AND `auth_extern`.`external_user_type` = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $token, + time() + 600, + (string)$this->studip_id, + (string)$this->index, + (int)$this->type + ]); + return $token; + } +} +?> diff --git a/lib/models/AdmissionApplication.class.php b/lib/models/AdmissionApplication.class.php deleted file mode 100644 index 02514ec..0000000 --- a/lib/models/AdmissionApplication.class.php +++ /dev/null @@ -1,296 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $user_id database column - * @property string $seminar_id database column - * @property string $status database column - * @property int $mkdate database column - * @property int|null $chdate database column - * @property int|null $position database column - * @property string $comment database column - * @property string $visible database column - * @property User $user belongs_to User - * @property Course $course belongs_to Course - * @property mixed $vorname additional field - * @property mixed $nachname additional field - * @property mixed $username additional field - * @property mixed $email additional field - * @property mixed $title_front additional field - * @property mixed $title_rear additional field - * @property mixed $course_name additional field - */ -class AdmissionApplication extends SimpleORMap implements PrivacyObject -{ - protected static function configure($config = []) - { - $config['db_table'] = 'admission_seminar_user'; - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - ]; - $config['belongs_to']['course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'seminar_id', - ]; - $config['additional_fields']['vorname'] = ['user', 'vorname']; - $config['additional_fields']['nachname'] = ['user', 'nachname']; - $config['additional_fields']['username'] = ['user', 'username']; - $config['additional_fields']['email'] = ['user', 'email']; - $config['additional_fields']['title_front'] = ['user', 'title_front']; - $config['additional_fields']['title_rear'] = ['user', 'title_rear']; - $config['additional_fields']['course_name'] = []; - parent::configure($config); - } - - public static function findByCourse($course_id) - { - $db = DBManager::get(); - return $db->fetchAll("SELECT admission_seminar_user.*, aum.vorname,aum.nachname,aum.email, - aum.username,ui.title_front,ui.title_rear - FROM admission_seminar_user - LEFT JOIN auth_user_md5 aum USING (user_id) - LEFT JOIN user_info ui USING (user_id) - WHERE seminar_id = ? ORDER BY position", - [$course_id], - __CLASS__ . '::buildExisting'); - } - - public static function findByUser($user_id) - { - $db = DBManager::get(); - return $db->fetchAll("SELECT admission_seminar_user.*, seminare.Name as course_name - FROM admission_seminar_user - LEFT JOIN seminare USING (seminar_id) - WHERE user_id = ? ORDER BY seminare.Name", - [$user_id], - __CLASS__ . '::buildExisting'); - } - - public function getUserFullname($format = 'full') - { - return User::build(array_merge(['motto' => ''], $this->toArray('vorname nachname username title_front title_rear')))->getFullName($format); - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findByUser($storage->user_id); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Wartelisten'), 'admission_seminar_user', $field_data); - } - } - } - - /** - * @param string $course_id - * @param string $sort_status - * @param string $order_by - * @return array - */ - public static function getAdmissionMembers(string $course_id, string $sort_status = 'autor', string $order_by = 'nachname asc'): array - { - [$order, $asc] = explode(' ', $order_by); - if ($order === 'nachname') { - $order_by = "nachname {$asc},vorname {$asc}"; - } - - $cs = CourseSet::getSetForCourse($course_id); - $claiming = []; - if (is_object($cs) && !$cs->hasAlgorithmRun()) { - foreach (AdmissionPriority::getPrioritiesByCourse($cs->getId(), $course_id) as $user_id => $p) { - $user = User::find($user_id); - $data = $user->toArray('user_id username vorname nachname email'); - $data['fullname'] = $user->getFullName('full_rev'); - $data['position'] = $cs->hasAdmissionRule('LimitedAdmission') ? $p : '-'; - $data['visible'] = 'unknown'; - $data['status'] = 'claiming'; - $claiming[] = $data; - } - } - - $query = "SELECT asu.user_id, username, Vorname, Nachname, Email, status, - position, asu.mkdate, asu.visible, asu.comment, - {$GLOBALS['_fullname_sql']['full_rev']} AS fullname - FROM admission_seminar_user AS asu - JOIN auth_user_md5 USING (user_id) - JOIN user_info USING (user_id) - WHERE seminar_id = ? - ORDER BY position, Nachname"; - $st = DBManager::get()->prepare($query); - $st->execute([$course_id]); - $application_members = SimpleCollection::createFromArray(array_merge($claiming, $st->fetchAll(PDO::FETCH_ASSOC))); - $filtered_members = []; - foreach (['awaiting', 'accepted', 'claiming'] as $status) { - $filtered_members[$status] = $application_members->findBy('status', $status); - if ($status === $sort_status) { - $filtered_members[$status]->orderBy($order_by, $order !== 'nachname' ? SORT_NUMERIC : SORT_LOCALE_STRING); - } - } - return $filtered_members; - } - - /** - * returns the position for a user on a waiting list - * - * if the user is not found false is returned, return true if the user is found but - * no position is available - * - * @param string $user_id user_id - * @param string $seminar_id seminar_id - * @return bool position in waiting list or false if not found - * - */ - public static function checkMemberPosition(string $user_id, string $seminar_id): bool - { - $position = DBManager::get()->fetchColumn("SELECT IFNULL(position, 'na') - FROM admission_seminar_user - WHERE user_id = ? AND seminar_id = ? AND status = 'awaiting'", - [$user_id, $seminar_id] - ); - - return $position === 'na'; - } - - /** - * @param string $seminar_id - * @param string $send_message - * @return void - * @throws NotificationVetoException - */ - public static function addMembers(string $seminar_id, bool $send_message = true): void - { - $messaging = new messaging; - - //Daten holen / Abfrage ob ueberhaupt begrenzt - $seminar = Seminar::GetInstance($seminar_id, true); - - if($seminar->isAdmissionEnabled()){ - $sem_preliminary = ($seminar->admission_prelim == 1); - $cs = $seminar->getCourseSet(); - //Veranstaltung einfach auffuellen (nach Lostermin und Ende der Kontingentierung) - if (!$seminar->admission_disable_waitlist_move && $cs->hasAlgorithmRun()) { - $count = (int)$seminar->getFreeAdmissionSeats(); - $memberships = self::findBySQL( - "seminar_id = ? AND status = 'awaiting' ORDER BY position LIMIT {$count}", - [$seminar_id] - ); - $log_message = 'Wurde automatisch aus der Warteliste in die Veranstaltung eingetragen.'; - foreach ($memberships as $membership) { - if (!$sem_preliminary) { - $affected = CourseMember::insertCourseMember($seminar_id, $membership->user_id, 'autor', false, false, $log_message); - } else { - $membership->status = 'accepted'; - $affected = $membership->store(); - StudipLog::log('SEM_USER_ADD', $seminar->getId(), $membership->user_id,'accepted', $log_message); - } - if ($affected) { - //User benachrichtigen - if ($send_message) { - setTempLanguage($membership->user_id); - if (!$sem_preliminary) { - $message = sprintf (_('Sie sind in die Veranstaltung **%s (%s)** eingetragen worden, da für Sie ein Platz frei geworden ist. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen.'), $seminar->getName(), $seminar->getFormattedTurnus(true)); - } else { - $message = sprintf (_('Sie haben den Status vorläufig akzeptiert in der Veranstaltung **%s (%s)** erhalten, da für Sie ein Platz frei geworden ist.'), $seminar->getName(), $seminar->getFormattedTurnus(true)); - } - $subject = sprintf(_("Teilnahme an der Veranstaltung %s"), $seminar->getName()); - restoreLanguage(); - - $messaging->insert_message($message, $membership->username, '____%system%____', false, false, '1', false, $subject, true); - } - } - } - //Warteposition der restlichen User neu eintragen - AdmissionApplication::renumberAdmission($seminar_id, FALSE); - } - $seminar->restore(); - } - } - - /** - * Renumber admissions - * @param string $seminar_id - * @param bool $send_message - * @return void - */ - public static function renumberAdmission (string $seminar_id, bool $send_message = true): void - { - $messaging = new messaging; - $seminar = Seminar::GetInstance($seminar_id); - if ($seminar->isAdmissionEnabled()) { - $admission_users = self::findBySQL( - "seminar_id = ? AND status = 'awaiting' ORDER BY position", - [$seminar->id] - ); - $position = 1; - foreach ($admission_users as $admission) { - $admission->position = $position; - if ($admission->store() && Config::get()->NOTIFY_ON_WAITLIST_ADVANCE && $send_message) { - $username = $admission->user->username; - setTempLanguage($admission->user_id); - $message = sprintf(_('Sie sind auf der Warteliste der Veranstaltung **%s (%s)** hochgestuft worden. Sie stehen zur Zeit auf Position %s.'), - $seminar->name, - $seminar->getFormattedTurnus(), - $position); - $subject = sprintf(_('Ihre Position auf der Warteliste der Veranstaltung %s wurde verändert'), $seminar->name); - restoreLanguage(); - - $messaging->insert_message($message, $username, '____%system%____', FALSE, FALSE, '1', FALSE, $subject); - } - $position += 1; - } - } - } - - - /** - * Prepare data for member export - * @return array - */ - public function getExportData(): array - { - $user = $this->user; - $studycourse = []; - $user->studycourses->map(function($sc) use (&$studycourse) { - $studycourse[]= $sc->studycourse->name . ',' . $sc->degree->name . ',' . $sc->semester; - }); - return [ - 'status' => $this->status, - 'salutation' => $user->salutation, - 'Titel' => $user->title_front, - 'Vorname' => $this->vorname, - 'Nachname' => $this->nachname, - 'Titel2' => $user->title_rear, - 'username' => $this->username, - 'privadr' => $user->privadr, - 'privatnr' => $user->privatnr, - 'Email' => $this->email, - 'Anmeldedatum' => date('d.m.Y H:i:s', $this->mkdate), - 'Matrikelnummer' => $user->matriculation_number, - 'studiengaenge' => implode(';', $studycourse), - 'position' => $this->position, - ]; - } -} diff --git a/lib/models/AdmissionApplication.php b/lib/models/AdmissionApplication.php new file mode 100644 index 0000000..02514ec --- /dev/null +++ b/lib/models/AdmissionApplication.php @@ -0,0 +1,296 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $user_id database column + * @property string $seminar_id database column + * @property string $status database column + * @property int $mkdate database column + * @property int|null $chdate database column + * @property int|null $position database column + * @property string $comment database column + * @property string $visible database column + * @property User $user belongs_to User + * @property Course $course belongs_to Course + * @property mixed $vorname additional field + * @property mixed $nachname additional field + * @property mixed $username additional field + * @property mixed $email additional field + * @property mixed $title_front additional field + * @property mixed $title_rear additional field + * @property mixed $course_name additional field + */ +class AdmissionApplication extends SimpleORMap implements PrivacyObject +{ + protected static function configure($config = []) + { + $config['db_table'] = 'admission_seminar_user'; + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + ]; + $config['belongs_to']['course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'seminar_id', + ]; + $config['additional_fields']['vorname'] = ['user', 'vorname']; + $config['additional_fields']['nachname'] = ['user', 'nachname']; + $config['additional_fields']['username'] = ['user', 'username']; + $config['additional_fields']['email'] = ['user', 'email']; + $config['additional_fields']['title_front'] = ['user', 'title_front']; + $config['additional_fields']['title_rear'] = ['user', 'title_rear']; + $config['additional_fields']['course_name'] = []; + parent::configure($config); + } + + public static function findByCourse($course_id) + { + $db = DBManager::get(); + return $db->fetchAll("SELECT admission_seminar_user.*, aum.vorname,aum.nachname,aum.email, + aum.username,ui.title_front,ui.title_rear + FROM admission_seminar_user + LEFT JOIN auth_user_md5 aum USING (user_id) + LEFT JOIN user_info ui USING (user_id) + WHERE seminar_id = ? ORDER BY position", + [$course_id], + __CLASS__ . '::buildExisting'); + } + + public static function findByUser($user_id) + { + $db = DBManager::get(); + return $db->fetchAll("SELECT admission_seminar_user.*, seminare.Name as course_name + FROM admission_seminar_user + LEFT JOIN seminare USING (seminar_id) + WHERE user_id = ? ORDER BY seminare.Name", + [$user_id], + __CLASS__ . '::buildExisting'); + } + + public function getUserFullname($format = 'full') + { + return User::build(array_merge(['motto' => ''], $this->toArray('vorname nachname username title_front title_rear')))->getFullName($format); + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findByUser($storage->user_id); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Wartelisten'), 'admission_seminar_user', $field_data); + } + } + } + + /** + * @param string $course_id + * @param string $sort_status + * @param string $order_by + * @return array + */ + public static function getAdmissionMembers(string $course_id, string $sort_status = 'autor', string $order_by = 'nachname asc'): array + { + [$order, $asc] = explode(' ', $order_by); + if ($order === 'nachname') { + $order_by = "nachname {$asc},vorname {$asc}"; + } + + $cs = CourseSet::getSetForCourse($course_id); + $claiming = []; + if (is_object($cs) && !$cs->hasAlgorithmRun()) { + foreach (AdmissionPriority::getPrioritiesByCourse($cs->getId(), $course_id) as $user_id => $p) { + $user = User::find($user_id); + $data = $user->toArray('user_id username vorname nachname email'); + $data['fullname'] = $user->getFullName('full_rev'); + $data['position'] = $cs->hasAdmissionRule('LimitedAdmission') ? $p : '-'; + $data['visible'] = 'unknown'; + $data['status'] = 'claiming'; + $claiming[] = $data; + } + } + + $query = "SELECT asu.user_id, username, Vorname, Nachname, Email, status, + position, asu.mkdate, asu.visible, asu.comment, + {$GLOBALS['_fullname_sql']['full_rev']} AS fullname + FROM admission_seminar_user AS asu + JOIN auth_user_md5 USING (user_id) + JOIN user_info USING (user_id) + WHERE seminar_id = ? + ORDER BY position, Nachname"; + $st = DBManager::get()->prepare($query); + $st->execute([$course_id]); + $application_members = SimpleCollection::createFromArray(array_merge($claiming, $st->fetchAll(PDO::FETCH_ASSOC))); + $filtered_members = []; + foreach (['awaiting', 'accepted', 'claiming'] as $status) { + $filtered_members[$status] = $application_members->findBy('status', $status); + if ($status === $sort_status) { + $filtered_members[$status]->orderBy($order_by, $order !== 'nachname' ? SORT_NUMERIC : SORT_LOCALE_STRING); + } + } + return $filtered_members; + } + + /** + * returns the position for a user on a waiting list + * + * if the user is not found false is returned, return true if the user is found but + * no position is available + * + * @param string $user_id user_id + * @param string $seminar_id seminar_id + * @return bool position in waiting list or false if not found + * + */ + public static function checkMemberPosition(string $user_id, string $seminar_id): bool + { + $position = DBManager::get()->fetchColumn("SELECT IFNULL(position, 'na') + FROM admission_seminar_user + WHERE user_id = ? AND seminar_id = ? AND status = 'awaiting'", + [$user_id, $seminar_id] + ); + + return $position === 'na'; + } + + /** + * @param string $seminar_id + * @param string $send_message + * @return void + * @throws NotificationVetoException + */ + public static function addMembers(string $seminar_id, bool $send_message = true): void + { + $messaging = new messaging; + + //Daten holen / Abfrage ob ueberhaupt begrenzt + $seminar = Seminar::GetInstance($seminar_id, true); + + if($seminar->isAdmissionEnabled()){ + $sem_preliminary = ($seminar->admission_prelim == 1); + $cs = $seminar->getCourseSet(); + //Veranstaltung einfach auffuellen (nach Lostermin und Ende der Kontingentierung) + if (!$seminar->admission_disable_waitlist_move && $cs->hasAlgorithmRun()) { + $count = (int)$seminar->getFreeAdmissionSeats(); + $memberships = self::findBySQL( + "seminar_id = ? AND status = 'awaiting' ORDER BY position LIMIT {$count}", + [$seminar_id] + ); + $log_message = 'Wurde automatisch aus der Warteliste in die Veranstaltung eingetragen.'; + foreach ($memberships as $membership) { + if (!$sem_preliminary) { + $affected = CourseMember::insertCourseMember($seminar_id, $membership->user_id, 'autor', false, false, $log_message); + } else { + $membership->status = 'accepted'; + $affected = $membership->store(); + StudipLog::log('SEM_USER_ADD', $seminar->getId(), $membership->user_id,'accepted', $log_message); + } + if ($affected) { + //User benachrichtigen + if ($send_message) { + setTempLanguage($membership->user_id); + if (!$sem_preliminary) { + $message = sprintf (_('Sie sind in die Veranstaltung **%s (%s)** eingetragen worden, da für Sie ein Platz frei geworden ist. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen.'), $seminar->getName(), $seminar->getFormattedTurnus(true)); + } else { + $message = sprintf (_('Sie haben den Status vorläufig akzeptiert in der Veranstaltung **%s (%s)** erhalten, da für Sie ein Platz frei geworden ist.'), $seminar->getName(), $seminar->getFormattedTurnus(true)); + } + $subject = sprintf(_("Teilnahme an der Veranstaltung %s"), $seminar->getName()); + restoreLanguage(); + + $messaging->insert_message($message, $membership->username, '____%system%____', false, false, '1', false, $subject, true); + } + } + } + //Warteposition der restlichen User neu eintragen + AdmissionApplication::renumberAdmission($seminar_id, FALSE); + } + $seminar->restore(); + } + } + + /** + * Renumber admissions + * @param string $seminar_id + * @param bool $send_message + * @return void + */ + public static function renumberAdmission (string $seminar_id, bool $send_message = true): void + { + $messaging = new messaging; + $seminar = Seminar::GetInstance($seminar_id); + if ($seminar->isAdmissionEnabled()) { + $admission_users = self::findBySQL( + "seminar_id = ? AND status = 'awaiting' ORDER BY position", + [$seminar->id] + ); + $position = 1; + foreach ($admission_users as $admission) { + $admission->position = $position; + if ($admission->store() && Config::get()->NOTIFY_ON_WAITLIST_ADVANCE && $send_message) { + $username = $admission->user->username; + setTempLanguage($admission->user_id); + $message = sprintf(_('Sie sind auf der Warteliste der Veranstaltung **%s (%s)** hochgestuft worden. Sie stehen zur Zeit auf Position %s.'), + $seminar->name, + $seminar->getFormattedTurnus(), + $position); + $subject = sprintf(_('Ihre Position auf der Warteliste der Veranstaltung %s wurde verändert'), $seminar->name); + restoreLanguage(); + + $messaging->insert_message($message, $username, '____%system%____', FALSE, FALSE, '1', FALSE, $subject); + } + $position += 1; + } + } + } + + + /** + * Prepare data for member export + * @return array + */ + public function getExportData(): array + { + $user = $this->user; + $studycourse = []; + $user->studycourses->map(function($sc) use (&$studycourse) { + $studycourse[]= $sc->studycourse->name . ',' . $sc->degree->name . ',' . $sc->semester; + }); + return [ + 'status' => $this->status, + 'salutation' => $user->salutation, + 'Titel' => $user->title_front, + 'Vorname' => $this->vorname, + 'Nachname' => $this->nachname, + 'Titel2' => $user->title_rear, + 'username' => $this->username, + 'privadr' => $user->privadr, + 'privatnr' => $user->privatnr, + 'Email' => $this->email, + 'Anmeldedatum' => date('d.m.Y H:i:s', $this->mkdate), + 'Matrikelnummer' => $user->matriculation_number, + 'studiengaenge' => implode(';', $studycourse), + 'position' => $this->position, + ]; + } +} diff --git a/lib/models/ArchivedCourse.class.php b/lib/models/ArchivedCourse.class.php deleted file mode 100644 index 81879af..0000000 --- a/lib/models/ArchivedCourse.class.php +++ /dev/null @@ -1,105 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for seminar_id - * @property string $seminar_id database column - * @property string $name database column - * @property string $untertitel database column - * @property string $beschreibung database column - * @property int $start_time database column - * @property string $semester database column - * @property string $heimat_inst_id database column - * @property string $institute database column - * @property string $dozenten database column - * @property string $fakultaet database column - * @property string $dump database column - * @property string $archiv_file_id database column - * @property string $archiv_protected_file_id database column - * @property int $mkdate database column - * @property string $forumdump database column - * @property string|null $wikidump database column - * @property string $studienbereiche database column - * @property string $veranstaltungsnummer database column - * @property SimpleORMapCollection|ArchivedCourseMember[] $members has_many ArchivedCourseMember - * @property Institute $home_institut belongs_to Institute - */ - -class ArchivedCourse extends SimpleORMap implements PrivacyObject -{ - protected static function configure($config = []) - { - $config['db_table'] = 'archiv'; - - $config['has_many']['members'] = [ - 'class_name' => ArchivedCourseMember::class, - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['belongs_to']['home_institut'] = [ - 'class_name' => Institute::class, - 'foreign_key' => 'heimat_inst_id', - ]; - - $config['registered_callbacks']['after_delete'][] = 'deleteFiles'; - - parent::configure($config); - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findThru($storage->user_id, [ - 'thru_table' => 'archiv_user', - 'thru_key' => 'user_id', - 'thru_assoc_key' => 'Seminar_id', - 'assoc_foreign_key' => 'Seminar_id', - ]); - if ($sorm) { - $limit = 'seminar_id name untertitel beschreibung start_time ' - . 'semester heimat_inst_id institute dozenten fakultaet ' - . 'archiv_file_id archiv_protected_file_id mkdate ' - . 'studienbereiche VeranstaltungsNummer'; - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray($limit); - } - if ($field_data) { - $storage->addTabularData(_('archivierte Seminare'), 'archiv', $field_data); - } - } - } - - /** - * delete data files belonging to this archived course - * - * @return int number of deleted files - */ - public function deleteFiles() - { - $ok = 0; - if ($this->archiv_file_id) { - $ok += unlink($GLOBALS['ARCHIV_PATH'] . '/' . basename($this->archiv_file_id)); - } - if ($this->archiv_protected_file_id) { - $ok += unlink($GLOBALS['ARCHIV_PATH'] . '/' . basename($this->archiv_protected_file_id)); - } - return $ok; - } -} diff --git a/lib/models/ArchivedCourse.php b/lib/models/ArchivedCourse.php new file mode 100644 index 0000000..81879af --- /dev/null +++ b/lib/models/ArchivedCourse.php @@ -0,0 +1,105 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for seminar_id + * @property string $seminar_id database column + * @property string $name database column + * @property string $untertitel database column + * @property string $beschreibung database column + * @property int $start_time database column + * @property string $semester database column + * @property string $heimat_inst_id database column + * @property string $institute database column + * @property string $dozenten database column + * @property string $fakultaet database column + * @property string $dump database column + * @property string $archiv_file_id database column + * @property string $archiv_protected_file_id database column + * @property int $mkdate database column + * @property string $forumdump database column + * @property string|null $wikidump database column + * @property string $studienbereiche database column + * @property string $veranstaltungsnummer database column + * @property SimpleORMapCollection|ArchivedCourseMember[] $members has_many ArchivedCourseMember + * @property Institute $home_institut belongs_to Institute + */ + +class ArchivedCourse extends SimpleORMap implements PrivacyObject +{ + protected static function configure($config = []) + { + $config['db_table'] = 'archiv'; + + $config['has_many']['members'] = [ + 'class_name' => ArchivedCourseMember::class, + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['belongs_to']['home_institut'] = [ + 'class_name' => Institute::class, + 'foreign_key' => 'heimat_inst_id', + ]; + + $config['registered_callbacks']['after_delete'][] = 'deleteFiles'; + + parent::configure($config); + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findThru($storage->user_id, [ + 'thru_table' => 'archiv_user', + 'thru_key' => 'user_id', + 'thru_assoc_key' => 'Seminar_id', + 'assoc_foreign_key' => 'Seminar_id', + ]); + if ($sorm) { + $limit = 'seminar_id name untertitel beschreibung start_time ' + . 'semester heimat_inst_id institute dozenten fakultaet ' + . 'archiv_file_id archiv_protected_file_id mkdate ' + . 'studienbereiche VeranstaltungsNummer'; + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray($limit); + } + if ($field_data) { + $storage->addTabularData(_('archivierte Seminare'), 'archiv', $field_data); + } + } + } + + /** + * delete data files belonging to this archived course + * + * @return int number of deleted files + */ + public function deleteFiles() + { + $ok = 0; + if ($this->archiv_file_id) { + $ok += unlink($GLOBALS['ARCHIV_PATH'] . '/' . basename($this->archiv_file_id)); + } + if ($this->archiv_protected_file_id) { + $ok += unlink($GLOBALS['ARCHIV_PATH'] . '/' . basename($this->archiv_protected_file_id)); + } + return $ok; + } +} diff --git a/lib/models/ArchivedCourseMember.class.php b/lib/models/ArchivedCourseMember.class.php deleted file mode 100644 index 1febd35..0000000 --- a/lib/models/ArchivedCourseMember.class.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $seminar_id database column - * @property string $user_id database column - * @property string $status database column - * @property User $user belongs_to User - * @property ArchivedCourse $course belongs_to ArchivedCourse - */ -class ArchivedCourseMember extends SimpleORMap implements PrivacyObject -{ - protected static function configure($config = []) - { - $config['db_table'] = 'archiv_user'; - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - ]; - $config['belongs_to']['course'] = [ - 'class_name' => ArchivedCourse::class, - 'foreign_key' => 'seminar_id', - ]; - parent::configure($config); - } - - public static function findByCourse($course_id) - { - return self::findBySeminar_id($course_id); - } - - public static function findByUser($user_id) - { - return self::findByUser_id($user_id); - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL("user_id = ?", [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('archivierte SeminareUser'), 'archiv_user', $field_data); - } - } - } -} diff --git a/lib/models/ArchivedCourseMember.php b/lib/models/ArchivedCourseMember.php new file mode 100644 index 0000000..1febd35 --- /dev/null +++ b/lib/models/ArchivedCourseMember.php @@ -0,0 +1,68 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $seminar_id database column + * @property string $user_id database column + * @property string $status database column + * @property User $user belongs_to User + * @property ArchivedCourse $course belongs_to ArchivedCourse + */ +class ArchivedCourseMember extends SimpleORMap implements PrivacyObject +{ + protected static function configure($config = []) + { + $config['db_table'] = 'archiv_user'; + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + ]; + $config['belongs_to']['course'] = [ + 'class_name' => ArchivedCourse::class, + 'foreign_key' => 'seminar_id', + ]; + parent::configure($config); + } + + public static function findByCourse($course_id) + { + return self::findBySeminar_id($course_id); + } + + public static function findByUser($user_id) + { + return self::findByUser_id($user_id); + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL("user_id = ?", [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('archivierte SeminareUser'), 'archiv_user', $field_data); + } + } + } +} diff --git a/lib/models/AuthUserMd5.class.php b/lib/models/AuthUserMd5.class.php deleted file mode 100644 index 48e27f8..0000000 --- a/lib/models/AuthUserMd5.class.php +++ /dev/null @@ -1,42 +0,0 @@ - - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for user_id - * @property string $user_id database column - * @property string $username database column - * @property string $password database column - * @property string $perms database column - * @property string $vorname database column - * @property string $nachname database column - * @property string $email database column - * @property string $validation_key database column - * @property string|null $auth_plugin database column - * @property int $locked database column - * @property string|null $lock_comment database column - * @property string|null $locked_by database column - * @property string $visible database column - */ - -class AuthUserMd5 extends SimpleORMap -{ - /** - * @param array $config - */ - protected static function configure($config = []) - { - $config['db_table'] = 'auth_user_md5'; - parent::configure($config); - } -} diff --git a/lib/models/AuthUserMd5.php b/lib/models/AuthUserMd5.php new file mode 100644 index 0000000..48e27f8 --- /dev/null +++ b/lib/models/AuthUserMd5.php @@ -0,0 +1,42 @@ + + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for user_id + * @property string $user_id database column + * @property string $username database column + * @property string $password database column + * @property string $perms database column + * @property string $vorname database column + * @property string $nachname database column + * @property string $email database column + * @property string $validation_key database column + * @property string|null $auth_plugin database column + * @property int $locked database column + * @property string|null $lock_comment database column + * @property string|null $locked_by database column + * @property string $visible database column + */ + +class AuthUserMd5 extends SimpleORMap +{ + /** + * @param array $config + */ + protected static function configure($config = []) + { + $config['db_table'] = 'auth_user_md5'; + parent::configure($config); + } +} diff --git a/lib/models/Banner.class.php b/lib/models/Banner.class.php deleted file mode 100644 index a418760..0000000 --- a/lib/models/Banner.class.php +++ /dev/null @@ -1,232 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @package admin - * @since 2.4 - * - * @property string $id alias column for ad_id - * @property string $ad_id database column - * @property string $banner_path database column - * @property string|null $description database column - * @property string|null $alttext database column - * @property string $target_type database column - * @property string $target database column - * @property int $startdate database column - * @property int $enddate database column - * @property int $priority database column - * @property int $views database column - * @property int $clicks database column - * @property int $mkdate database column - * @property int $chdate database column - * @property SimpleORMapCollection|BannerRoles[] $banner_roles has_many BannerRoles - */ - -class Banner extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'banner_ads'; - - $config['has_many']['banner_roles'] = [ - 'class_name' => BannerRoles::class, - 'assoc_foreign_key' => 'ad_id', - 'on_delete' => 'delete' - ]; - - parent::configure($config); - } - - /** - * Returns a random banner - */ - public static function getRandomBanner() - { - $query = "SELECT ad_id, priority, startdate, enddate - FROM banner_ads - WHERE priority > 0 - AND (startdate = 0 OR startdate < UNIX_TIMESTAMP()) - AND (enddate = 0 OR enddate > UNIX_TIMESTAMP())"; - $statement = DBManager::get()->query($query); - - // array that contains banner ids and an offset - // offsets start with 0 and increase by pow(2, priority) - // a random number between 0 and sum(pow(2,priorities)) is - // drawn and the banner with the highest offset smaller than - // this number is chosen - - $banners = []; - $sum = 0; - // collect banners to consider, build banners array - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - if (BannerRoles::checkUserAccess($row['ad_id'])) { - $sum += pow(2, $row['priority']); - $banners[] = [ - 'ad_id' => $row['ad_id'], - 'offset' => $sum - ]; - } - } - - // draw random number and select banner - $x = mt_rand(0, $sum); - $ad_id = false; - foreach ($banners as $i) { - if ($i['offset'] >= $x) { - $ad_id = $i['ad_id']; - break; - } - } - - return new Banner($ad_id); - } - - /** - * Get all banners - * - * @return array() list of banners - */ - public static function getAllBanners() - { - $query = "SELECT ad_id FROM banner_ads ORDER BY priority DESC"; - $statement = DBManager::get()->query($query); - $ids = $statement->fetchAll(PDO::FETCH_COLUMN); - - $banners = []; - foreach ($ids as $id) { - $banners[$id] = new Banner($id); - } - return $banners; - } - - /** - * delete entry from database - * the object is cleared and turned to new state - * @return boolean - */ - public function delete() - { - if (!$this->isNew()) { - // Remove banner file - unlink($GLOBALS['DYNAMIC_CONTENT_PATH'] . '/banner/' . $this->banner_path); - } - return parent::delete(); - } - - /* - * Check the priority for a banner - * @param Int $prio priority (1-10) - * @return - */ - public function getViewProbability() - { - static $computed = false, $sum = null; - - if ($this->priority == 0) { - return '--'; - } - - if ($computed === false) { - $sum = DBManager::get()->query("SELECT SUM(POW(2, priority)) FROM banner_ads WHERE priority > 0") - ->fetchColumn(); - $computed = true; - } -// return '1/' . (1 / (pow(2, $prio) / $sum)); - return number_format(100 / (1 / (pow(2, $this->priority) / $sum)), 2, ',', '.') . '%'; - } - - - /** - * Returns the appropriate link for this banner. - * - * @return string - */ - public function getLink($internal = false) - { - if ($this->isNew()) { - return ''; - } - - if ($internal) { - return URLHelper::getLink('dispatch.php/banner/click/' . $this->ad_id); - } - - if ($this->target_type === 'url') { - return $this->target; - } - if ($this->target_type === 'seminar') { - return URLHelper::getLink('dispatch.php/course/details/', ['sem_id' => $this->target]); - } - if ($this->target_type === 'user') { - return URLHelper::getLink('dispatch.php/profile', ['username' => $this->target]); - } - if ($this->target_type === 'inst') { - return URLHelper::getLink( - 'dispatch.php/institute/overview', - ['auswahl' => $this->target] - ); - } - - return ''; - } - - /** - * Returns the img-tag for this banner - * - * @return string - */ - public function toImg($attributes = []) - { - $attr = [ - 'src' => $GLOBALS['DYNAMIC_CONTENT_URL'] . '/banner/' . $this->banner_path, - 'border' => '0', - ]; - if ($this->alttext) { - $attr['title'] = $attr['alt'] = $this->alttext; - } - $attr = array_merge($attr, $attributes); - - $attr_string = ''; - foreach ($attr as $key => $value) { - $attr_string .= sprintf(' %s="%s"', $key, htmlReady($value)); - } - - return ''; - } - - /** - * Returns the complete html (link + img) for this banner - * - * @return string - */ - public function toHTML($internal = true) - { - if ($this->isNew()) { - return ''; - } - - if ($this->target_type === 'url') { - $template = '%s'; - } elseif ($this->target_type === 'none') { - $template = '%2$s'; - } else { - $template = '%s'; - } - - $link = sprintf($template, $this->getLink($internal), $this->toImg()); - - $this->views += 1; - $this->store(); - - return sprintf('
%s
', $link); - } -} diff --git a/lib/models/Banner.php b/lib/models/Banner.php new file mode 100644 index 0000000..a418760 --- /dev/null +++ b/lib/models/Banner.php @@ -0,0 +1,232 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package admin + * @since 2.4 + * + * @property string $id alias column for ad_id + * @property string $ad_id database column + * @property string $banner_path database column + * @property string|null $description database column + * @property string|null $alttext database column + * @property string $target_type database column + * @property string $target database column + * @property int $startdate database column + * @property int $enddate database column + * @property int $priority database column + * @property int $views database column + * @property int $clicks database column + * @property int $mkdate database column + * @property int $chdate database column + * @property SimpleORMapCollection|BannerRoles[] $banner_roles has_many BannerRoles + */ + +class Banner extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'banner_ads'; + + $config['has_many']['banner_roles'] = [ + 'class_name' => BannerRoles::class, + 'assoc_foreign_key' => 'ad_id', + 'on_delete' => 'delete' + ]; + + parent::configure($config); + } + + /** + * Returns a random banner + */ + public static function getRandomBanner() + { + $query = "SELECT ad_id, priority, startdate, enddate + FROM banner_ads + WHERE priority > 0 + AND (startdate = 0 OR startdate < UNIX_TIMESTAMP()) + AND (enddate = 0 OR enddate > UNIX_TIMESTAMP())"; + $statement = DBManager::get()->query($query); + + // array that contains banner ids and an offset + // offsets start with 0 and increase by pow(2, priority) + // a random number between 0 and sum(pow(2,priorities)) is + // drawn and the banner with the highest offset smaller than + // this number is chosen + + $banners = []; + $sum = 0; + // collect banners to consider, build banners array + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + if (BannerRoles::checkUserAccess($row['ad_id'])) { + $sum += pow(2, $row['priority']); + $banners[] = [ + 'ad_id' => $row['ad_id'], + 'offset' => $sum + ]; + } + } + + // draw random number and select banner + $x = mt_rand(0, $sum); + $ad_id = false; + foreach ($banners as $i) { + if ($i['offset'] >= $x) { + $ad_id = $i['ad_id']; + break; + } + } + + return new Banner($ad_id); + } + + /** + * Get all banners + * + * @return array() list of banners + */ + public static function getAllBanners() + { + $query = "SELECT ad_id FROM banner_ads ORDER BY priority DESC"; + $statement = DBManager::get()->query($query); + $ids = $statement->fetchAll(PDO::FETCH_COLUMN); + + $banners = []; + foreach ($ids as $id) { + $banners[$id] = new Banner($id); + } + return $banners; + } + + /** + * delete entry from database + * the object is cleared and turned to new state + * @return boolean + */ + public function delete() + { + if (!$this->isNew()) { + // Remove banner file + unlink($GLOBALS['DYNAMIC_CONTENT_PATH'] . '/banner/' . $this->banner_path); + } + return parent::delete(); + } + + /* + * Check the priority for a banner + * @param Int $prio priority (1-10) + * @return + */ + public function getViewProbability() + { + static $computed = false, $sum = null; + + if ($this->priority == 0) { + return '--'; + } + + if ($computed === false) { + $sum = DBManager::get()->query("SELECT SUM(POW(2, priority)) FROM banner_ads WHERE priority > 0") + ->fetchColumn(); + $computed = true; + } +// return '1/' . (1 / (pow(2, $prio) / $sum)); + return number_format(100 / (1 / (pow(2, $this->priority) / $sum)), 2, ',', '.') . '%'; + } + + + /** + * Returns the appropriate link for this banner. + * + * @return string + */ + public function getLink($internal = false) + { + if ($this->isNew()) { + return ''; + } + + if ($internal) { + return URLHelper::getLink('dispatch.php/banner/click/' . $this->ad_id); + } + + if ($this->target_type === 'url') { + return $this->target; + } + if ($this->target_type === 'seminar') { + return URLHelper::getLink('dispatch.php/course/details/', ['sem_id' => $this->target]); + } + if ($this->target_type === 'user') { + return URLHelper::getLink('dispatch.php/profile', ['username' => $this->target]); + } + if ($this->target_type === 'inst') { + return URLHelper::getLink( + 'dispatch.php/institute/overview', + ['auswahl' => $this->target] + ); + } + + return ''; + } + + /** + * Returns the img-tag for this banner + * + * @return string + */ + public function toImg($attributes = []) + { + $attr = [ + 'src' => $GLOBALS['DYNAMIC_CONTENT_URL'] . '/banner/' . $this->banner_path, + 'border' => '0', + ]; + if ($this->alttext) { + $attr['title'] = $attr['alt'] = $this->alttext; + } + $attr = array_merge($attr, $attributes); + + $attr_string = ''; + foreach ($attr as $key => $value) { + $attr_string .= sprintf(' %s="%s"', $key, htmlReady($value)); + } + + return ''; + } + + /** + * Returns the complete html (link + img) for this banner + * + * @return string + */ + public function toHTML($internal = true) + { + if ($this->isNew()) { + return ''; + } + + if ($this->target_type === 'url') { + $template = '%s'; + } elseif ($this->target_type === 'none') { + $template = '%2$s'; + } else { + $template = '%s'; + } + + $link = sprintf($template, $this->getLink($internal), $this->toImg()); + + $this->views += 1; + $this->store(); + + return sprintf('
%s
', $link); + } +} diff --git a/lib/models/BannerRoles.class.php b/lib/models/BannerRoles.class.php deleted file mode 100644 index d1c2986..0000000 --- a/lib/models/BannerRoles.class.php +++ /dev/null @@ -1,122 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @package admin - * @since 5.1 - * - * @property array $id alias for pk - * @property string $ad_id database column - * @property int $roleid database column - * @property Banner $banner_ads belongs_to Banner - */ - -class BannerRoles extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'banner_roles'; - - $config['belongs_to']['banner_ads'] = [ - 'class_name' => Banner::class, - 'foreign_key' => 'ad_id', - ]; - - parent::configure($config); - } - - public static function checkUserAccess($ad_id, $user_id = null) - { - $user_id = $user_id ?: $GLOBALS['user']->id; - $banner_roles = self::getRoles($ad_id); - $user_roles = RolePersistence::getAssignedRoles($user_id, true); - - if (!$banner_roles) { - return true; - } - - foreach ($banner_roles as $banner_role) { - foreach ($user_roles as $user_role) { - if ($banner_role->getRoleid() === $user_role->getRoleid()) { - return true; - } - } - } - - return false; - } - - public static function getRoles($ad_id) - { - $banner_roles = self::findByad_id($ad_id); - $banner_role_ids = []; - foreach ($banner_roles as $banner_role) { - $banner_role_ids[] = $banner_role['roleid']; - } - - $only_system_roles = Config::get()->BANNER_ONLY_SYSTEM_ROLES; - $roles = RolePersistence::getAllRoles(); - $re = []; - foreach ($banner_role_ids as $role_id) { - if (isset($roles[$role_id])) { - if ($only_system_roles && !$roles[$role_id]->getSystemtype()) { - continue; - } - $re[$role_id] = $roles[$role_id]; - } - } - return $re; - } - - public static function getAvailableRoles($ad_id = null) - { - $banner_role_ids = []; - if ($ad_id) { - $banner_roles = self::findByad_id($ad_id); - foreach ($banner_roles as $banner_role) { - $banner_role_ids[] = $banner_role['roleid']; - } - } - - $only_system_roles = Config::get()->BANNER_ONLY_SYSTEM_ROLES; - $roles = RolePersistence::getAllRoles(); - $rolesStats = RolePersistence::getStatistics(); - $re = []; - foreach ($roles as $key => $role) { - if (!in_array($key, $banner_role_ids)) { - if ($only_system_roles && !$role->getSystemtype()) { - continue; - } - if ($rolesStats[$role->getRoleid()]['explicit'] + $rolesStats[$role->getRoleid()]['implicit'] == 0) { - continue; - } - $re[$key] = $role; - } - } - - return $re; - } - - public static function update($ad_id, $new_roles) - { - self::deleteByAd_id($ad_id); - - if ($new_roles) { - foreach ($new_roles as $new_role) { - $BannerRoles = new self(); - $BannerRoles->ad_id = $ad_id; - $BannerRoles->roleid = $new_role; - $BannerRoles->store(); - } - } - } -} diff --git a/lib/models/BannerRoles.php b/lib/models/BannerRoles.php new file mode 100644 index 0000000..d1c2986 --- /dev/null +++ b/lib/models/BannerRoles.php @@ -0,0 +1,122 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package admin + * @since 5.1 + * + * @property array $id alias for pk + * @property string $ad_id database column + * @property int $roleid database column + * @property Banner $banner_ads belongs_to Banner + */ + +class BannerRoles extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'banner_roles'; + + $config['belongs_to']['banner_ads'] = [ + 'class_name' => Banner::class, + 'foreign_key' => 'ad_id', + ]; + + parent::configure($config); + } + + public static function checkUserAccess($ad_id, $user_id = null) + { + $user_id = $user_id ?: $GLOBALS['user']->id; + $banner_roles = self::getRoles($ad_id); + $user_roles = RolePersistence::getAssignedRoles($user_id, true); + + if (!$banner_roles) { + return true; + } + + foreach ($banner_roles as $banner_role) { + foreach ($user_roles as $user_role) { + if ($banner_role->getRoleid() === $user_role->getRoleid()) { + return true; + } + } + } + + return false; + } + + public static function getRoles($ad_id) + { + $banner_roles = self::findByad_id($ad_id); + $banner_role_ids = []; + foreach ($banner_roles as $banner_role) { + $banner_role_ids[] = $banner_role['roleid']; + } + + $only_system_roles = Config::get()->BANNER_ONLY_SYSTEM_ROLES; + $roles = RolePersistence::getAllRoles(); + $re = []; + foreach ($banner_role_ids as $role_id) { + if (isset($roles[$role_id])) { + if ($only_system_roles && !$roles[$role_id]->getSystemtype()) { + continue; + } + $re[$role_id] = $roles[$role_id]; + } + } + return $re; + } + + public static function getAvailableRoles($ad_id = null) + { + $banner_role_ids = []; + if ($ad_id) { + $banner_roles = self::findByad_id($ad_id); + foreach ($banner_roles as $banner_role) { + $banner_role_ids[] = $banner_role['roleid']; + } + } + + $only_system_roles = Config::get()->BANNER_ONLY_SYSTEM_ROLES; + $roles = RolePersistence::getAllRoles(); + $rolesStats = RolePersistence::getStatistics(); + $re = []; + foreach ($roles as $key => $role) { + if (!in_array($key, $banner_role_ids)) { + if ($only_system_roles && !$role->getSystemtype()) { + continue; + } + if ($rolesStats[$role->getRoleid()]['explicit'] + $rolesStats[$role->getRoleid()]['implicit'] == 0) { + continue; + } + $re[$key] = $role; + } + } + + return $re; + } + + public static function update($ad_id, $new_roles) + { + self::deleteByAd_id($ad_id); + + if ($new_roles) { + foreach ($new_roles as $new_role) { + $BannerRoles = new self(); + $BannerRoles->ad_id = $ad_id; + $BannerRoles->roleid = $new_role; + $BannerRoles->store(); + } + } + } +} diff --git a/lib/models/Clipboard.class.php b/lib/models/Clipboard.class.php deleted file mode 100644 index cd8fe89..0000000 --- a/lib/models/Clipboard.class.php +++ /dev/null @@ -1,335 +0,0 @@ - - * @copyright 2018-2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.5 - * - * The "allowed_item_class" column containts the StudipItem class - * name where items have to be children of in order to be used - * with the clipboard. - * This attribute defaults to 'StudipItem' which means that by default - * all implementations of StudipItem can be inserted into a clipboard. - * If only items of a special implementation of StudipItem shall be - * able to be inserted into the clipboard this attribute has to be set - * to the class name of that implementation. - * - * @property int $id database column - * @property string $user_id database column - * @property string $name database column - * @property string $handler database column - * @property string $allowed_item_class database column - * @property int $mkdate database column - * @property int $chdate database column - * @property SimpleORMapCollection|ClipboardItem[] $items has_many ClipboardItem - * @property User $user belongs_to User - */ -class Clipboard extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'clipboards'; - - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - 'assoc_func' => 'find' - ]; - - $config['has_many']['items'] = [ - 'class_name' => ClipboardItem::class, - 'assoc_foreign_key' => 'clipboard_id', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - - parent::configure($config); - } - - - /** - * This class returns all clipboards for a specified user, - * optionally filtered by required item range types. - * - * @param string $user_id: The ID of the user whose clipboards - * shall be returned. - * @param array $item_range_types: The range types which at least one item - * of the clipboard must have to be included in the result set. - * - * @returns Clipboard[] An array of clipboard objects which are - * associated with the user specified by $user_id. - */ - public static function getClipboardsForUser($user_id = '', $item_range_types = []) - { - if (!$user_id) { - return []; - } - - if (is_array($item_range_types) and count($item_range_types) > 0) { - return self::findBySql( - "INNER JOIN clipboard_items - ON clipboards.id = clipboard_items.clipboard_id - WHERE - clipboards.user_id = :user_id - AND - clipboard_items.range_type IN ( :range_types ) - GROUP BY clipboards.id ORDER BY name ASC, mkdate ASC", - [ - 'user_id' => $user_id, - 'range_types' => $item_range_types - ] - ); - } else { - return self::findBySql( - 'user_id = :user_id ORDER BY name ASC, mkdate ASC', - [ - 'user_id' => $user_id - ] - ); - } - } - - - /** - * Adds an item to the clipboard by specifying the - * ID of the object that shall be added to it. - * - * @param string $range_id The ID of the object that shall be added. - * - * @throws ClipboardException If an error occurs. - * - * @returns ClipboardItem A ClipboardItem instance on success, - * false on failure. - */ - public function addItem($range_id = null, $range_type = 'StudipItem') - { - if (($range_id === null) or !is_a($range_type, 'StudipItem', true)) { - //Either the range_id is not set or the range_type does not - //name a StudipItem class. - throw new ClipboardException( - _('Die Daten zum Hinzufügen eines Eintrags sind ungültig!') - ); - } - - //Check if the item already exists: - - $item = ClipboardItem::findOneBySql( - 'clipboard_id = :clipboard_id - AND range_id = :range_id - AND range_type = :range_type', - [ - 'clipboard_id' => $this->id, - 'range_id' => $range_id, - 'range_type' => $range_type - ] - ); - if ($item) { - //Item already exists and nothing has to be modified. - return $item; - } - - //Item does not exist: create it. - $item = new ClipboardItem(); - $item->clipboard_id = $this->id; - $item->range_id = $range_id; - $item->range_type = $range_type; - if (!$item->store()) { - throw new ClipboardException( - _('Fehler beim Speichern des Eintrags!') - ); - } - return $item; - } - - - /** - * Removes an item from the clipboard by specifying the ID of the - * object that shall be removed from the pad. - * - * @param string $range_id The ID of the object that shall be removed. - * - * @returns bool True on success, false on failure. - */ - public function removeItem($range_id = null) - { - if ($range_id === null) { - //The range_id is not set. - return false; - } - - return ClipboardItem::deleteBySql( - 'clipboard_id = :clipboard_id AND range_id = :range_id', - [ - 'clipboard_id' => $this->id, - 'range_id' => $range_id - ] - ) > 0; - } - - - /** - * Formats the content of the clipboard for display. - * This method should be overloaded by derived classes - * to output the data of the clipboard with appropriate names - * and other attributes which may be needed. - * - * @param string $order_by sort column and direction - * - * @returns string[][] A two-dimensional array with strings. - * The first dimension represents the list of clipboard items. - * The second dimension represents an item and holds at least - * the following attributes of the item: id, name. - * These attributes are the keys of the second array dimension. - * The array has the following structure: - * [ - * [ - * 'id' => (id of the item) - * 'name' => (name of the item) - * ], - * [ - * … - * ] - * ] - * - * Derived classes may add further attributes to the array, - * if necessary. - */ - public function getContent($order_by = 'name asc') - { - if (!$this->items) { - return []; - } - - $content = new SimpleCollection(); - foreach ($this->items as $item) { - //Only those elements which store the IDs of objects - //from the allowed content class or its descendants - //are added to the $content array. - if (is_a($item->range_type, $this->allowed_item_class, true)) { - $content[] = [ - 'id' => $item->id, - 'range_type' => $item->range_type, - 'range_id' => $item->range_id, - 'name' => $item->__toString() - ]; - } - } - - return $content->orderBy($order_by)->getArrayCopy(); - } - - - /** - * Retrieves all range-IDs of objects that are associated - * with this clipboard. The objects can be filtered by their - * range type. - * - * @param string|string[] $range_types The class name(s) of the objects - * which shall be included in the result set. - * This parameter can be a string or an array of strings. - * - * @returns string[] An array with all range-IDs that match - * the specified range type. - */ - public function getAllRangeIds($range_types = 'StudipItem') - { - if (!$range_types) { - //If no range types are specified we cannot retrieve range-IDs: - return []; - } - - if (!is_array($range_types)) { - //Make $range_types an array: - $range_types = [$range_types]; - } - - $db = DBManager::get(); - - $stmt = $db->prepare( - "SELECT range_id FROM clipboard_items - WHERE range_type IN ( :range_types ) - AND clipboard_id = :clipboard_id;" - ); - - $stmt->execute( - [ - 'range_types' => $range_types, - 'clipboard_id' => $this->id - ] - ); - - return $stmt->fetchAll( - PDO::FETCH_COLUMN, - 0 - ); - } - - - /** - * Retrieves specific range-IDs of objects that are associated - * with this clipboard and referenced by the specified clipboard item-IDs. - * The objects can be filtered by their range type, too. - * - * @param string|string[] $range_types The class name(s) of the objects - * which shall be included in the result set. This parameter - * can be an array or a string. - * - * @param string[] $item_ids The item-IDs of the clipboard items - * whose range-IDs shall be included in the result set. - * Note that the clipboard items must be associated with this clipboard. - * - * @returns string[] An array with all range-IDs that match - * the specified range type. - */ - public function getSomeRangeIds($range_types = 'StudipItem', $item_ids = []) - { - if ((!is_array($item_ids) and !$item_ids) or !$range_types) { - //Item-IDs is either an array or empty or it isn't even an array - //or $range_types is not set. - //We cannot use it to retrieve range-IDs. - return []; - } - - $db = DBManager::get(); - - if (!is_array($range_types)) { - //Make $range_types an array: - $range_types = [$range_types]; - } - - $stmt = $db->prepare( - "SELECT range_id FROM clipboard_items - WHERE range_type IN ( :range_types ) - AND clipboard_id = :clipboard_id - AND id IN ( :item_ids );" - ); - - $stmt->execute( - [ - 'range_types' => $range_types, - 'clipboard_id' => $this->id, - 'item_ids' => $item_ids - ] - ); - - return $stmt->fetchAll( - PDO::FETCH_COLUMN, - 0 - ); - } -} diff --git a/lib/models/Clipboard.php b/lib/models/Clipboard.php new file mode 100644 index 0000000..cd8fe89 --- /dev/null +++ b/lib/models/Clipboard.php @@ -0,0 +1,335 @@ + + * @copyright 2018-2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.5 + * + * The "allowed_item_class" column containts the StudipItem class + * name where items have to be children of in order to be used + * with the clipboard. + * This attribute defaults to 'StudipItem' which means that by default + * all implementations of StudipItem can be inserted into a clipboard. + * If only items of a special implementation of StudipItem shall be + * able to be inserted into the clipboard this attribute has to be set + * to the class name of that implementation. + * + * @property int $id database column + * @property string $user_id database column + * @property string $name database column + * @property string $handler database column + * @property string $allowed_item_class database column + * @property int $mkdate database column + * @property int $chdate database column + * @property SimpleORMapCollection|ClipboardItem[] $items has_many ClipboardItem + * @property User $user belongs_to User + */ +class Clipboard extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'clipboards'; + + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + 'assoc_func' => 'find' + ]; + + $config['has_many']['items'] = [ + 'class_name' => ClipboardItem::class, + 'assoc_foreign_key' => 'clipboard_id', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + + parent::configure($config); + } + + + /** + * This class returns all clipboards for a specified user, + * optionally filtered by required item range types. + * + * @param string $user_id: The ID of the user whose clipboards + * shall be returned. + * @param array $item_range_types: The range types which at least one item + * of the clipboard must have to be included in the result set. + * + * @returns Clipboard[] An array of clipboard objects which are + * associated with the user specified by $user_id. + */ + public static function getClipboardsForUser($user_id = '', $item_range_types = []) + { + if (!$user_id) { + return []; + } + + if (is_array($item_range_types) and count($item_range_types) > 0) { + return self::findBySql( + "INNER JOIN clipboard_items + ON clipboards.id = clipboard_items.clipboard_id + WHERE + clipboards.user_id = :user_id + AND + clipboard_items.range_type IN ( :range_types ) + GROUP BY clipboards.id ORDER BY name ASC, mkdate ASC", + [ + 'user_id' => $user_id, + 'range_types' => $item_range_types + ] + ); + } else { + return self::findBySql( + 'user_id = :user_id ORDER BY name ASC, mkdate ASC', + [ + 'user_id' => $user_id + ] + ); + } + } + + + /** + * Adds an item to the clipboard by specifying the + * ID of the object that shall be added to it. + * + * @param string $range_id The ID of the object that shall be added. + * + * @throws ClipboardException If an error occurs. + * + * @returns ClipboardItem A ClipboardItem instance on success, + * false on failure. + */ + public function addItem($range_id = null, $range_type = 'StudipItem') + { + if (($range_id === null) or !is_a($range_type, 'StudipItem', true)) { + //Either the range_id is not set or the range_type does not + //name a StudipItem class. + throw new ClipboardException( + _('Die Daten zum Hinzufügen eines Eintrags sind ungültig!') + ); + } + + //Check if the item already exists: + + $item = ClipboardItem::findOneBySql( + 'clipboard_id = :clipboard_id + AND range_id = :range_id + AND range_type = :range_type', + [ + 'clipboard_id' => $this->id, + 'range_id' => $range_id, + 'range_type' => $range_type + ] + ); + if ($item) { + //Item already exists and nothing has to be modified. + return $item; + } + + //Item does not exist: create it. + $item = new ClipboardItem(); + $item->clipboard_id = $this->id; + $item->range_id = $range_id; + $item->range_type = $range_type; + if (!$item->store()) { + throw new ClipboardException( + _('Fehler beim Speichern des Eintrags!') + ); + } + return $item; + } + + + /** + * Removes an item from the clipboard by specifying the ID of the + * object that shall be removed from the pad. + * + * @param string $range_id The ID of the object that shall be removed. + * + * @returns bool True on success, false on failure. + */ + public function removeItem($range_id = null) + { + if ($range_id === null) { + //The range_id is not set. + return false; + } + + return ClipboardItem::deleteBySql( + 'clipboard_id = :clipboard_id AND range_id = :range_id', + [ + 'clipboard_id' => $this->id, + 'range_id' => $range_id + ] + ) > 0; + } + + + /** + * Formats the content of the clipboard for display. + * This method should be overloaded by derived classes + * to output the data of the clipboard with appropriate names + * and other attributes which may be needed. + * + * @param string $order_by sort column and direction + * + * @returns string[][] A two-dimensional array with strings. + * The first dimension represents the list of clipboard items. + * The second dimension represents an item and holds at least + * the following attributes of the item: id, name. + * These attributes are the keys of the second array dimension. + * The array has the following structure: + * [ + * [ + * 'id' => (id of the item) + * 'name' => (name of the item) + * ], + * [ + * … + * ] + * ] + * + * Derived classes may add further attributes to the array, + * if necessary. + */ + public function getContent($order_by = 'name asc') + { + if (!$this->items) { + return []; + } + + $content = new SimpleCollection(); + foreach ($this->items as $item) { + //Only those elements which store the IDs of objects + //from the allowed content class or its descendants + //are added to the $content array. + if (is_a($item->range_type, $this->allowed_item_class, true)) { + $content[] = [ + 'id' => $item->id, + 'range_type' => $item->range_type, + 'range_id' => $item->range_id, + 'name' => $item->__toString() + ]; + } + } + + return $content->orderBy($order_by)->getArrayCopy(); + } + + + /** + * Retrieves all range-IDs of objects that are associated + * with this clipboard. The objects can be filtered by their + * range type. + * + * @param string|string[] $range_types The class name(s) of the objects + * which shall be included in the result set. + * This parameter can be a string or an array of strings. + * + * @returns string[] An array with all range-IDs that match + * the specified range type. + */ + public function getAllRangeIds($range_types = 'StudipItem') + { + if (!$range_types) { + //If no range types are specified we cannot retrieve range-IDs: + return []; + } + + if (!is_array($range_types)) { + //Make $range_types an array: + $range_types = [$range_types]; + } + + $db = DBManager::get(); + + $stmt = $db->prepare( + "SELECT range_id FROM clipboard_items + WHERE range_type IN ( :range_types ) + AND clipboard_id = :clipboard_id;" + ); + + $stmt->execute( + [ + 'range_types' => $range_types, + 'clipboard_id' => $this->id + ] + ); + + return $stmt->fetchAll( + PDO::FETCH_COLUMN, + 0 + ); + } + + + /** + * Retrieves specific range-IDs of objects that are associated + * with this clipboard and referenced by the specified clipboard item-IDs. + * The objects can be filtered by their range type, too. + * + * @param string|string[] $range_types The class name(s) of the objects + * which shall be included in the result set. This parameter + * can be an array or a string. + * + * @param string[] $item_ids The item-IDs of the clipboard items + * whose range-IDs shall be included in the result set. + * Note that the clipboard items must be associated with this clipboard. + * + * @returns string[] An array with all range-IDs that match + * the specified range type. + */ + public function getSomeRangeIds($range_types = 'StudipItem', $item_ids = []) + { + if ((!is_array($item_ids) and !$item_ids) or !$range_types) { + //Item-IDs is either an array or empty or it isn't even an array + //or $range_types is not set. + //We cannot use it to retrieve range-IDs. + return []; + } + + $db = DBManager::get(); + + if (!is_array($range_types)) { + //Make $range_types an array: + $range_types = [$range_types]; + } + + $stmt = $db->prepare( + "SELECT range_id FROM clipboard_items + WHERE range_type IN ( :range_types ) + AND clipboard_id = :clipboard_id + AND id IN ( :item_ids );" + ); + + $stmt->execute( + [ + 'range_types' => $range_types, + 'clipboard_id' => $this->id, + 'item_ids' => $item_ids + ] + ); + + return $stmt->fetchAll( + PDO::FETCH_COLUMN, + 0 + ); + } +} diff --git a/lib/models/ClipboardItem.class.php b/lib/models/ClipboardItem.class.php deleted file mode 100644 index 888250f..0000000 --- a/lib/models/ClipboardItem.class.php +++ /dev/null @@ -1,69 +0,0 @@ - - * @copyright 2018-2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.5 - * - * @property int $id database column - * @property int $clipboard_id database column - * @property string $range_id database column - * @property string $range_type database column - * @property int $mkdate database column - * @property int $chdate database column - * @property Clipboard $clipboard belongs_to Clipboard - * - * @property-read string $name - */ -class ClipboardItem extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'clipboard_items'; - - $config['belongs_to']['clipboard'] = [ - 'class_name' => Clipboard::class, - 'foreign_key' => 'clipboard_id', - 'assoc_func' => 'find' - ]; - - $config['additional_fields']['name'] = [ - 'get' => fn(ClipboardItem $item) => $item->__toString(), - ]; - - parent::configure($config); - } - - /** - * @returns string representation of this clipboard item. - */ - public function __toString() - { - // Get the class $range_type and the object with ID $range_id, - // if $range_type is a StudipItem: - if (is_subclass_of($this->range_type, StudipItem::class)) { - $range_class_name = $this->range_type; - $object = $range_class_name::find($this->range_id); - if ($object) { - return $object->getItemName(false); - } - } - - // $range_type is not a class name of a StudipItem class - // or no object of a StudipItem class could be found: - // We cannot determine the name and must therefore use - // a generic name: - return $this->range_type . '_' . $this->range_id; - } -} diff --git a/lib/models/ClipboardItem.php b/lib/models/ClipboardItem.php new file mode 100644 index 0000000..888250f --- /dev/null +++ b/lib/models/ClipboardItem.php @@ -0,0 +1,69 @@ + + * @copyright 2018-2019 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.5 + * + * @property int $id database column + * @property int $clipboard_id database column + * @property string $range_id database column + * @property string $range_type database column + * @property int $mkdate database column + * @property int $chdate database column + * @property Clipboard $clipboard belongs_to Clipboard + * + * @property-read string $name + */ +class ClipboardItem extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'clipboard_items'; + + $config['belongs_to']['clipboard'] = [ + 'class_name' => Clipboard::class, + 'foreign_key' => 'clipboard_id', + 'assoc_func' => 'find' + ]; + + $config['additional_fields']['name'] = [ + 'get' => fn(ClipboardItem $item) => $item->__toString(), + ]; + + parent::configure($config); + } + + /** + * @returns string representation of this clipboard item. + */ + public function __toString() + { + // Get the class $range_type and the object with ID $range_id, + // if $range_type is a StudipItem: + if (is_subclass_of($this->range_type, StudipItem::class)) { + $range_class_name = $this->range_type; + $object = $range_class_name::find($this->range_id); + if ($object) { + return $object->getItemName(false); + } + } + + // $range_type is not a class name of a StudipItem class + // or no object of a StudipItem class could be found: + // We cannot determine the name and must therefore use + // a generic name: + return $this->range_type . '_' . $this->range_id; + } +} diff --git a/lib/models/ColourValue.class.php b/lib/models/ColourValue.class.php deleted file mode 100644 index 6ae05ad..0000000 --- a/lib/models/ColourValue.class.php +++ /dev/null @@ -1,104 +0,0 @@ - - * @copyright 2018-2019 - * @since 4.5 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for colour_id - * @property string $colour_id database column - * @property I18NString $description database column - * @property string $value database column - * @property int $mkdate database column - * @property int $chdate database column - */ -class ColourValue extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'colour_values'; - - $config['i18n_fields']['description'] = true; - - parent::configure($config); - } - - /** - * $colours is an array with all colour values that is filled - * when a colour is requested. - */ - protected static $colours; - - /** - * The find method is overloaded here since the table is usually very - * small and the colour values are requested often. They are stored - * in an array and served from there to save database requests. - */ - public static function find($id) - { - if (!is_array(self::$colours)) { - self::$colours = []; - //Load all colours: - $colours = self::findBySql('TRUE'); - foreach ($colours as $colour) { - self::$colours[$colour->id] = $colour; - } - } - - return self::$colours[$id]; - } - - - /** - * DEVELOPER WARNING: Do not rename this method to setValue since setValue - * is a SimpleORMap reserved method for setting attribute values - * of a SORM object! - */ - public function setColourValue($r = 0xff, $g = 0xff, $b = 0xff, $a = 0xff) - { - $value = dechex($r) . dechex($g) . dechex($b) . dechex($a); - $this->value = $value; - } - - - public function __toString() - { - $r = $this->value[0] . $this->value[1]; - $g = $this->value[2] . $this->value[3]; - $b = $this->value[4] . $this->value[5]; - - //The color values are output as '#RRGGBB'. - //This way it is compatible with the input type color. - return mb_strtolower('#' . $r . $g . $b); - } - - - public function toRGBAFunction() - { - $r = $this->value[0] . $this->value[1]; - $g = $this->value[2] . $this->value[3]; - $b = $this->value[4] . $this->value[5]; - $a = $this->value[6] . $this->value[7]; - - return sprintf( - 'rgba(%s %s %s %s)', - hexdec($r), - hexdec($g), - hexdec($b), - hexdec($a) - ); - } -} diff --git a/lib/models/ColourValue.php b/lib/models/ColourValue.php new file mode 100644 index 0000000..6ae05ad --- /dev/null +++ b/lib/models/ColourValue.php @@ -0,0 +1,104 @@ + + * @copyright 2018-2019 + * @since 4.5 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for colour_id + * @property string $colour_id database column + * @property I18NString $description database column + * @property string $value database column + * @property int $mkdate database column + * @property int $chdate database column + */ +class ColourValue extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'colour_values'; + + $config['i18n_fields']['description'] = true; + + parent::configure($config); + } + + /** + * $colours is an array with all colour values that is filled + * when a colour is requested. + */ + protected static $colours; + + /** + * The find method is overloaded here since the table is usually very + * small and the colour values are requested often. They are stored + * in an array and served from there to save database requests. + */ + public static function find($id) + { + if (!is_array(self::$colours)) { + self::$colours = []; + //Load all colours: + $colours = self::findBySql('TRUE'); + foreach ($colours as $colour) { + self::$colours[$colour->id] = $colour; + } + } + + return self::$colours[$id]; + } + + + /** + * DEVELOPER WARNING: Do not rename this method to setValue since setValue + * is a SimpleORMap reserved method for setting attribute values + * of a SORM object! + */ + public function setColourValue($r = 0xff, $g = 0xff, $b = 0xff, $a = 0xff) + { + $value = dechex($r) . dechex($g) . dechex($b) . dechex($a); + $this->value = $value; + } + + + public function __toString() + { + $r = $this->value[0] . $this->value[1]; + $g = $this->value[2] . $this->value[3]; + $b = $this->value[4] . $this->value[5]; + + //The color values are output as '#RRGGBB'. + //This way it is compatible with the input type color. + return mb_strtolower('#' . $r . $g . $b); + } + + + public function toRGBAFunction() + { + $r = $this->value[0] . $this->value[1]; + $g = $this->value[2] . $this->value[3]; + $b = $this->value[4] . $this->value[5]; + $a = $this->value[6] . $this->value[7]; + + return sprintf( + 'rgba(%s %s %s %s)', + hexdec($r), + hexdec($g), + hexdec($b), + hexdec($a) + ); + } +} diff --git a/lib/models/ConfigEntry.class.php b/lib/models/ConfigEntry.class.php deleted file mode 100644 index 5e06da8..0000000 --- a/lib/models/ConfigEntry.class.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @copyright 2010 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for field - * @property string $field database column - * @property string $value database column - * @property string $type database column - * @property string $range database column - * @property string $section database column - * @property int $mkdate database column - * @property int $chdate database column - * @property string $description database column - */ - -class ConfigEntry extends SimpleORMap -{ - /** - * Configures this model. - * - * @param array $config Configuration array - */ - protected static function configure($config = []) - { - $config['db_table'] = 'config'; - parent::configure($config); - } - - /** - * Returns whether a config value does not differ from the default entry. - * - * @param ConfigValue $value Value to check - * @return boolean - */ - public function isDefault(ConfigValue $value) - { - return $value->value == $this->value - && !$value->comment; - } -} diff --git a/lib/models/ConfigEntry.php b/lib/models/ConfigEntry.php new file mode 100644 index 0000000..5e06da8 --- /dev/null +++ b/lib/models/ConfigEntry.php @@ -0,0 +1,51 @@ + + * @copyright 2010 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for field + * @property string $field database column + * @property string $value database column + * @property string $type database column + * @property string $range database column + * @property string $section database column + * @property int $mkdate database column + * @property int $chdate database column + * @property string $description database column + */ + +class ConfigEntry extends SimpleORMap +{ + /** + * Configures this model. + * + * @param array $config Configuration array + */ + protected static function configure($config = []) + { + $config['db_table'] = 'config'; + parent::configure($config); + } + + /** + * Returns whether a config value does not differ from the default entry. + * + * @param ConfigValue $value Value to check + * @return boolean + */ + public function isDefault(ConfigValue $value) + { + return $value->value == $this->value + && !$value->comment; + } +} diff --git a/lib/models/Contact.class.php b/lib/models/Contact.class.php deleted file mode 100644 index 16bff26..0000000 --- a/lib/models/Contact.class.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @license GPL 2 or later - * - * @property array $id alias for pk - * @property string $owner_id database column - * @property string $user_id database column - * @property string $calendar_permissions database column - * An enum with the possible values "", "READ" and "WRITE". - * The empty string specifies that no calendar permissions are granted. - * @property User $owner belongs_to User - * @property User $friend belongs_to User - * @property string $mkdate database column - * @property string $chdate database column - */ -class Contact extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'contact'; - - $config['belongs_to']['owner'] = [ - 'class_name' => User::class, - 'foreign_key' => 'owner_id' - ]; - $config['belongs_to']['friend'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id' - ]; - - $config['has_many']['groups'] = [ - 'class_name' => ContactGroupItem::class, - 'assoc_func' => 'findByContact', - 'foreign_key' => function ($me) { - return [$me]; - }, - 'assoc_foreign_key' => function ($item, $params) { - //Nothing else here. But this has to be present - //so that storing a new contact works. - }, - 'on_store' => 'store', - 'on_delete' => 'delete' - ]; - - parent::configure($config); - } -} diff --git a/lib/models/Contact.php b/lib/models/Contact.php new file mode 100644 index 0000000..16bff26 --- /dev/null +++ b/lib/models/Contact.php @@ -0,0 +1,51 @@ + + * @license GPL 2 or later + * + * @property array $id alias for pk + * @property string $owner_id database column + * @property string $user_id database column + * @property string $calendar_permissions database column + * An enum with the possible values "", "READ" and "WRITE". + * The empty string specifies that no calendar permissions are granted. + * @property User $owner belongs_to User + * @property User $friend belongs_to User + * @property string $mkdate database column + * @property string $chdate database column + */ +class Contact extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'contact'; + + $config['belongs_to']['owner'] = [ + 'class_name' => User::class, + 'foreign_key' => 'owner_id' + ]; + $config['belongs_to']['friend'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id' + ]; + + $config['has_many']['groups'] = [ + 'class_name' => ContactGroupItem::class, + 'assoc_func' => 'findByContact', + 'foreign_key' => function ($me) { + return [$me]; + }, + 'assoc_foreign_key' => function ($item, $params) { + //Nothing else here. But this has to be present + //so that storing a new contact works. + }, + 'on_store' => 'store', + 'on_delete' => 'delete' + ]; + + parent::configure($config); + } +} diff --git a/lib/models/ContactGroup.class.php b/lib/models/ContactGroup.class.php deleted file mode 100644 index 0e60d3b..0000000 --- a/lib/models/ContactGroup.class.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @copyright 2023 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @package resources - * @since 5.5 - * - * @property string $id The ID of the group. - * @property string $name Name of the group. - * @property string $owner_id The ID of the owner to whom the group belongs to. - * @property string $mkdate The creation date of the group. - * @property string $chdate The modification date of the group. - * @property User $owner The owner of the group. - * @property ContactGroupItem[]|SimpleORMapCollection $items The items (users) that belong to the group. - */ -class ContactGroup extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'contact_groups'; - $config['belongs_to']['owner'] = [ - 'class_name' => User::class, - 'foreign_key' => 'owner_id' - ]; - $config['has_many']['items'] = [ - 'class_name' => ContactGroupItem::class, - 'assoc_foreign_key' => 'group_id', - 'on_store' => 'store', - 'on_delete' => 'delete' - ]; - parent::configure($config); - } -} diff --git a/lib/models/ContactGroup.php b/lib/models/ContactGroup.php new file mode 100644 index 0000000..0e60d3b --- /dev/null +++ b/lib/models/ContactGroup.php @@ -0,0 +1,44 @@ + + * @copyright 2023 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package resources + * @since 5.5 + * + * @property string $id The ID of the group. + * @property string $name Name of the group. + * @property string $owner_id The ID of the owner to whom the group belongs to. + * @property string $mkdate The creation date of the group. + * @property string $chdate The modification date of the group. + * @property User $owner The owner of the group. + * @property ContactGroupItem[]|SimpleORMapCollection $items The items (users) that belong to the group. + */ +class ContactGroup extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'contact_groups'; + $config['belongs_to']['owner'] = [ + 'class_name' => User::class, + 'foreign_key' => 'owner_id' + ]; + $config['has_many']['items'] = [ + 'class_name' => ContactGroupItem::class, + 'assoc_foreign_key' => 'group_id', + 'on_store' => 'store', + 'on_delete' => 'delete' + ]; + parent::configure($config); + } +} diff --git a/lib/models/ContactGroupItem.class.php b/lib/models/ContactGroupItem.class.php deleted file mode 100644 index 0204d0a..0000000 --- a/lib/models/ContactGroupItem.class.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @copyright 2023 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @package resources - * @since 5.5 - * - * @property string $group_id The ID of the group. - * @property string $user_id The ID of the user that is inside the group. - * @property string $mkdate The creation date of the group. - * @property string $chdate The modification date of the group. - * @property ContactGroup $contact_group The group instance for the item. - * @property User $user The user instance for the item. - */ -class ContactGroupItem extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'contact_group_items'; - $config['belongs_to']['contact_group'] = [ - 'class_name' => ContactGroup::class, - 'foreign_key' => 'group_id' - ]; - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id' - ]; - parent::configure($config); - } - - /** - * Finds and returns all group items for a contact. - * - * @param Contact $contact The contact for which to find all contact group items. - * @return ContactGroupItem[] All memberships of the contact. - */ - public static function findByContact(Contact $contact): array - { - return self::findBySQL( - 'JOIN `contact_groups` - ON (`contact_group_items`.`group_id` = `contact_groups`.`id`) - WHERE `contact_groups`.`owner_id` = :owner_id - AND `contact_group_items`.`user_id` = :user_id', - [ - 'owner_id' => $contact->owner_id, - 'user_id' => $contact->user_id - ] - ); - } -} diff --git a/lib/models/ContactGroupItem.php b/lib/models/ContactGroupItem.php new file mode 100644 index 0000000..0204d0a --- /dev/null +++ b/lib/models/ContactGroupItem.php @@ -0,0 +1,61 @@ + + * @copyright 2023 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package resources + * @since 5.5 + * + * @property string $group_id The ID of the group. + * @property string $user_id The ID of the user that is inside the group. + * @property string $mkdate The creation date of the group. + * @property string $chdate The modification date of the group. + * @property ContactGroup $contact_group The group instance for the item. + * @property User $user The user instance for the item. + */ +class ContactGroupItem extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'contact_group_items'; + $config['belongs_to']['contact_group'] = [ + 'class_name' => ContactGroup::class, + 'foreign_key' => 'group_id' + ]; + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id' + ]; + parent::configure($config); + } + + /** + * Finds and returns all group items for a contact. + * + * @param Contact $contact The contact for which to find all contact group items. + * @return ContactGroupItem[] All memberships of the contact. + */ + public static function findByContact(Contact $contact): array + { + return self::findBySQL( + 'JOIN `contact_groups` + ON (`contact_group_items`.`group_id` = `contact_groups`.`id`) + WHERE `contact_groups`.`owner_id` = :owner_id + AND `contact_group_items`.`user_id` = :user_id', + [ + 'owner_id' => $contact->owner_id, + 'user_id' => $contact->user_id + ] + ); + } +} diff --git a/lib/models/ContentTermsOfUse.class.php b/lib/models/ContentTermsOfUse.class.php deleted file mode 100644 index fb2e592..0000000 --- a/lib/models/ContentTermsOfUse.class.php +++ /dev/null @@ -1,225 +0,0 @@ - - * @copyright 2016 data-quest - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id database column - * @property I18NString $name database column - * @property int $position database column - * @property I18NString $description database column - * @property I18NString $student_description database column - * @property int $download_condition database column - * @property string $icon database column - * @property int $is_default database column - * @property int $mkdate database column - * @property int $chdate database column - */ - -class ContentTermsOfUse extends SimpleORMap -{ - const DOWNLOAD_CONDITION_NONE = 0; // no conditions (downloadable by anyone) - const DOWNLOAD_CONDITION_CLOSED_GROUPS = 1; // closed groups (e.g. courses with signup rules) - const DOWNLOAD_CONDITION_OWNER_ONLY = 2; // only for owner - - /** - * @var - */ - private static $cache = null; - - /** - * @param array $config - */ - protected static function configure($config = []) - { - $config['db_table'] = 'content_terms_of_use_entries'; - - $config['i18n_fields']['name'] = true; - $config['i18n_fields']['description'] = true; - $config['i18n_fields']['student_description'] = true; - - $config['default_values']['download_condition'] = self::DOWNLOAD_CONDITION_NONE; - $config['default_values']['icon'] = 'license'; - $config['default_values']['position'] = 0; - $config['default_values']['is_default'] = false; - - $config['registered_callbacks']['after_store'][] = 'cbCheckDefault'; - - parent::configure($config); - } - - /** - * @return ContentTermsOfUse[] - */ - public static function findAll() - { - if (self::$cache === null) { - self::$cache = new SimpleCollection(self::findBySQL('1 ORDER by position, id')); - } - return self::$cache; - } - - /** - * @param $id string - * @return ContentTermsOfUse - */ - public static function find($id) - { - return self::findAll()->findOneBy('id', $id); - } - - /** - * @param $id string - * @return ContentTermsOfUse - */ - public static function findOrBuild($id) - { - return self::find($id) ?: self::build(['id' => 'UNDEFINED', 'name' => 'unbekannt']); - } - - /** - * @return ContentTermsOfUse - */ - public static function findDefault() - { - return self::findAll()->findOneBy('is_default', 1); - } - - /** - * Returns a list of all valid conditions. - * - * @return array - */ - public static function getConditions() - { - return [ - self::DOWNLOAD_CONDITION_NONE => _('Ohne Bedingung'), - self::DOWNLOAD_CONDITION_CLOSED_GROUPS => _('Nur innerhalb geschlossener Veranstaltungen erlaubt'), - self::DOWNLOAD_CONDITION_OWNER_ONLY => _('Nur für EigentümerIn erlaubt'), - ]; - } - - /** - * Returns the textual representation of a condition. - * - * @param int $condition - * @return string - */ - public static function describeCondition($condition) - { - $conditions = self::getConditions(); - return $conditions[$condition] ?? _('Nicht definiert'); - } - - /** - * - */ - public function cbCheckDefault() - { - if ($this->is_default) { - $query = "UPDATE `content_terms_of_use_entries` - SET `is_default` = 0 - WHERE id != :id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':id', $this->id); - $statement->execute(); - } - self::$cache = null; - } - - /** - * Validates this entry - * - * @return array with error messages, if it's empty everyhting is fine - */ - public function validate() - { - $errors = []; - if ($this->isNew() && self::exists($this->id)) { - $errors[] = sprintf( - _('Es existiert bereits ein Eintrag mit der ID %s!'), - $this->id - ); - } - if (!$this->name) { - $errors[] = _('Es wurde kein Name für den Eintrag gesetzt!'); - } - return $errors; - } - - /** - * Determines if a user is permitted to download a file. - * - * Depening on the value of the download_condition attribute a decision - * is made regarding the permission of the given user to download - * a file, given by one of its associated FileRef objects. - * - * The folder condition can have the values 0, 1 and 2. - * - 0 means that there are no conditions for downloading, therefore the - * file is downloadable by anyone. - * - 1 means that the file is only downloadable inside a closed group. - * Such a group can be a course or study group with closed admission. - * In this case this method checks if the user is a member of the - * course or study group. - * - 2 means that the file is only downloadable for the owner. - * The user's ID must therefore match the user_id attribute - * of the FileRef object. - */ - public function isDownloadable($context_id, $context_type, $allow_owner = true, $user_id = null) - { - $user_id = $user_id ?: $GLOBALS['user']->id; - if ($allow_owner) { - if (in_array($context_type, ['course', 'institute']) - && Seminar_Perm::get()->have_studip_perm( - 'tutor', $context_id, $user_id - ) - ) { - return true; - } elseif ($context_type === "profile" && $context_id === $user_id) { - return true; - } - } - if ($this->download_condition == self::DOWNLOAD_CONDITION_CLOSED_GROUPS) { - - //the content is only downloadable when the user is inside a closed group - //(referenced by range_id). If download_condition is set to 2 - //the group must also have a terminated signup deadline. - if ($context_type === "course") { - //check where this range_id comes from: - $seminar = Seminar::GetInstance($context_id); - $timed_admission = $seminar->getAdmissionTimeFrame(); - - if ($seminar->admission_prelim - || $seminar->isPasswordProtected() - || $seminar->isAdmissionLocked() - || (is_array($timed_admission) && $timed_admission['end_time'] > 0 && $timed_admission['end_time'] < time()) - ) { - return true; - } - } - return false; - } - - if ($this->download_condition == self::DOWNLOAD_CONDITION_OWNER_ONLY) { - return false; - } - - return true; - } -} diff --git a/lib/models/ContentTermsOfUse.php b/lib/models/ContentTermsOfUse.php new file mode 100644 index 0000000..fb2e592 --- /dev/null +++ b/lib/models/ContentTermsOfUse.php @@ -0,0 +1,225 @@ + + * @copyright 2016 data-quest + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id database column + * @property I18NString $name database column + * @property int $position database column + * @property I18NString $description database column + * @property I18NString $student_description database column + * @property int $download_condition database column + * @property string $icon database column + * @property int $is_default database column + * @property int $mkdate database column + * @property int $chdate database column + */ + +class ContentTermsOfUse extends SimpleORMap +{ + const DOWNLOAD_CONDITION_NONE = 0; // no conditions (downloadable by anyone) + const DOWNLOAD_CONDITION_CLOSED_GROUPS = 1; // closed groups (e.g. courses with signup rules) + const DOWNLOAD_CONDITION_OWNER_ONLY = 2; // only for owner + + /** + * @var + */ + private static $cache = null; + + /** + * @param array $config + */ + protected static function configure($config = []) + { + $config['db_table'] = 'content_terms_of_use_entries'; + + $config['i18n_fields']['name'] = true; + $config['i18n_fields']['description'] = true; + $config['i18n_fields']['student_description'] = true; + + $config['default_values']['download_condition'] = self::DOWNLOAD_CONDITION_NONE; + $config['default_values']['icon'] = 'license'; + $config['default_values']['position'] = 0; + $config['default_values']['is_default'] = false; + + $config['registered_callbacks']['after_store'][] = 'cbCheckDefault'; + + parent::configure($config); + } + + /** + * @return ContentTermsOfUse[] + */ + public static function findAll() + { + if (self::$cache === null) { + self::$cache = new SimpleCollection(self::findBySQL('1 ORDER by position, id')); + } + return self::$cache; + } + + /** + * @param $id string + * @return ContentTermsOfUse + */ + public static function find($id) + { + return self::findAll()->findOneBy('id', $id); + } + + /** + * @param $id string + * @return ContentTermsOfUse + */ + public static function findOrBuild($id) + { + return self::find($id) ?: self::build(['id' => 'UNDEFINED', 'name' => 'unbekannt']); + } + + /** + * @return ContentTermsOfUse + */ + public static function findDefault() + { + return self::findAll()->findOneBy('is_default', 1); + } + + /** + * Returns a list of all valid conditions. + * + * @return array + */ + public static function getConditions() + { + return [ + self::DOWNLOAD_CONDITION_NONE => _('Ohne Bedingung'), + self::DOWNLOAD_CONDITION_CLOSED_GROUPS => _('Nur innerhalb geschlossener Veranstaltungen erlaubt'), + self::DOWNLOAD_CONDITION_OWNER_ONLY => _('Nur für EigentümerIn erlaubt'), + ]; + } + + /** + * Returns the textual representation of a condition. + * + * @param int $condition + * @return string + */ + public static function describeCondition($condition) + { + $conditions = self::getConditions(); + return $conditions[$condition] ?? _('Nicht definiert'); + } + + /** + * + */ + public function cbCheckDefault() + { + if ($this->is_default) { + $query = "UPDATE `content_terms_of_use_entries` + SET `is_default` = 0 + WHERE id != :id"; + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':id', $this->id); + $statement->execute(); + } + self::$cache = null; + } + + /** + * Validates this entry + * + * @return array with error messages, if it's empty everyhting is fine + */ + public function validate() + { + $errors = []; + if ($this->isNew() && self::exists($this->id)) { + $errors[] = sprintf( + _('Es existiert bereits ein Eintrag mit der ID %s!'), + $this->id + ); + } + if (!$this->name) { + $errors[] = _('Es wurde kein Name für den Eintrag gesetzt!'); + } + return $errors; + } + + /** + * Determines if a user is permitted to download a file. + * + * Depening on the value of the download_condition attribute a decision + * is made regarding the permission of the given user to download + * a file, given by one of its associated FileRef objects. + * + * The folder condition can have the values 0, 1 and 2. + * - 0 means that there are no conditions for downloading, therefore the + * file is downloadable by anyone. + * - 1 means that the file is only downloadable inside a closed group. + * Such a group can be a course or study group with closed admission. + * In this case this method checks if the user is a member of the + * course or study group. + * - 2 means that the file is only downloadable for the owner. + * The user's ID must therefore match the user_id attribute + * of the FileRef object. + */ + public function isDownloadable($context_id, $context_type, $allow_owner = true, $user_id = null) + { + $user_id = $user_id ?: $GLOBALS['user']->id; + if ($allow_owner) { + if (in_array($context_type, ['course', 'institute']) + && Seminar_Perm::get()->have_studip_perm( + 'tutor', $context_id, $user_id + ) + ) { + return true; + } elseif ($context_type === "profile" && $context_id === $user_id) { + return true; + } + } + if ($this->download_condition == self::DOWNLOAD_CONDITION_CLOSED_GROUPS) { + + //the content is only downloadable when the user is inside a closed group + //(referenced by range_id). If download_condition is set to 2 + //the group must also have a terminated signup deadline. + if ($context_type === "course") { + //check where this range_id comes from: + $seminar = Seminar::GetInstance($context_id); + $timed_admission = $seminar->getAdmissionTimeFrame(); + + if ($seminar->admission_prelim + || $seminar->isPasswordProtected() + || $seminar->isAdmissionLocked() + || (is_array($timed_admission) && $timed_admission['end_time'] > 0 && $timed_admission['end_time'] < time()) + ) { + return true; + } + } + return false; + } + + if ($this->download_condition == self::DOWNLOAD_CONDITION_OWNER_ONLY) { + return false; + } + + return true; + } +} diff --git a/lib/models/Course.class.php b/lib/models/Course.class.php deleted file mode 100644 index bfea7a0..0000000 --- a/lib/models/Course.class.php +++ /dev/null @@ -1,1181 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for seminar_id - * @property string $seminar_id database column - * @property string|null $veranstaltungsnummer database column - * @property string $institut_id database column - * @property I18NString $name database column - * @property I18NString|null $untertitel database column - * @property int $status database column - * @property I18NString $beschreibung database column - * @property I18NString|null $ort database column - * @property string|null $sonstiges database column - * @property int $lesezugriff database column - * @property int $schreibzugriff database column - * @property int|null $start_time database column - * @property int|null $duration_time database column - * @property I18NString|null $art database column - * @property I18NString|null $teilnehmer database column - * @property I18NString|null $vorrausetzungen database column - * @property I18NString|null $lernorga database column - * @property I18NString|null $leistungsnachweis database column - * @property int $mkdate database column - * @property int $chdate database column - * @property string|null $ects database column - * @property int|null $admission_turnout database column - * @property int|null $admission_binding database column - * @property int $admission_prelim database column - * @property string|null $admission_prelim_txt database column - * @property int $admission_disable_waitlist database column - * @property int $visible database column - * @property int|null $showscore database column - * @property string|null $aux_lock_rule database column - * @property int $aux_lock_rule_forced database column - * @property string|null $lock_rule database column - * @property int $admission_waitlist_max database column - * @property int $admission_disable_waitlist_move database column - * @property int $completion database column - * @property string|null $parent_course database column - * @property SimpleORMapCollection|CourseTopic[] $topics has_many CourseTopic - * @property SimpleORMapCollection|CourseDate[] $dates has_many CourseDate - * @property SimpleORMapCollection|CourseExDate[] $ex_dates has_many CourseExDate - * @property SimpleORMapCollection|CourseMember[] $members has_many CourseMember - * @property SimpleORMapCollection|Deputy[] $deputies has_many Deputy - * @property SimpleORMapCollection|Statusgruppen[] $statusgruppen has_many Statusgruppen - * @property SimpleORMapCollection|AdmissionApplication[] $admission_applicants has_many AdmissionApplication - * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel - * @property SimpleORMapCollection|SeminarCycleDate[] $cycles has_many SeminarCycleDate - * @property SimpleORMapCollection|BlubberThread[] $blubberthreads has_many BlubberThread - * @property SimpleORMapCollection|ConsultationBlock[] $consultation_blocks has_many ConsultationBlock - * @property SimpleORMapCollection|RoomRequest[] $room_requests has_many RoomRequest - * @property SimpleORMapCollection|Course[] $children has_many Course - * @property SimpleORMapCollection|ToolActivation[] $tools has_many ToolActivation - * @property SimpleORMapCollection|CourseMemberNotification[] $member_notifications has_many CourseMemberNotification - * @property SimpleORMapCollection|Courseware\Unit[] $courseware_units has_many Courseware\Unit - * @property Institute $home_institut belongs_to Institute - * @property AuxLockRule|null $aux belongs_to AuxLockRule - * @property Course|null $parent belongs_to Course - * @property SimpleORMapCollection|Semester[] $semesters has_and_belongs_to_many Semester - * @property SimpleORMapCollection|StudipStudyArea[] $study_areas has_and_belongs_to_many StudipStudyArea - * @property SimpleORMapCollection|Institute[] $institutes has_and_belongs_to_many Institute - * @property SimpleORMapCollection|UserDomain[] $domains has_and_belongs_to_many UserDomain - * @property-read mixed $teachers additional field - * @property mixed $end_time additional field - * @property mixed $start_semester additional field - * @property mixed $end_semester additional field - * @property-read mixed $semester_text additional field - * @property-read mixed $config additional field - */ - -class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, FeedbackRange, Studip\Calendar\Owner -{ - protected static function configure($config = []) - { - $config['db_table'] = 'seminare'; - $config['has_many']['topics'] = [ - 'class_name' => CourseTopic::class, - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['dates'] = [ - 'class_name' => CourseDate::class, - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'order_by' => 'ORDER BY date' - ]; - $config['has_many']['ex_dates'] = [ - 'class_name' => CourseExDate::class, - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['members'] = [ - 'class_name' => CourseMember::class, - 'assoc_func' => 'findByCourse', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['deputies'] = [ - 'class_name' => Deputy::class, - 'assoc_func' => 'findByRange_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['statusgruppen'] = [ - 'class_name' => Statusgruppen::class, - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['admission_applicants'] = [ - 'class_name' => AdmissionApplication::class, - 'assoc_func' => 'findByCourse', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['datafields'] = [ - 'class_name' => DatafieldEntryModel::class, - 'assoc_func' => 'findByModel', - 'assoc_foreign_key' => function ($model, $params) { - $model->setValue('range_id', $params[0]->id); - }, - 'foreign_key' => function ($course) { - return [$course]; - }, - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['cycles'] = [ - 'class_name' => SeminarCycleDate::class, - 'assoc_func' => 'findBySeminar', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['blubberthreads'] = [ - 'class_name' => BlubberThread::class, - 'assoc_func' => 'findBySeminar', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['consultation_blocks'] = [ - 'class_name' => ConsultationBlock::class, - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - ]; - - $config['has_and_belongs_to_many']['semesters'] = [ - 'class_name' => Semester::class, - 'thru_table' => 'semester_courses', - 'thru_key' => 'course_id', - 'thru_assoc_key' => 'semester_id', - 'order_by' => 'ORDER BY beginn ASC', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - - $config['belongs_to']['home_institut'] = [ - 'class_name' => Institute::class, - 'foreign_key' => 'institut_id', - 'assoc_func' => 'find', - ]; - $config['belongs_to']['aux'] = [ - 'class_name' => AuxLockRule::class, - 'foreign_key' => 'aux_lock_rule', - ]; - $config['has_and_belongs_to_many']['study_areas'] = [ - 'class_name' => StudipStudyArea::class, - 'thru_table' => 'seminar_sem_tree', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_and_belongs_to_many']['institutes'] = [ - 'class_name' => Institute::class, - 'thru_table' => 'seminar_inst', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - - $config['has_and_belongs_to_many']['domains'] = [ - 'class_name' => UserDomain::class, - 'thru_table' => 'seminar_userdomains', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'order_by' => 'ORDER BY name', - ]; - - $config['has_many']['room_requests'] = [ - 'class_name' => RoomRequest::class, - 'assoc_foreign_key' => 'course_id', - 'on_delete' => 'delete', - ]; - $config['belongs_to']['parent'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'parent_course' - ]; - $config['has_many']['children'] = [ - 'class_name' => Course::class, - 'assoc_foreign_key' => 'parent_course', - 'order_by' => 'GROUP BY seminar_id ORDER BY VeranstaltungsNummer, Name' - ]; - $config['has_many']['tools'] = [ - 'class_name' => ToolActivation::class, - 'assoc_foreign_key' => 'range_id', - 'order_by' => 'ORDER BY position', - 'on_delete' => 'delete', - ]; - $config['has_many']['member_notifications'] = [ - 'class_name' => CourseMemberNotification::class, - 'on_delete' => 'delete', - ]; - - $config['has_many']['courseware_units'] = [ - 'class_name' => \Courseware\Unit::class, - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - ]; - - $config['default_values']['lesezugriff'] = 1; - $config['default_values']['schreibzugriff'] = 1; - $config['default_values']['duration_time'] = 0; - - $config['additional_fields']['teachers'] = [ - 'get' => 'getTeachers' - ]; - $config['additional_fields']['end_time'] = true; - - $config['additional_fields']['start_semester'] = [ - 'get' => 'getStartSemester', - 'set' => '_set_semester' - ]; - $config['additional_fields']['end_semester'] = [ - 'get' => 'getEndSemester', - 'set' => '_set_semester' - ]; - $config['additional_fields']['semester_text'] = [ - 'get' => 'getTextualSemester' - ]; - - $config['additional_fields']['config'] = [ - 'get' => function (Course $course) { - return $course->getConfiguration(); - } - ]; - - $config['notification_map']['after_create'] = 'CourseDidCreateOrUpdate'; - $config['notification_map']['after_store'] = 'CourseDidCreateOrUpdate'; - - $config['i18n_fields']['name'] = true; - $config['i18n_fields']['untertitel'] = true; - $config['i18n_fields']['beschreibung'] = true; - $config['i18n_fields']['art'] = true; - $config['i18n_fields']['teilnehmer'] = true; - $config['i18n_fields']['vorrausetzungen'] = true; - $config['i18n_fields']['lernorga'] = true; - $config['i18n_fields']['leistungsnachweis'] = true; - $config['i18n_fields']['ort'] = true; - - $config['registered_callbacks']['before_update'][] = 'logStore'; - $config['registered_callbacks']['before_store'][] = 'cbSetStartAndDurationTime'; - $config['registered_callbacks']['after_create'][] = 'setDefaultTools'; - $config['registered_callbacks']['after_delete'][] = function ($course) { - CourseAvatar::getAvatar($course->id)->reset(); - FeedbackElement::deleteBySQL('course_id = ?', [$course->id]); - // Remove subcourse relations, leaving subcourses intact. - DBManager::get()->execute( - "UPDATE `seminare` SET `parent_course` = NULL WHERE `parent_course` = :course", - ['course' => $course->id] - ); - DBManager::get()->execute( - "DELETE FROM `forum_visits` WHERE `seminar_id` = ?", - [$course->id] - ); - }; - - parent::configure($config); - } - - - /** - * Returns the currently active course or false if none is active. - * - * @return Course object of currently active course, null otherwise - * @since 3.0 - */ - public static function findCurrent() - { - if (Context::isCourse()) { - return Context::get(); - } - - return null; - } - - /** - * Returns the associated mvv modules for a given course id. - * - * @param string $course_id - * @param array|null $statusses Limit the results by a given module status - * @return Modul[] - */ - public static function getMVVModulesForCourseId(string $course_id, ?array $statusses = null): array - { - $query = "SELECT mvv_modul.* - FROM mvv_lvgruppe_seminar - JOIN `mvv_lvgruppe` ON (`mvv_lvgruppe_seminar`.`lvgruppe_id` = `mvv_lvgruppe`.`lvgruppe_id`) - JOIN `mvv_lvgruppe_modulteil` ON (`mvv_lvgruppe_seminar`.`lvgruppe_id` = `mvv_lvgruppe_modulteil`.`lvgruppe_id`) - JOIN `mvv_modulteil` ON (`mvv_lvgruppe_modulteil`.`modulteil_id` = `mvv_modulteil`.`modulteil_id`) - JOIN `mvv_modul` ON (`mvv_modulteil`.`modul_id` = `mvv_modul`.`modul_id`) - WHERE seminar_id = ?"; - $parameters = [$course_id]; - - if ($statusses !== null) { - $query .= ' AND `mvv_modul`.`stat` IN (?)'; - $parameters[] = $statusses; - } - - return DBManager::get()->fetchAll($query, $parameters, function ($row) { - return Modul::buildExisting($row); - }); - } - - public function getEnd_Time() - { - return $this->duration_time == -1 ? -1 : $this->start_time + $this->duration_time; - } - - public function setEnd_Time($value) - { - if ($value == -1) { - $this->duration_time = -1; - } elseif ($this->start_time > 0 && $value > $this->start_time) { - $this->duration_time = $value - $this->start_time; - } else { - $this->duration_time = 0; - } - } - - public function _set_semester($field, $value) - { - $method = 'set' . ($field === 'start_semester' ? 'StartSemester' : 'EndSemester'); - $this->$method($value); - } - - /** - * @param Semester $semester - */ - public function setStartSemester(Semester $semester) - { - $end_semester = $this->semesters->last(); - $start_semester = $this->semesters->first(); - if ($start_semester && $start_semester->id === $semester->id) { - return; - } - if ($end_semester) { - if (count($this->semesters) > 1 && $end_semester->beginn < $semester->beginn) { - throw new InvalidArgumentException('start-semester must start before end-semester'); - } - foreach ($this->semesters as $key => $one_semester) { - if ($one_semester->beginn < $semester->beginn) { - $this->semesters->offsetUnset($key); - } - } - } - $this->semesters[] = $semester; - $this->semesters->orderBy('beginn asc'); - //add possibly missing semesters between start_semester and end_semester - if (count($this->semesters) > 1 && $semester->beginn < $start_semester->beginn) { - $this->setEndSemester($end_semester); - } - } - - /** - * @param Semester|null $semester - */ - public function setEndSemester(?Semester $semester) - { - $end_semester = $this->semesters->last(); - $start_semester = $this->semesters->first(); - if ( - (is_null($end_semester) && is_null($semester)) - || ($end_semester && $semester && $end_semester->id === $semester->id)) { - return; - } - if ($start_semester) { - if ($semester && $start_semester->beginn > $semester->beginn) { - throw new InvalidArgumentException('end-semester must start after start-semester'); - } - $this->semesters = []; - if ($semester) { - $all_semester = SimpleCollection::createFromArray(Semester::getAll()); - $this->semesters = $all_semester->findBy('beginn', [$start_semester->beginn, $semester->beginn], '>=<='); - } - } else { - if ($semester) { - $this->semesters[] = $semester; - } - } - } - - /** - * Retrieves the first semester of a course, if applicable. - * - * @returns Semester|null Either the first semester of the course - * or null, if no semester could be found. - */ - public function getStartSemester() - { - if (count($this->semesters) > 0) { - return $this->semesters->first(); - } else { - return Semester::findCurrent(); - } - } - - /** - * Retrieves the last semester of a course, if applicable. - * - * @returns Semester|null Either the last semester of the course - * or null, if no semester could be found. - */ - public function getEndSemester() - { - if (count($this->semesters) > 0) { - return $this->semesters->last(); - } - } - - /** - * Returns the readable semester duration as as string - * @return string : readable semester - */ - public function getTextualSemester() - { - if (count($this->semesters) > 1) { - return $this->start_semester->short_name . ' - ' . $this->end_semester->short_name; - } elseif (count($this->semesters) === 1) { - return $this->start_semester->short_name; - } else { - return _('unbegrenzt'); - } - } - - /** - * Returns true if this course has no end-semester. Else false. - * @return bool : true if there is no end-semester - */ - public function isOpenEnded() - { - return count($this->semesters) === 0; - } - - /** - * Returns if this course is in the given semester - * @param Semester $semester : instance of the given semester - * @return bool : true if this course is part of this semester - */ - public function isInSemester(Semester $semester) - { - if (count($this->semesters) > 0) { - foreach ($this->semesters as $s) { - if ($s->id === $semester->id) { - return true; - } - } - return false; - } else { - return true; - } - } - - public function getTeachers() - { - return $this->members->filter(function ($m) { - return $m['status'] === 'dozent'; - }); - } - - public function getFreeSeats() - { - $free_seats = $this->admission_turnout - $this->getNumParticipants(); - return max($free_seats, 0); - } - - public function isWaitlistAvailable() - { - if ($this->admission_disable_waitlist) { - return false; - } - - if ($this->admission_waitlist_max) { - return $this->admission_waitlist_max - $this->getNumWaiting() > 0; - } - - return true; - } - - /** - * Retrieves all members of a status - * - * @param String|Array $status the status to filter with - * @param bool $as_collection return collection instead of array? - * @return Array|SimpleCollection an array of all those members. - */ - public function getMembersWithStatus($status, $as_collection = false) - { - $result = CourseMember::findByCourseAndStatus($this->id, $status); - return $as_collection - ? SimpleCollection::createFromArray($result) - : $result; - } - - /** - * Retrieves the number of all members of a status - * - * @param String|Array $status the status to filter with - * - * @return int the number of all those members. - */ - public function countMembersWithStatus($status) - { - return CourseMember::countByCourseAndStatus($this->id, $status); - } - - public function getNumParticipants() - { - return $this->countMembersWithStatus('user autor') + $this->getNumPrelimParticipants(); - } - - public function getNumPrelimParticipants() - { - return AdmissionApplication::countBySql( - "seminar_id = ? AND status = 'accepted'", - [$this->id] - ); - } - - public function getNumWaiting() - { - return AdmissionApplication::countBySql( - "seminar_id = ? AND status = 'awaiting'", - [$this->id] - ); - } - - public function getParticipantStatus($user_id) - { - $p_status = $this->members->findBy('user_id', $user_id)->val('status'); - if (!$p_status) { - $p_status = $this->admission_applicants->findBy('user_id', $user_id)->val('status'); - } - return $p_status; - } - - /** - * Returns the semType object that is defined for the course - * - * @return SemType The semTypeObject for the course - */ - public function getSemType() - { - $semTypes = SemType::getTypes(); - if (isset($semTypes[$this->status])) { - return $semTypes[$this->status]; - } - - Log::error(sprintf('SemType not found id:%s status:%s', $this->id, $this->status)); - return new SemType(['name' => 'Fehlerhafter Veranstaltungstyp']); - } - - /** - * Returns the SemClass object that is defined for the course - * - * @return SemClass The SemClassObject for the course - */ - public function getSemClass() - { - return $this->getSemType()->getClass(); - } - - /** - * Returns the full name of a course. If the important course numbers - * (IMPORTANT_SEMNUMBER) is set in global configs it will also display - * the coursenumber - * - * @param string formatting template name - * @return string Fullname - */ - public function getFullName($format = 'default') - { - $template = [ - 'name' => '%1$s', - 'name-semester' => '%1$s (%4$s)', - 'number-name' => '%3$s %1$s', - 'number-name-semester' => '%3$s %1$s (%4$s)', - 'number-type-name' => '%3$s %2$s: %1$s', - 'sem-duration-name' => '%4$s', - 'type-name' => '%2$s: %1$s', - 'type-number-name' => '%2$s: %3$s %1$s', - ]; - - if ($format === 'default' || !isset($template[$format])) { - $format = Config::get()->IMPORTANT_SEMNUMBER ? 'type-number-name' : 'type-name'; - } - $sem_type = $this->getSemType(); - $data[0] = $this->name; - $data[1] = $sem_type['name']; - $data[2] = $this->veranstaltungsnummer; - $data[3] = $this->getTextualSemester(); - return trim(vsprintf($template[$format], array_map('trim', $data))); - } - - - /** - * Retrieves the course dates including cancelled dates ("ex-dates"). - * The dates can be filtered by an optional time range. By default, - * all dates are retrieved. - * - * @param int $range_begin The begin timestamp of the time range. - * - * @param int $range_end The end timestamp of the time range. - * - * @returns SimpleCollection A collection of all retrieved dates and - * cancelled dates. - */ - public function getDatesWithExdates($range_begin = 0, $range_end = 0) - { - $dates = []; - if (($range_begin > 0) && ($range_end > 0) && ($range_end > $range_begin)) { - $ex_dates = $this->ex_dates->findBy('content', '', '<>') - ->findBy('date', $range_begin, '>=') - ->findBy('end_time', $range_end, '<='); - $dates = $this->dates->findBy('date', $range_begin, '>=') - ->findBy('end_time', $range_end, '<='); - $dates->merge($ex_dates); - } else { - $dates = $this->ex_dates->findBy('content', '', '<>'); - $dates->merge($this->dates); - } - $dates->uasort(function($a, $b) { - return $a->date - $b->date - ?: strnatcasecmp($a->getRoomName(), $b->getRoomName()); - }); - return $dates; - } - - /** - * Sets this courses study areas to the given values. - * - * @param array $ids the new study areas - * @return bool Changes successfully saved? - */ - public function setStudyAreas($ids) - { - $old = $this->study_areas->pluck('sem_tree_id'); - $added = array_diff($ids, $old); - $removed = array_diff($old, $ids); - $success = false; - if ($added || $removed) { - - $this->study_areas = SimpleCollection::createFromArray(StudipStudyArea::findMany($ids)); - - if ($this->store()) { - NotificationCenter::postNotification('CourseDidChangeStudyArea', $this); - $success = true; - - foreach ($added as $one) { - StudipLog::log('SEM_ADD_STUDYAREA', $this->id, $one); - - $area = $this->study_areas->find($one); - if ($area->isModule()) { - NotificationCenter::postNotification( - 'CourseAddedToModule', - $area, - ['module_id' => $one, 'course_id' => $this->id] - ); - } - } - - foreach ($removed as $one) { - StudipLog::log('SEM_DELETE_STUDYAREA', $this->id, $one); - - $area = StudipStudyArea::find($one); - if ($area->isModule()) { - NotificationCenter::postNotification( - 'CourseRemovedFromModule', - $area, - ['module_id' => $one, 'course_id' => $this->id] - ); - } - } - } - } - - return $success; - } - - /** - * Is the current course visible for the current user? - * @param string $user_id - * @return bool Visible? - */ - public function isVisibleForUser($user_id = null) - { - return $this->visible - || $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM, $user_id) - || $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id); - } - - /** - * Returns a descriptive text for the range type. - * - * @return string - */ - public function describeRange() - { - return _('Veranstaltung'); - } - - /** - * Returns a unique identificator for the range type. - * - * @return string - */ - public function getRangeType() - { - return 'course'; - } - - /** - * Returns the id of the current range - * - * @return string - */ - public function getRangeId() - { - return $this->id; - } - - /** - * {@inheritdoc} - */ - public function getConfiguration() - { - return CourseConfig::get($this); - } - - /** - * Decides whether the user may access the range. - * - * @param string|null $user_id Optional id of a user, defaults to current user - * @return bool - * @todo Check permissions - */ - public function isAccessibleToUser($user_id = null) - { - if ($user_id === null) { - $user_id = $GLOBALS['user']->id; - } - return $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id); - } - - /** - * Decides whether the user may edit/alter the range. - * - * @param string|null $user_id Optional id of a user, defaults to current user - * @return bool - * @todo Check permissions - */ - public function isEditableByUser($user_id = null) - { - if ($user_id === null) { - $user_id = $GLOBALS['user']->id; - } - return $GLOBALS['perm']->have_studip_perm('tutor', $this->id, $user_id); - } - - /** - * Returns the appropriate icon for the completion status. - * - * Mapping (completion -> icon role): - * - 0 => status-red - * - 1 => status-yellow - * - 2 => status-green - * - * @return Icon class - */ - public function getCompletionIcon() - { - $role = Icon::ROLE_STATUS_RED; - if ($this->completion == 1) { - $role = Icon::ROLE_STATUS_YELLOW; - } elseif ($this->completion == 2) { - $role = Icon::ROLE_STATUS_GREEN; - } - return Icon::create('radiobutton-checked', $role); - } - - /** - * Returns the appropriate label for the completion status. - * - * @return string - */ - public function getCompetionLabel(): string - { - return [ - 0 => _('unvollständig'), - 1 => _('in Bearbeitung'), - 2 => _('fertig'), - ][$this->completion] ?? _('undefiniert'); - } - - /** - * Generates a general log entry if the course were changed. - * Furthermore, this method emits notifications when the - * start and/or the end semester has/have changed. - */ - protected function logStore() - { - if ($this->isFieldDirty('start_time')) { - //Log change of start semester: - StudipLog::log('SEM_SET_STARTSEMESTER', $this->id, isset($this->start_semester) ? $this->start_semester->name : _('unbegrenzt')); - NotificationCenter::postNotification('CourseDidChangeSchedule', $this); - } - if ($this->isFieldDirty('duration_time')) { - StudipLog::log('SEM_SET_ENDSEMESTER', $this->id, $this->getTextualSemester()); - NotificationCenter::postNotification('CourseDidChangeSchedule', $this); - } - - $log = []; - if ($this->isFieldDirty('admission_prelim')) { - $log[] = $this->admission_prelim ? _('Neuer Anmeldemodus: Vorläufiger Eintrag') : _('Neuer Anmeldemodus: Direkter Eintrag'); - } - - if ($this->isFieldDirty('admission_binding')) { - $log[] = $this->admission_binding? _('Anmeldung verbindlich') : _('Anmeldung unverbindlich'); - } - - if ($this->isFieldDirty('admission_turnout')) { - $log[] = sprintf(_('Neue Teilnehmerzahl: %s'), (int)$this->admission_turnout); - } - - if ($this->isFieldDirty('admission_disable_waitlist')) { - $log[] = $this->admission_disable_waitlist ? _('Warteliste aktiviert') : _('Warteliste deaktiviert'); - } - - if ($this->isFieldDirty('admission_waitlist_max')) { - $log[] = sprintf(_('Plätze auf der Warteliste geändert: %u'), (int)$this->admission_waitlist_max); - } - - if ($this->isFieldDirty('admission_disable_waitlist_move')) { - $log[] = $this->admission_disable_waitlist ? _('Nachrücken aktiviert') : _('Nachrücken deaktiviert'); - } - - if ($this->isFieldDirty('admission_prelim_txt')) { - if ($this->admission_prelim_txt) { - $log[] = sprintf(_('Neuer Hinweistext bei vorläufigen Eintragungen: %s'), strip_tags(kill_format($this->admission_prelim_txt))); - } else { - $log[] = _('Hinweistext bei vorläufigen Eintragungen wurde entfert'); - } - } - - if (!empty($log)) { - StudipLog::log( - 'SEM_CHANGED_ACCESS', - $this->id, - null, - '', - implode(' - ', $log) - ); - } - - if ($this->isFieldDirty('visible')) { - StudipLog::log($this->visible ? 'SEM_VISIBLE' : 'SEM_INVISIBLE', $this->id); - } - } - - /** - * Called directly before storing the object to edit the columns start_time and duration_time - * which are both deprecated but are still in use for older plugins. - */ - public function cbSetStartAndDurationTime() - { - if ($this->isFieldDirty('start_time')) { - $this->setStartSemester(Semester::findByTimestamp($this->start_time)); - } - if ($this->isFieldDirty('duration_time')) { - $this->setEndSemester($this->duration_time == -1 ? null : Semester::findByTimestamp($this->start_time + $this->duration_time)); - } - if ($this->isOpenEnded()) { - $this->start_time = $this->start_time ?: Semester::findCurrent()->beginn ?? time(); - $this->duration_time = -1; - } else { - $this->start_time = $this->getStartSemester()->beginn; - $this->duration_time = $this->getEndSemester()->beginn - $this->start_time; - } - } - - - //StudipItem interface implementation: - - public function getItemName($long_format = true) - { - if ($long_format) { - return $this->getFullName(); - } else { - return $this->name; - } - } - - public function getItemURL() - { - return URLHelper::getURL( - 'dispatch.php/course/details/index', - [ - 'cid' => $this->id - ] - ); - } - - public function getItemAvatarURL() - { - $avatar = CourseAvatar::getAvatar($this->id); - if ($avatar) { - return $avatar->getURL(Avatar::NORMAL); - } - return ''; - } - - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findThru($storage->user_id, [ - 'thru_table' => 'seminar_user', - 'thru_key' => 'user_id', - 'thru_assoc_key' => 'Seminar_id', - 'assoc_foreign_key' => 'Seminar_id', - ]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Seminare'), 'seminare', $field_data); - } - } - } - public function getRangeName() - { - return $this->name; - } - - public function getRangeIcon($role) - { - return Icon::create('seminar', $role); - } - - public function getRangeUrl() - { - return 'course/overview'; - } - - public function getRangeCourseId() - { - return $this->Seminar_id; - } - - public function isRangeAccessible(string $user_id = null): bool - { - $user_id = $user_id ?? $GLOBALS['user']->id; - return $GLOBALS['perm']->have_studip_perm('autor', $this->Seminar_id, $user_id); - } - - - public function getLink() : StudipLink - { - return new StudipLink($this->getItemURL(), $this->name, Icon::create('seminar')); - } - - - /** - * Returns a list of courses for the specified user. - * Permission levels may be supplied to limit the course list. - * - * @param string $user_id The ID of the user whose courses shall be retrieved. - * - * @param string[] $perms The permission levels of the user that shall be - * regarded when retrieving courses. - * - * @param bool $with_deputies Whether to include courses where the user is - * a deputy (true) or not (false). Defaults to true. - * - * @return Course[] A list of courses. - */ - public static function findByUser($user_id, $perms = [], $with_deputies = true) - { - if (!$user_id) { - return []; - } - - $db = DBManager::get(); - $sql = "SELECT `seminar_id` - FROM `seminar_user` - WHERE `user_id` = :user_id"; - $sql_params = ['user_id' => $user_id]; - if (is_array($perms) && count($perms)) { - $sql .= ' AND `status` IN (:perms)'; - $sql_params['perms'] = $perms; - } - $seminar_ids = $db->fetchFirst($sql, $sql_params); - if (Config::get()->DEPUTIES_ENABLE && $with_deputies) { - $sql = 'SELECT range_id FROM `deputies` WHERE `deputies`.`user_id` = :user_id'; - $seminar_ids = array_merge($seminar_ids, $db->fetchFirst($sql, $sql_params)); - } - - $name_sort = Config::get()->IMPORTANT_SEMNUMBER ? 'VeranstaltungsNummer, Name' : 'Name'; - - return Course::findBySQL( - "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id) - WHERE Seminar_id IN (?) - GROUP BY seminare.Seminar_id - ORDER BY semester_courses.semester_id IS NULL DESC, start_time DESC, {$name_sort}", - [$seminar_ids] - ); - } - - /** - * Returns whether this course is a studygroup - * @return bool - */ - public function isStudygroup() - { - return in_array($this->status, studygroup_sem_types()); - } - - /** - * - */ - public function setDefaultTools() - { - $this->tools = []; - foreach (array_values($this->getSemClass()->getActivatedModuleObjects()) as $module) { - PluginManager::getInstance()->setPluginActivated($module->getPluginId(), $this->id, true); - $this->tools[] = ToolActivation::find([$this->id, $module->getPluginId()]); - } - } - - /** - * @param $name string name of tool / plugin - * @return bool - */ - public function isToolActive($name) - { - $plugin = PluginEngine::getPlugin($name); - return $plugin && $this->tools->findOneby('plugin_id', $plugin->getPluginId()); - } - - /** - * returns all activated plugins/modules for this course - * @return StudipModule[] - */ - public function getActivatedTools() - { - return array_filter($this->tools->getStudipModule()); - } - - /** - * @see Range::__toString() - */ - public function __toString() : string - { - return $this->getFullName(); - } - - /** - * @inheritDoc - */ - public static function getCalendarOwner(string $owner_id): ?\Studip\Calendar\Owner - { - return self::find($owner_id); - } - - /** - * @inheritDoc - */ - public function isCalendarReadable(?string $user_id = null): bool - { - if ($user_id === null) { - $user_id = User::findCurrent()->id; - } - - //Calendar read permissions are granted for all participants - //that have at least user permissions. - return $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id); - } - - /** - * @inheritDoc - */ - public function isCalendarWritable(string $user_id = null): bool - { - if ($user_id === null) { - $user_id = User::findCurrent()->id; - } - - //Calendar write permissions are granted for all participants - //that have autor permissions or higher. - return $GLOBALS['perm']->have_studip_perm('autor', $this->id, $user_id); - } - - /** - * Get user information for all users in this course - * - */ - public function getMembersData(?string $status = ''): array - { - $result = []; - - if (!$status) { - foreach ($this->members->orderBy('position, nachname') as $member) { - $result[$member->user_id] = $member->getExportData(); - } - foreach ($this->admission_applicants->findBy('status', 'accepted')->orderBy('position') as $member) { - $result[$member->user_id] = $member->getExportData(); - } - } elseif ($status === 'awaiting') { - foreach ($this->admission_applicants->findBy('status', $status)->orderBy('position') as $member) { - $result[$member->user_id] = $member->getExportData(); - } - } elseif ($status === 'claiming') { - $cs = CourseSet::getSetForCourse($this->id); - if (is_object($cs) && !$cs->hasAlgorithmRun()) { - $claiming_users = User::findFullMany(array_keys(AdmissionPriority::getPrioritiesByCourse($cs->getId(), $this->id)), 'ORDER BY nachname'); - foreach ($claiming_users as $claiming_user) { - $studycourse = []; - $claiming_user->studycourses->map(function($sc) use (&$studycourse) { - $studycourse[]= $sc->studycourse->name . ',' . $sc->degree->name . ',' . $sc->semester; - }); - $export_data = [ - 'status' => $status, - 'salutation' => $claiming_user->salutation, - 'Titel' => $claiming_user->title_front, - 'Vorname' => $claiming_user->vorname, - 'Nachname' => $claiming_user->nachname, - 'Titel2' => $claiming_user->title_rear, - 'username' => $claiming_user->username, - 'privadr' => $claiming_user->privadr, - 'privatnr' => $claiming_user->privatnr, - 'Email' => $claiming_user->email, - 'Anmeldedatum' => '', - 'Matrikelnummer' => $claiming_user->matriculation_number, - 'studiengaenge' => implode(';', $studycourse), - 'position' => 0, - ]; - $result[$claiming_user->user_id] = $export_data; - } - } - } - - return $result; - } - -} diff --git a/lib/models/Course.php b/lib/models/Course.php new file mode 100644 index 0000000..bfea7a0 --- /dev/null +++ b/lib/models/Course.php @@ -0,0 +1,1181 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for seminar_id + * @property string $seminar_id database column + * @property string|null $veranstaltungsnummer database column + * @property string $institut_id database column + * @property I18NString $name database column + * @property I18NString|null $untertitel database column + * @property int $status database column + * @property I18NString $beschreibung database column + * @property I18NString|null $ort database column + * @property string|null $sonstiges database column + * @property int $lesezugriff database column + * @property int $schreibzugriff database column + * @property int|null $start_time database column + * @property int|null $duration_time database column + * @property I18NString|null $art database column + * @property I18NString|null $teilnehmer database column + * @property I18NString|null $vorrausetzungen database column + * @property I18NString|null $lernorga database column + * @property I18NString|null $leistungsnachweis database column + * @property int $mkdate database column + * @property int $chdate database column + * @property string|null $ects database column + * @property int|null $admission_turnout database column + * @property int|null $admission_binding database column + * @property int $admission_prelim database column + * @property string|null $admission_prelim_txt database column + * @property int $admission_disable_waitlist database column + * @property int $visible database column + * @property int|null $showscore database column + * @property string|null $aux_lock_rule database column + * @property int $aux_lock_rule_forced database column + * @property string|null $lock_rule database column + * @property int $admission_waitlist_max database column + * @property int $admission_disable_waitlist_move database column + * @property int $completion database column + * @property string|null $parent_course database column + * @property SimpleORMapCollection|CourseTopic[] $topics has_many CourseTopic + * @property SimpleORMapCollection|CourseDate[] $dates has_many CourseDate + * @property SimpleORMapCollection|CourseExDate[] $ex_dates has_many CourseExDate + * @property SimpleORMapCollection|CourseMember[] $members has_many CourseMember + * @property SimpleORMapCollection|Deputy[] $deputies has_many Deputy + * @property SimpleORMapCollection|Statusgruppen[] $statusgruppen has_many Statusgruppen + * @property SimpleORMapCollection|AdmissionApplication[] $admission_applicants has_many AdmissionApplication + * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel + * @property SimpleORMapCollection|SeminarCycleDate[] $cycles has_many SeminarCycleDate + * @property SimpleORMapCollection|BlubberThread[] $blubberthreads has_many BlubberThread + * @property SimpleORMapCollection|ConsultationBlock[] $consultation_blocks has_many ConsultationBlock + * @property SimpleORMapCollection|RoomRequest[] $room_requests has_many RoomRequest + * @property SimpleORMapCollection|Course[] $children has_many Course + * @property SimpleORMapCollection|ToolActivation[] $tools has_many ToolActivation + * @property SimpleORMapCollection|CourseMemberNotification[] $member_notifications has_many CourseMemberNotification + * @property SimpleORMapCollection|Courseware\Unit[] $courseware_units has_many Courseware\Unit + * @property Institute $home_institut belongs_to Institute + * @property AuxLockRule|null $aux belongs_to AuxLockRule + * @property Course|null $parent belongs_to Course + * @property SimpleORMapCollection|Semester[] $semesters has_and_belongs_to_many Semester + * @property SimpleORMapCollection|StudipStudyArea[] $study_areas has_and_belongs_to_many StudipStudyArea + * @property SimpleORMapCollection|Institute[] $institutes has_and_belongs_to_many Institute + * @property SimpleORMapCollection|UserDomain[] $domains has_and_belongs_to_many UserDomain + * @property-read mixed $teachers additional field + * @property mixed $end_time additional field + * @property mixed $start_semester additional field + * @property mixed $end_semester additional field + * @property-read mixed $semester_text additional field + * @property-read mixed $config additional field + */ + +class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, FeedbackRange, Studip\Calendar\Owner +{ + protected static function configure($config = []) + { + $config['db_table'] = 'seminare'; + $config['has_many']['topics'] = [ + 'class_name' => CourseTopic::class, + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['dates'] = [ + 'class_name' => CourseDate::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'order_by' => 'ORDER BY date' + ]; + $config['has_many']['ex_dates'] = [ + 'class_name' => CourseExDate::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['members'] = [ + 'class_name' => CourseMember::class, + 'assoc_func' => 'findByCourse', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['deputies'] = [ + 'class_name' => Deputy::class, + 'assoc_func' => 'findByRange_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['statusgruppen'] = [ + 'class_name' => Statusgruppen::class, + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['admission_applicants'] = [ + 'class_name' => AdmissionApplication::class, + 'assoc_func' => 'findByCourse', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['datafields'] = [ + 'class_name' => DatafieldEntryModel::class, + 'assoc_func' => 'findByModel', + 'assoc_foreign_key' => function ($model, $params) { + $model->setValue('range_id', $params[0]->id); + }, + 'foreign_key' => function ($course) { + return [$course]; + }, + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['cycles'] = [ + 'class_name' => SeminarCycleDate::class, + 'assoc_func' => 'findBySeminar', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['blubberthreads'] = [ + 'class_name' => BlubberThread::class, + 'assoc_func' => 'findBySeminar', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['consultation_blocks'] = [ + 'class_name' => ConsultationBlock::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + ]; + + $config['has_and_belongs_to_many']['semesters'] = [ + 'class_name' => Semester::class, + 'thru_table' => 'semester_courses', + 'thru_key' => 'course_id', + 'thru_assoc_key' => 'semester_id', + 'order_by' => 'ORDER BY beginn ASC', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + + $config['belongs_to']['home_institut'] = [ + 'class_name' => Institute::class, + 'foreign_key' => 'institut_id', + 'assoc_func' => 'find', + ]; + $config['belongs_to']['aux'] = [ + 'class_name' => AuxLockRule::class, + 'foreign_key' => 'aux_lock_rule', + ]; + $config['has_and_belongs_to_many']['study_areas'] = [ + 'class_name' => StudipStudyArea::class, + 'thru_table' => 'seminar_sem_tree', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_and_belongs_to_many']['institutes'] = [ + 'class_name' => Institute::class, + 'thru_table' => 'seminar_inst', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + + $config['has_and_belongs_to_many']['domains'] = [ + 'class_name' => UserDomain::class, + 'thru_table' => 'seminar_userdomains', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'order_by' => 'ORDER BY name', + ]; + + $config['has_many']['room_requests'] = [ + 'class_name' => RoomRequest::class, + 'assoc_foreign_key' => 'course_id', + 'on_delete' => 'delete', + ]; + $config['belongs_to']['parent'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'parent_course' + ]; + $config['has_many']['children'] = [ + 'class_name' => Course::class, + 'assoc_foreign_key' => 'parent_course', + 'order_by' => 'GROUP BY seminar_id ORDER BY VeranstaltungsNummer, Name' + ]; + $config['has_many']['tools'] = [ + 'class_name' => ToolActivation::class, + 'assoc_foreign_key' => 'range_id', + 'order_by' => 'ORDER BY position', + 'on_delete' => 'delete', + ]; + $config['has_many']['member_notifications'] = [ + 'class_name' => CourseMemberNotification::class, + 'on_delete' => 'delete', + ]; + + $config['has_many']['courseware_units'] = [ + 'class_name' => \Courseware\Unit::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + ]; + + $config['default_values']['lesezugriff'] = 1; + $config['default_values']['schreibzugriff'] = 1; + $config['default_values']['duration_time'] = 0; + + $config['additional_fields']['teachers'] = [ + 'get' => 'getTeachers' + ]; + $config['additional_fields']['end_time'] = true; + + $config['additional_fields']['start_semester'] = [ + 'get' => 'getStartSemester', + 'set' => '_set_semester' + ]; + $config['additional_fields']['end_semester'] = [ + 'get' => 'getEndSemester', + 'set' => '_set_semester' + ]; + $config['additional_fields']['semester_text'] = [ + 'get' => 'getTextualSemester' + ]; + + $config['additional_fields']['config'] = [ + 'get' => function (Course $course) { + return $course->getConfiguration(); + } + ]; + + $config['notification_map']['after_create'] = 'CourseDidCreateOrUpdate'; + $config['notification_map']['after_store'] = 'CourseDidCreateOrUpdate'; + + $config['i18n_fields']['name'] = true; + $config['i18n_fields']['untertitel'] = true; + $config['i18n_fields']['beschreibung'] = true; + $config['i18n_fields']['art'] = true; + $config['i18n_fields']['teilnehmer'] = true; + $config['i18n_fields']['vorrausetzungen'] = true; + $config['i18n_fields']['lernorga'] = true; + $config['i18n_fields']['leistungsnachweis'] = true; + $config['i18n_fields']['ort'] = true; + + $config['registered_callbacks']['before_update'][] = 'logStore'; + $config['registered_callbacks']['before_store'][] = 'cbSetStartAndDurationTime'; + $config['registered_callbacks']['after_create'][] = 'setDefaultTools'; + $config['registered_callbacks']['after_delete'][] = function ($course) { + CourseAvatar::getAvatar($course->id)->reset(); + FeedbackElement::deleteBySQL('course_id = ?', [$course->id]); + // Remove subcourse relations, leaving subcourses intact. + DBManager::get()->execute( + "UPDATE `seminare` SET `parent_course` = NULL WHERE `parent_course` = :course", + ['course' => $course->id] + ); + DBManager::get()->execute( + "DELETE FROM `forum_visits` WHERE `seminar_id` = ?", + [$course->id] + ); + }; + + parent::configure($config); + } + + + /** + * Returns the currently active course or false if none is active. + * + * @return Course object of currently active course, null otherwise + * @since 3.0 + */ + public static function findCurrent() + { + if (Context::isCourse()) { + return Context::get(); + } + + return null; + } + + /** + * Returns the associated mvv modules for a given course id. + * + * @param string $course_id + * @param array|null $statusses Limit the results by a given module status + * @return Modul[] + */ + public static function getMVVModulesForCourseId(string $course_id, ?array $statusses = null): array + { + $query = "SELECT mvv_modul.* + FROM mvv_lvgruppe_seminar + JOIN `mvv_lvgruppe` ON (`mvv_lvgruppe_seminar`.`lvgruppe_id` = `mvv_lvgruppe`.`lvgruppe_id`) + JOIN `mvv_lvgruppe_modulteil` ON (`mvv_lvgruppe_seminar`.`lvgruppe_id` = `mvv_lvgruppe_modulteil`.`lvgruppe_id`) + JOIN `mvv_modulteil` ON (`mvv_lvgruppe_modulteil`.`modulteil_id` = `mvv_modulteil`.`modulteil_id`) + JOIN `mvv_modul` ON (`mvv_modulteil`.`modul_id` = `mvv_modul`.`modul_id`) + WHERE seminar_id = ?"; + $parameters = [$course_id]; + + if ($statusses !== null) { + $query .= ' AND `mvv_modul`.`stat` IN (?)'; + $parameters[] = $statusses; + } + + return DBManager::get()->fetchAll($query, $parameters, function ($row) { + return Modul::buildExisting($row); + }); + } + + public function getEnd_Time() + { + return $this->duration_time == -1 ? -1 : $this->start_time + $this->duration_time; + } + + public function setEnd_Time($value) + { + if ($value == -1) { + $this->duration_time = -1; + } elseif ($this->start_time > 0 && $value > $this->start_time) { + $this->duration_time = $value - $this->start_time; + } else { + $this->duration_time = 0; + } + } + + public function _set_semester($field, $value) + { + $method = 'set' . ($field === 'start_semester' ? 'StartSemester' : 'EndSemester'); + $this->$method($value); + } + + /** + * @param Semester $semester + */ + public function setStartSemester(Semester $semester) + { + $end_semester = $this->semesters->last(); + $start_semester = $this->semesters->first(); + if ($start_semester && $start_semester->id === $semester->id) { + return; + } + if ($end_semester) { + if (count($this->semesters) > 1 && $end_semester->beginn < $semester->beginn) { + throw new InvalidArgumentException('start-semester must start before end-semester'); + } + foreach ($this->semesters as $key => $one_semester) { + if ($one_semester->beginn < $semester->beginn) { + $this->semesters->offsetUnset($key); + } + } + } + $this->semesters[] = $semester; + $this->semesters->orderBy('beginn asc'); + //add possibly missing semesters between start_semester and end_semester + if (count($this->semesters) > 1 && $semester->beginn < $start_semester->beginn) { + $this->setEndSemester($end_semester); + } + } + + /** + * @param Semester|null $semester + */ + public function setEndSemester(?Semester $semester) + { + $end_semester = $this->semesters->last(); + $start_semester = $this->semesters->first(); + if ( + (is_null($end_semester) && is_null($semester)) + || ($end_semester && $semester && $end_semester->id === $semester->id)) { + return; + } + if ($start_semester) { + if ($semester && $start_semester->beginn > $semester->beginn) { + throw new InvalidArgumentException('end-semester must start after start-semester'); + } + $this->semesters = []; + if ($semester) { + $all_semester = SimpleCollection::createFromArray(Semester::getAll()); + $this->semesters = $all_semester->findBy('beginn', [$start_semester->beginn, $semester->beginn], '>=<='); + } + } else { + if ($semester) { + $this->semesters[] = $semester; + } + } + } + + /** + * Retrieves the first semester of a course, if applicable. + * + * @returns Semester|null Either the first semester of the course + * or null, if no semester could be found. + */ + public function getStartSemester() + { + if (count($this->semesters) > 0) { + return $this->semesters->first(); + } else { + return Semester::findCurrent(); + } + } + + /** + * Retrieves the last semester of a course, if applicable. + * + * @returns Semester|null Either the last semester of the course + * or null, if no semester could be found. + */ + public function getEndSemester() + { + if (count($this->semesters) > 0) { + return $this->semesters->last(); + } + } + + /** + * Returns the readable semester duration as as string + * @return string : readable semester + */ + public function getTextualSemester() + { + if (count($this->semesters) > 1) { + return $this->start_semester->short_name . ' - ' . $this->end_semester->short_name; + } elseif (count($this->semesters) === 1) { + return $this->start_semester->short_name; + } else { + return _('unbegrenzt'); + } + } + + /** + * Returns true if this course has no end-semester. Else false. + * @return bool : true if there is no end-semester + */ + public function isOpenEnded() + { + return count($this->semesters) === 0; + } + + /** + * Returns if this course is in the given semester + * @param Semester $semester : instance of the given semester + * @return bool : true if this course is part of this semester + */ + public function isInSemester(Semester $semester) + { + if (count($this->semesters) > 0) { + foreach ($this->semesters as $s) { + if ($s->id === $semester->id) { + return true; + } + } + return false; + } else { + return true; + } + } + + public function getTeachers() + { + return $this->members->filter(function ($m) { + return $m['status'] === 'dozent'; + }); + } + + public function getFreeSeats() + { + $free_seats = $this->admission_turnout - $this->getNumParticipants(); + return max($free_seats, 0); + } + + public function isWaitlistAvailable() + { + if ($this->admission_disable_waitlist) { + return false; + } + + if ($this->admission_waitlist_max) { + return $this->admission_waitlist_max - $this->getNumWaiting() > 0; + } + + return true; + } + + /** + * Retrieves all members of a status + * + * @param String|Array $status the status to filter with + * @param bool $as_collection return collection instead of array? + * @return Array|SimpleCollection an array of all those members. + */ + public function getMembersWithStatus($status, $as_collection = false) + { + $result = CourseMember::findByCourseAndStatus($this->id, $status); + return $as_collection + ? SimpleCollection::createFromArray($result) + : $result; + } + + /** + * Retrieves the number of all members of a status + * + * @param String|Array $status the status to filter with + * + * @return int the number of all those members. + */ + public function countMembersWithStatus($status) + { + return CourseMember::countByCourseAndStatus($this->id, $status); + } + + public function getNumParticipants() + { + return $this->countMembersWithStatus('user autor') + $this->getNumPrelimParticipants(); + } + + public function getNumPrelimParticipants() + { + return AdmissionApplication::countBySql( + "seminar_id = ? AND status = 'accepted'", + [$this->id] + ); + } + + public function getNumWaiting() + { + return AdmissionApplication::countBySql( + "seminar_id = ? AND status = 'awaiting'", + [$this->id] + ); + } + + public function getParticipantStatus($user_id) + { + $p_status = $this->members->findBy('user_id', $user_id)->val('status'); + if (!$p_status) { + $p_status = $this->admission_applicants->findBy('user_id', $user_id)->val('status'); + } + return $p_status; + } + + /** + * Returns the semType object that is defined for the course + * + * @return SemType The semTypeObject for the course + */ + public function getSemType() + { + $semTypes = SemType::getTypes(); + if (isset($semTypes[$this->status])) { + return $semTypes[$this->status]; + } + + Log::error(sprintf('SemType not found id:%s status:%s', $this->id, $this->status)); + return new SemType(['name' => 'Fehlerhafter Veranstaltungstyp']); + } + + /** + * Returns the SemClass object that is defined for the course + * + * @return SemClass The SemClassObject for the course + */ + public function getSemClass() + { + return $this->getSemType()->getClass(); + } + + /** + * Returns the full name of a course. If the important course numbers + * (IMPORTANT_SEMNUMBER) is set in global configs it will also display + * the coursenumber + * + * @param string formatting template name + * @return string Fullname + */ + public function getFullName($format = 'default') + { + $template = [ + 'name' => '%1$s', + 'name-semester' => '%1$s (%4$s)', + 'number-name' => '%3$s %1$s', + 'number-name-semester' => '%3$s %1$s (%4$s)', + 'number-type-name' => '%3$s %2$s: %1$s', + 'sem-duration-name' => '%4$s', + 'type-name' => '%2$s: %1$s', + 'type-number-name' => '%2$s: %3$s %1$s', + ]; + + if ($format === 'default' || !isset($template[$format])) { + $format = Config::get()->IMPORTANT_SEMNUMBER ? 'type-number-name' : 'type-name'; + } + $sem_type = $this->getSemType(); + $data[0] = $this->name; + $data[1] = $sem_type['name']; + $data[2] = $this->veranstaltungsnummer; + $data[3] = $this->getTextualSemester(); + return trim(vsprintf($template[$format], array_map('trim', $data))); + } + + + /** + * Retrieves the course dates including cancelled dates ("ex-dates"). + * The dates can be filtered by an optional time range. By default, + * all dates are retrieved. + * + * @param int $range_begin The begin timestamp of the time range. + * + * @param int $range_end The end timestamp of the time range. + * + * @returns SimpleCollection A collection of all retrieved dates and + * cancelled dates. + */ + public function getDatesWithExdates($range_begin = 0, $range_end = 0) + { + $dates = []; + if (($range_begin > 0) && ($range_end > 0) && ($range_end > $range_begin)) { + $ex_dates = $this->ex_dates->findBy('content', '', '<>') + ->findBy('date', $range_begin, '>=') + ->findBy('end_time', $range_end, '<='); + $dates = $this->dates->findBy('date', $range_begin, '>=') + ->findBy('end_time', $range_end, '<='); + $dates->merge($ex_dates); + } else { + $dates = $this->ex_dates->findBy('content', '', '<>'); + $dates->merge($this->dates); + } + $dates->uasort(function($a, $b) { + return $a->date - $b->date + ?: strnatcasecmp($a->getRoomName(), $b->getRoomName()); + }); + return $dates; + } + + /** + * Sets this courses study areas to the given values. + * + * @param array $ids the new study areas + * @return bool Changes successfully saved? + */ + public function setStudyAreas($ids) + { + $old = $this->study_areas->pluck('sem_tree_id'); + $added = array_diff($ids, $old); + $removed = array_diff($old, $ids); + $success = false; + if ($added || $removed) { + + $this->study_areas = SimpleCollection::createFromArray(StudipStudyArea::findMany($ids)); + + if ($this->store()) { + NotificationCenter::postNotification('CourseDidChangeStudyArea', $this); + $success = true; + + foreach ($added as $one) { + StudipLog::log('SEM_ADD_STUDYAREA', $this->id, $one); + + $area = $this->study_areas->find($one); + if ($area->isModule()) { + NotificationCenter::postNotification( + 'CourseAddedToModule', + $area, + ['module_id' => $one, 'course_id' => $this->id] + ); + } + } + + foreach ($removed as $one) { + StudipLog::log('SEM_DELETE_STUDYAREA', $this->id, $one); + + $area = StudipStudyArea::find($one); + if ($area->isModule()) { + NotificationCenter::postNotification( + 'CourseRemovedFromModule', + $area, + ['module_id' => $one, 'course_id' => $this->id] + ); + } + } + } + } + + return $success; + } + + /** + * Is the current course visible for the current user? + * @param string $user_id + * @return bool Visible? + */ + public function isVisibleForUser($user_id = null) + { + return $this->visible + || $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM, $user_id) + || $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id); + } + + /** + * Returns a descriptive text for the range type. + * + * @return string + */ + public function describeRange() + { + return _('Veranstaltung'); + } + + /** + * Returns a unique identificator for the range type. + * + * @return string + */ + public function getRangeType() + { + return 'course'; + } + + /** + * Returns the id of the current range + * + * @return string + */ + public function getRangeId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() + { + return CourseConfig::get($this); + } + + /** + * Decides whether the user may access the range. + * + * @param string|null $user_id Optional id of a user, defaults to current user + * @return bool + * @todo Check permissions + */ + public function isAccessibleToUser($user_id = null) + { + if ($user_id === null) { + $user_id = $GLOBALS['user']->id; + } + return $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id); + } + + /** + * Decides whether the user may edit/alter the range. + * + * @param string|null $user_id Optional id of a user, defaults to current user + * @return bool + * @todo Check permissions + */ + public function isEditableByUser($user_id = null) + { + if ($user_id === null) { + $user_id = $GLOBALS['user']->id; + } + return $GLOBALS['perm']->have_studip_perm('tutor', $this->id, $user_id); + } + + /** + * Returns the appropriate icon for the completion status. + * + * Mapping (completion -> icon role): + * - 0 => status-red + * - 1 => status-yellow + * - 2 => status-green + * + * @return Icon class + */ + public function getCompletionIcon() + { + $role = Icon::ROLE_STATUS_RED; + if ($this->completion == 1) { + $role = Icon::ROLE_STATUS_YELLOW; + } elseif ($this->completion == 2) { + $role = Icon::ROLE_STATUS_GREEN; + } + return Icon::create('radiobutton-checked', $role); + } + + /** + * Returns the appropriate label for the completion status. + * + * @return string + */ + public function getCompetionLabel(): string + { + return [ + 0 => _('unvollständig'), + 1 => _('in Bearbeitung'), + 2 => _('fertig'), + ][$this->completion] ?? _('undefiniert'); + } + + /** + * Generates a general log entry if the course were changed. + * Furthermore, this method emits notifications when the + * start and/or the end semester has/have changed. + */ + protected function logStore() + { + if ($this->isFieldDirty('start_time')) { + //Log change of start semester: + StudipLog::log('SEM_SET_STARTSEMESTER', $this->id, isset($this->start_semester) ? $this->start_semester->name : _('unbegrenzt')); + NotificationCenter::postNotification('CourseDidChangeSchedule', $this); + } + if ($this->isFieldDirty('duration_time')) { + StudipLog::log('SEM_SET_ENDSEMESTER', $this->id, $this->getTextualSemester()); + NotificationCenter::postNotification('CourseDidChangeSchedule', $this); + } + + $log = []; + if ($this->isFieldDirty('admission_prelim')) { + $log[] = $this->admission_prelim ? _('Neuer Anmeldemodus: Vorläufiger Eintrag') : _('Neuer Anmeldemodus: Direkter Eintrag'); + } + + if ($this->isFieldDirty('admission_binding')) { + $log[] = $this->admission_binding? _('Anmeldung verbindlich') : _('Anmeldung unverbindlich'); + } + + if ($this->isFieldDirty('admission_turnout')) { + $log[] = sprintf(_('Neue Teilnehmerzahl: %s'), (int)$this->admission_turnout); + } + + if ($this->isFieldDirty('admission_disable_waitlist')) { + $log[] = $this->admission_disable_waitlist ? _('Warteliste aktiviert') : _('Warteliste deaktiviert'); + } + + if ($this->isFieldDirty('admission_waitlist_max')) { + $log[] = sprintf(_('Plätze auf der Warteliste geändert: %u'), (int)$this->admission_waitlist_max); + } + + if ($this->isFieldDirty('admission_disable_waitlist_move')) { + $log[] = $this->admission_disable_waitlist ? _('Nachrücken aktiviert') : _('Nachrücken deaktiviert'); + } + + if ($this->isFieldDirty('admission_prelim_txt')) { + if ($this->admission_prelim_txt) { + $log[] = sprintf(_('Neuer Hinweistext bei vorläufigen Eintragungen: %s'), strip_tags(kill_format($this->admission_prelim_txt))); + } else { + $log[] = _('Hinweistext bei vorläufigen Eintragungen wurde entfert'); + } + } + + if (!empty($log)) { + StudipLog::log( + 'SEM_CHANGED_ACCESS', + $this->id, + null, + '', + implode(' - ', $log) + ); + } + + if ($this->isFieldDirty('visible')) { + StudipLog::log($this->visible ? 'SEM_VISIBLE' : 'SEM_INVISIBLE', $this->id); + } + } + + /** + * Called directly before storing the object to edit the columns start_time and duration_time + * which are both deprecated but are still in use for older plugins. + */ + public function cbSetStartAndDurationTime() + { + if ($this->isFieldDirty('start_time')) { + $this->setStartSemester(Semester::findByTimestamp($this->start_time)); + } + if ($this->isFieldDirty('duration_time')) { + $this->setEndSemester($this->duration_time == -1 ? null : Semester::findByTimestamp($this->start_time + $this->duration_time)); + } + if ($this->isOpenEnded()) { + $this->start_time = $this->start_time ?: Semester::findCurrent()->beginn ?? time(); + $this->duration_time = -1; + } else { + $this->start_time = $this->getStartSemester()->beginn; + $this->duration_time = $this->getEndSemester()->beginn - $this->start_time; + } + } + + + //StudipItem interface implementation: + + public function getItemName($long_format = true) + { + if ($long_format) { + return $this->getFullName(); + } else { + return $this->name; + } + } + + public function getItemURL() + { + return URLHelper::getURL( + 'dispatch.php/course/details/index', + [ + 'cid' => $this->id + ] + ); + } + + public function getItemAvatarURL() + { + $avatar = CourseAvatar::getAvatar($this->id); + if ($avatar) { + return $avatar->getURL(Avatar::NORMAL); + } + return ''; + } + + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findThru($storage->user_id, [ + 'thru_table' => 'seminar_user', + 'thru_key' => 'user_id', + 'thru_assoc_key' => 'Seminar_id', + 'assoc_foreign_key' => 'Seminar_id', + ]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Seminare'), 'seminare', $field_data); + } + } + } + public function getRangeName() + { + return $this->name; + } + + public function getRangeIcon($role) + { + return Icon::create('seminar', $role); + } + + public function getRangeUrl() + { + return 'course/overview'; + } + + public function getRangeCourseId() + { + return $this->Seminar_id; + } + + public function isRangeAccessible(string $user_id = null): bool + { + $user_id = $user_id ?? $GLOBALS['user']->id; + return $GLOBALS['perm']->have_studip_perm('autor', $this->Seminar_id, $user_id); + } + + + public function getLink() : StudipLink + { + return new StudipLink($this->getItemURL(), $this->name, Icon::create('seminar')); + } + + + /** + * Returns a list of courses for the specified user. + * Permission levels may be supplied to limit the course list. + * + * @param string $user_id The ID of the user whose courses shall be retrieved. + * + * @param string[] $perms The permission levels of the user that shall be + * regarded when retrieving courses. + * + * @param bool $with_deputies Whether to include courses where the user is + * a deputy (true) or not (false). Defaults to true. + * + * @return Course[] A list of courses. + */ + public static function findByUser($user_id, $perms = [], $with_deputies = true) + { + if (!$user_id) { + return []; + } + + $db = DBManager::get(); + $sql = "SELECT `seminar_id` + FROM `seminar_user` + WHERE `user_id` = :user_id"; + $sql_params = ['user_id' => $user_id]; + if (is_array($perms) && count($perms)) { + $sql .= ' AND `status` IN (:perms)'; + $sql_params['perms'] = $perms; + } + $seminar_ids = $db->fetchFirst($sql, $sql_params); + if (Config::get()->DEPUTIES_ENABLE && $with_deputies) { + $sql = 'SELECT range_id FROM `deputies` WHERE `deputies`.`user_id` = :user_id'; + $seminar_ids = array_merge($seminar_ids, $db->fetchFirst($sql, $sql_params)); + } + + $name_sort = Config::get()->IMPORTANT_SEMNUMBER ? 'VeranstaltungsNummer, Name' : 'Name'; + + return Course::findBySQL( + "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id) + WHERE Seminar_id IN (?) + GROUP BY seminare.Seminar_id + ORDER BY semester_courses.semester_id IS NULL DESC, start_time DESC, {$name_sort}", + [$seminar_ids] + ); + } + + /** + * Returns whether this course is a studygroup + * @return bool + */ + public function isStudygroup() + { + return in_array($this->status, studygroup_sem_types()); + } + + /** + * + */ + public function setDefaultTools() + { + $this->tools = []; + foreach (array_values($this->getSemClass()->getActivatedModuleObjects()) as $module) { + PluginManager::getInstance()->setPluginActivated($module->getPluginId(), $this->id, true); + $this->tools[] = ToolActivation::find([$this->id, $module->getPluginId()]); + } + } + + /** + * @param $name string name of tool / plugin + * @return bool + */ + public function isToolActive($name) + { + $plugin = PluginEngine::getPlugin($name); + return $plugin && $this->tools->findOneby('plugin_id', $plugin->getPluginId()); + } + + /** + * returns all activated plugins/modules for this course + * @return StudipModule[] + */ + public function getActivatedTools() + { + return array_filter($this->tools->getStudipModule()); + } + + /** + * @see Range::__toString() + */ + public function __toString() : string + { + return $this->getFullName(); + } + + /** + * @inheritDoc + */ + public static function getCalendarOwner(string $owner_id): ?\Studip\Calendar\Owner + { + return self::find($owner_id); + } + + /** + * @inheritDoc + */ + public function isCalendarReadable(?string $user_id = null): bool + { + if ($user_id === null) { + $user_id = User::findCurrent()->id; + } + + //Calendar read permissions are granted for all participants + //that have at least user permissions. + return $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id); + } + + /** + * @inheritDoc + */ + public function isCalendarWritable(string $user_id = null): bool + { + if ($user_id === null) { + $user_id = User::findCurrent()->id; + } + + //Calendar write permissions are granted for all participants + //that have autor permissions or higher. + return $GLOBALS['perm']->have_studip_perm('autor', $this->id, $user_id); + } + + /** + * Get user information for all users in this course + * + */ + public function getMembersData(?string $status = ''): array + { + $result = []; + + if (!$status) { + foreach ($this->members->orderBy('position, nachname') as $member) { + $result[$member->user_id] = $member->getExportData(); + } + foreach ($this->admission_applicants->findBy('status', 'accepted')->orderBy('position') as $member) { + $result[$member->user_id] = $member->getExportData(); + } + } elseif ($status === 'awaiting') { + foreach ($this->admission_applicants->findBy('status', $status)->orderBy('position') as $member) { + $result[$member->user_id] = $member->getExportData(); + } + } elseif ($status === 'claiming') { + $cs = CourseSet::getSetForCourse($this->id); + if (is_object($cs) && !$cs->hasAlgorithmRun()) { + $claiming_users = User::findFullMany(array_keys(AdmissionPriority::getPrioritiesByCourse($cs->getId(), $this->id)), 'ORDER BY nachname'); + foreach ($claiming_users as $claiming_user) { + $studycourse = []; + $claiming_user->studycourses->map(function($sc) use (&$studycourse) { + $studycourse[]= $sc->studycourse->name . ',' . $sc->degree->name . ',' . $sc->semester; + }); + $export_data = [ + 'status' => $status, + 'salutation' => $claiming_user->salutation, + 'Titel' => $claiming_user->title_front, + 'Vorname' => $claiming_user->vorname, + 'Nachname' => $claiming_user->nachname, + 'Titel2' => $claiming_user->title_rear, + 'username' => $claiming_user->username, + 'privadr' => $claiming_user->privadr, + 'privatnr' => $claiming_user->privatnr, + 'Email' => $claiming_user->email, + 'Anmeldedatum' => '', + 'Matrikelnummer' => $claiming_user->matriculation_number, + 'studiengaenge' => implode(';', $studycourse), + 'position' => 0, + ]; + $result[$claiming_user->user_id] = $export_data; + } + } + } + + return $result; + } + +} diff --git a/lib/models/CourseDate.class.php b/lib/models/CourseDate.class.php deleted file mode 100644 index eadeb0a..0000000 --- a/lib/models/CourseDate.class.php +++ /dev/null @@ -1,665 +0,0 @@ - - * @copyright 2014 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for termin_id - * @property string $termin_id database column - * @property string $range_id database column - * @property string $autor_id database column - * @property string $content database column - * @property int $date database column - * @property int $end_time database column - * @property int $mkdate database column - * @property int $chdate database column - * @property int $date_typ database column - * @property string|null $raum database column - * @property string|null $metadate_id database column - * @property SimpleORMapCollection|Folder[] $folders has_many Folder - * @property SimpleORMapCollection|RoomRequest[] $room_requests has_many RoomRequest - * @property SimpleORMapCollection|ResourceRequestAppointment[] $resource_request_appointments has_many ResourceRequestAppointment - * @property User $author belongs_to User - * @property Course $course belongs_to Course - * @property SeminarCycleDate|null $cycle belongs_to SeminarCycleDate - * @property ResourceBooking $room_booking has_one ResourceBooking - * @property SimpleORMapCollection|CourseTopic[] $topics has_and_belongs_to_many CourseTopic - * @property SimpleORMapCollection|Statusgruppen[] $statusgruppen has_and_belongs_to_many Statusgruppen - * @property SimpleORMapCollection|User[] $dozenten has_and_belongs_to_many User - */ - -class CourseDate extends SimpleORMap implements PrivacyObject, Event -{ - const FORMAT_DEFAULT = 'default'; - const FORMAT_VERBOSE = 'verbose'; - - private static $numbered_dates = null; - - /** - * Configures this model. - * - * @param Array $config Configuration array - */ - protected static function configure($config = []) - { - $config['db_table'] = 'termine'; - $config['has_and_belongs_to_many']['topics'] = [ - 'class_name' => CourseTopic::class, - 'thru_table' => 'themen_termine', - 'order_by' => 'ORDER BY priority', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - $config['has_and_belongs_to_many']['statusgruppen'] = [ - 'class_name' => Statusgruppen::class, - 'thru_table' => 'termin_related_groups', - 'order_by' => 'ORDER BY position', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - $config['has_and_belongs_to_many']['dozenten'] = [ - 'class_name' => User::class, - 'thru_table' => 'termin_related_persons', - 'foreign_key' => 'termin_id', - 'thru_key' => 'range_id', - 'order_by' => 'ORDER BY Nachname, Vorname', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - $config['has_many']['folders'] = [ - 'class_name' => Folder::class, - 'assoc_func' => 'findByTermin_id' - ]; - $config['belongs_to']['author'] = [ - 'class_name' => User::class, - 'foreign_key' => 'autor_id' - ]; - $config['belongs_to']['course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'range_id' - ]; - $config['belongs_to']['cycle'] = [ - 'class_name' => SeminarCycleDate::class, - 'foreign_key' => 'metadate_id' - ]; - $config['has_one']['room_booking'] = [ - 'class_name' => ResourceBooking::class, - 'foreign_key' => 'termin_id', - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - //'on_store' => 'store' - ]; - $config['has_many']['room_requests'] = [ - 'class_name' => RoomRequest::class, - 'assoc_foreign_key' => 'termin_id', - 'on_delete' => 'delete', - ]; - $config['has_many']['resource_request_appointments'] = [ - 'class_name' => ResourceRequestAppointment::class, - 'assoc_foreign_key' => 'appointment_id', - 'on_delete' => 'delete', - ]; - $config['default_values']['date_typ'] = 1; - $config['registered_callbacks']['before_store'][] = 'cbStudipLog'; - $config['registered_callbacks']['after_create'][] = 'cbStudipLog'; - parent::configure($config); - } - - /** - * return consecutive number for a date in its course, if semester is given - * only within that time range - * - * @param CourseDate $date - * @param null|Semester $semester - * @return int|null - */ - public static function getConsecutiveNumber($date, $semester = null) - { - $semester_id = $semester ? $semester->id : 'all'; - - if (!isset(self::$numbered_dates[$semester_id])) { - $db = DBManager::get(); - $numbered = array_flip($db->fetchFirst("SELECT termin_id FROM termine WHERE range_id = ?" . - ($semester ? " AND date BETWEEN ? AND ?" : "") . - " ORDER BY date", - $semester ? [$date->range_id, $semester->beginn, $semester->ende] : [$date->range_id])); - self::$numbered_dates[$semester_id] = $numbered; - } - return isset(self::$numbered_dates[$semester_id][$date->termin_id]) - ? self::$numbered_dates[$semester_id][$date->termin_id] + 1 - : null; - } - - /** - * Returns course dates by issue id. - * - * @param String $issue_id Id of the issue - * @return array with the associated dates - */ - public static function findByIssue_id($issue_id) - { - return self::findBySQL("INNER JOIN themen_termine USING (termin_id) - WHERE themen_termine.issue_id = ? - ORDER BY date ASC", - [$issue_id] - ); - } - - /** - * Returns course dates by course id - * - * @param String $seminar_id Id of the course - * @return array with the associated dates - */ - public static function findBySeminar_id($seminar_id) - { - return self::findByRange_id($seminar_id); - } - - /** - * Return course dates by range id (which is in many cases the course id) - * - * @param String $seminar_id Id of the course - * @param String $order_by Optional order definition - * @return array with the associated dates - */ - public static function findByRange_id($seminar_id, $order_by = 'ORDER BY date') - { - return parent::findByRange_id($seminar_id, $order_by); - } - - /** - * Returns course dates by issue id. - * - * @param String $issue_id Id of the issue - * @return array with the associated dates - */ - public static function findByStatusgruppe_id($group_id) - { - return self::findBySQL("INNER JOIN `termin_related_groups` USING (`termin_id`) - WHERE `termin_related_groups`.`statusgruppe_id` = ? - ORDER BY `date` ASC", - [$group_id] - ); - } - - /** - * Adds a topic to this date. - * - * @param mixed $topic Topic definition (might be an id, an array or an - * object) - * @return int|false number addition of all return values, false if none was called - */ - public function addTopic($topic) - { - $topic = CourseTopic::toObject($topic); - if (!$this->topics->find($topic->id)) { - $this->topics[] = $topic; - return $this->storeRelations('topics'); - } - return false; - } - - /** - * Removes a topic from this date. - * - * @param mixed $topic Topic definition (might be an id, an array or an - * object) - * @return number addition of all return values, false if none was called - */ - public function removeTopic($topic) - { - $this->topics->unsetByPk(is_string($topic) ? $topic : $topic->id); - return $this->storeRelations('topics'); - } - - /** - * Returns the name of the assigned room for this date. - * - * @return String containing the room name - */ - public function getRoomName() - { - if (Config::get()->RESOURCES_ENABLE && !empty($this->room_booking->resource)) { - return $this->room_booking->resource->name; - } - return $this['raum']; - } - - /** - * Returns the assigned room for this date as an object. - * - * @return Room Either the object or null if no room is assigned - */ - public function getRoom() - { - if (Config::get()->RESOURCES_ENABLE && !empty($this->room_booking->resource)) { - return $this->room_booking->resource->getDerivedClassInstance(); - } - return null; - } - - /** - * Returns the name of the type of this date. - * - * @param String containing the type name - */ - public function getTypeName() - { - return $GLOBALS['TERMIN_TYP'][$this->date_typ]['name'] ?? ''; - } - - /** - * Returns the full qualified name of this date. - * - * @param String $format Optional format type (only 'default', 'include-room' and - * 'verbose' are supported by now) - * @return String containing the full name of this date. - */ - public function getFullName($format = 'default') - { - if (!$this->date || !in_array($format, ['default', 'verbose', 'include-room'])) { - return ''; - } - - $latter_template = $format === 'verbose' ? _('%R Uhr') : '%R'; - - if (($this->end_time - $this->date) / 60 / 60 > 23) { - $string = strftime('%a., %x (' . _('ganztägig') . ')' , $this->date); - } else { - $string = strftime('%a., %x, %R', $this->date) . ' - ' - . strftime($latter_template, $this->end_time); - } - - if($format === 'include-room') { - $room = $this->getRoom(); - if($room) { - $string = sprintf('%s %s', - $string, - $room->getActionURL('booking_plan'), - htmlReady($room->name) - ); - } - } - return $string; - } - - /** - * Converts a CourseDate Entry to a CourseExDate Entry - * returns instance of the new CourseExDate or NULL - * - * @return Object CourseExDate - */ - public function cancelDate() - { - //NOTE: If you modify this method make sure the changes - //are also inserted in SingleDateDB::storeSingleDate - //and CourseExDate::unCancelDate to keep the behavior consistent - //across Stud.IP! - - //These statements are used below to update the relations - //of this date. - $db = DBManager::get(); - - $groups_stmt = $db->prepare( - "UPDATE termin_related_groups - SET termin_id = :ex_termin_id - WHERE termin_id = :termin_id;" - ); - - $persons_stmt = $db->prepare( - "UPDATE termin_related_persons - SET range_id = :ex_termin_id - WHERE range_id = :termin_id;" - ); - - $date = $this->toArray(); - - $ex_date = new CourseExDate(); - $ex_date->setData($date); - if ($room = $this->getRoom()) { - $ex_date['resource_id'] = $room->getId(); - } - $ex_date->setId($ex_date->getNewId()); - - if ($ex_date->store()) { - //Update some (but not all) relations to the date so that they - //use the ID of the new ex-date. - - $groups_stmt->execute( - [ - 'ex_termin_id' => $ex_date->id, - 'termin_id' => $this->id - ] - ); - - $persons_stmt->execute( - [ - 'ex_termin_id' => $ex_date->id, - 'termin_id' => $this->id - ] - ); - - //After we updated the relations so that they refer to the - //new ex-date we can delete this date and return the ex-date: - $this->delete(); - return $ex_date; - } - return null; - } - - /** - * saves this object and expires the cache - * - * @see SimpleORMap::store() - */ - public function store() - { - // load room-booking, if any - $this->room_booking; - - $cache = \Studip\Cache\Factory::getCache(); - $cache->expire('course/undecorated_data/'. $this->range_id); - return parent::store(); - } - - /** - * deletes this object and expires the cache - * - * @see SimpleORMap::delete() - */ - public function delete() - { - $cache = \Studip\Cache\Factory::getCache(); - $cache->expire('course/undecorated_data/'. $this->range_id); - return parent::delete(); - } - - /** - * @param $type string type of callback - */ - protected function cbStudipLog($type) - { - if (!$this->metadate_id) { - if ($type === 'after_create') { - StudipLog::log('SEM_ADD_SINGLEDATE', $this->range_id, $this->id, $this->getFullName()); - } - if ($type === 'before_store' && !$this->isNew() && ($this->isFieldDirty('date') || $this->isFieldDirty('end_time'))) { - $old_entry = self::build($this->content_db); - StudipLog::log('SINGLEDATE_CHANGE_TIME', $this->range_id, $this->id, $old_entry->getFullName() . ' -> ' . $this->getFullName()); - } - } - } - - /** - * Returns a list of all possible warnings that should be considered when - * this date is deleted. - * - * @return array of warnings - */ - public function getDeletionWarnings() - { - $warnings = []; - if (count($this->topics) > 0) { - $warnings[] = _('Diesem Termin ist ein Thema zugeordnet.'); - } - - if (Config::get()->RESOURCES_ENABLE && $this->getRoom()) { - $warnings[] = _('Dieser Termin hat eine Raumbuchung, welche mit dem Termin gelöscht wird.'); - } - - return $warnings; - } - - /** - * return all filerefs belonging to this date, permissions fpr given user are checked - * - * @param string|User $user_or_id - * @return mixed[] A mixed array with FolderType and FileRef objects. - */ - public function getAccessibleFolderFiles($user_or_id) - { - $user_id = $user_or_id instanceof User ? $user_or_id->id : $user_or_id; - $all_files = []; - $all_folders = []; - $folders = $this->folders->getArrayCopy(); - foreach ($this->topics as $topic) { - $folders = array_merge($folders, $topic->folders->getArrayCopy()); - } - foreach ($folders as $folder) { - [$files, $typed_folders] = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); - foreach ($files as $file) { - $all_files[$file->id] = $file; - } - $all_folders = array_merge($all_folders, $typed_folders); - } - return ['files' => $all_files, 'folders' => $all_folders]; - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL("autor_id = ?", [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Termine'), 'termine', $field_data); - } - } - } - - - /** - * @return string A string representation of the course date. - */ - public function __toString() : string - { - return sprintf( - _('Termin am %1$s, %2$s von %3$s bis %4$s Uhr'), - strftime('%A', $this->date), - strftime('%x', $this->date), - date('H:i', $this->date), - date('H:i', $this->end_time) - ); - } - - //Start of Event interface implementation. - - public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array - { - return self::findBySQL( - "JOIN `seminar_user` - ON `seminar_user`.`seminar_id` = `termine`.`range_id` - WHERE `seminar_user`.`user_id` = :user_id - AND `termine`.`date` BETWEEN :begin AND :end - AND ( - IFNULL(`termine`.`metadate_id`, '') = '' - OR `termine`.`metadate_id` NOT IN ( - SELECT `metadate_id` - FROM `schedule_seminare` - WHERE `user_id` = :user_id - AND `visible` = 0 - ) - ) - ORDER BY date", - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp(), - 'user_id' => $range_id - ] - ); - } - - //Event interface implementation: - - public function getObjectId() : string - { - return (string) $this->id; - } - - public function getPrimaryObjectID(): string - { - return $this->range_id; - } - - public function getObjectClass(): string - { - return static::class; - } - - public function getTitle(): string - { - return $this->course->name ?? ''; - } - - public function getBegin(): DateTime - { - $begin = new DateTime(); - $begin->setTimestamp($this->date); - return $begin; - } - - public function getEnd(): DateTime - { - $end = new DateTime(); - $end->setTimestamp($this->end_time); - return $end; - } - - public function getDuration(): DateInterval - { - $begin = $this->getBegin(); - $end = $this->getEnd(); - return $end->diff($begin); - } - - public function getLocation(): string - { - return $this->raum ?? ''; - } - - public function getUniqueId(): string - { - return sprintf('Stud.IP-SEM-%1$s@%2$s', $this->id, $_SERVER['SERVER_NAME']); - } - - public function getDescription(): string - { - $descriptions = $this->topics->map(function ($topic) { - $desc = $topic->title . "\n"; - $desc .= $topic->description; - - return $desc; - }); - return implode("\n\n", $descriptions); - } - - public function getAdditionalDescriptions(): array - { - $descriptions = []; - if (count($this->dozenten) > 0) { - $descriptions[_('Durchführende Lehrende')] = implode(', ', $this->dozenten->getFullName()); - } - if (count($this->statusgruppen) > 0) { - $descriptions[_('Beteiligte Gruppen')] = implode(', ', $this->statusgruppen->getValue('name')); - } - return $descriptions; - } - - public function isAllDayEvent(): bool - { - //Course dates are never all day events. - return false; - } - - public function isWritable(string $user_id): bool - { - return $GLOBALS['perm']->have_studip_perm('dozent', $this->range_id, $user_id); - } - - public function getCreationDate(): DateTime - { - $mkdate = new DateTime(); - $mkdate->setTimestamp($this->mkdate); - return $mkdate; - } - - public function getModificationDate(): DateTime - { - $chdate = new DateTime(); - $chdate->setTimestamp($this->chdate); - return $chdate; - } - - public function getImportDate(): DateTime - { - return $this->getCreationDate(); - } - - public function getAuthor(): ?User - { - return $this->author; - } - - public function getEditor(): ?User - { - return null; - } - - public function toEventData(string $user_id): \Studip\Calendar\EventData - { - $begin = new DateTime(); - $begin->setTimestamp($this->date); - $end = new DateTime(); - $end->setTimestamp($this->end_time); - - $membership = CourseMember::findOneBySQL( - 'seminar_id = :course_id AND user_id = :user_id', - ['course_id' => $this->range_id, 'user_id' => $user_id] - ); - $class_names = []; - if ($membership) { - $class_names[] = 'course-color-' . $membership->gruppe; - } - $studip_view_urls = []; - if ($GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user_id)) { - $studip_view_urls['show'] = URLHelper::getURL('dispatch.php/course/dates/details/' . $this->id, ['cid' => $this->range_id, 'extra_buttons' => '1']); - } - - return new \Studip\Calendar\EventData( - $begin, - $end, - $this->getTitle(), - $class_names, - '#000000', - '#aaaaaa', - $this->isWritable($user_id), - CourseDate::class, - $this->id, - Course::class, - $this->range_id, - 'course', - $this->range_id, - $studip_view_urls, - [], - 'seminar', - 'rgba(0,0,0,0)' - ); - } - - //End of Event interface implementation. -} diff --git a/lib/models/CourseDate.php b/lib/models/CourseDate.php new file mode 100644 index 0000000..eadeb0a --- /dev/null +++ b/lib/models/CourseDate.php @@ -0,0 +1,665 @@ + + * @copyright 2014 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for termin_id + * @property string $termin_id database column + * @property string $range_id database column + * @property string $autor_id database column + * @property string $content database column + * @property int $date database column + * @property int $end_time database column + * @property int $mkdate database column + * @property int $chdate database column + * @property int $date_typ database column + * @property string|null $raum database column + * @property string|null $metadate_id database column + * @property SimpleORMapCollection|Folder[] $folders has_many Folder + * @property SimpleORMapCollection|RoomRequest[] $room_requests has_many RoomRequest + * @property SimpleORMapCollection|ResourceRequestAppointment[] $resource_request_appointments has_many ResourceRequestAppointment + * @property User $author belongs_to User + * @property Course $course belongs_to Course + * @property SeminarCycleDate|null $cycle belongs_to SeminarCycleDate + * @property ResourceBooking $room_booking has_one ResourceBooking + * @property SimpleORMapCollection|CourseTopic[] $topics has_and_belongs_to_many CourseTopic + * @property SimpleORMapCollection|Statusgruppen[] $statusgruppen has_and_belongs_to_many Statusgruppen + * @property SimpleORMapCollection|User[] $dozenten has_and_belongs_to_many User + */ + +class CourseDate extends SimpleORMap implements PrivacyObject, Event +{ + const FORMAT_DEFAULT = 'default'; + const FORMAT_VERBOSE = 'verbose'; + + private static $numbered_dates = null; + + /** + * Configures this model. + * + * @param Array $config Configuration array + */ + protected static function configure($config = []) + { + $config['db_table'] = 'termine'; + $config['has_and_belongs_to_many']['topics'] = [ + 'class_name' => CourseTopic::class, + 'thru_table' => 'themen_termine', + 'order_by' => 'ORDER BY priority', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + $config['has_and_belongs_to_many']['statusgruppen'] = [ + 'class_name' => Statusgruppen::class, + 'thru_table' => 'termin_related_groups', + 'order_by' => 'ORDER BY position', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + $config['has_and_belongs_to_many']['dozenten'] = [ + 'class_name' => User::class, + 'thru_table' => 'termin_related_persons', + 'foreign_key' => 'termin_id', + 'thru_key' => 'range_id', + 'order_by' => 'ORDER BY Nachname, Vorname', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + $config['has_many']['folders'] = [ + 'class_name' => Folder::class, + 'assoc_func' => 'findByTermin_id' + ]; + $config['belongs_to']['author'] = [ + 'class_name' => User::class, + 'foreign_key' => 'autor_id' + ]; + $config['belongs_to']['course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'range_id' + ]; + $config['belongs_to']['cycle'] = [ + 'class_name' => SeminarCycleDate::class, + 'foreign_key' => 'metadate_id' + ]; + $config['has_one']['room_booking'] = [ + 'class_name' => ResourceBooking::class, + 'foreign_key' => 'termin_id', + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + //'on_store' => 'store' + ]; + $config['has_many']['room_requests'] = [ + 'class_name' => RoomRequest::class, + 'assoc_foreign_key' => 'termin_id', + 'on_delete' => 'delete', + ]; + $config['has_many']['resource_request_appointments'] = [ + 'class_name' => ResourceRequestAppointment::class, + 'assoc_foreign_key' => 'appointment_id', + 'on_delete' => 'delete', + ]; + $config['default_values']['date_typ'] = 1; + $config['registered_callbacks']['before_store'][] = 'cbStudipLog'; + $config['registered_callbacks']['after_create'][] = 'cbStudipLog'; + parent::configure($config); + } + + /** + * return consecutive number for a date in its course, if semester is given + * only within that time range + * + * @param CourseDate $date + * @param null|Semester $semester + * @return int|null + */ + public static function getConsecutiveNumber($date, $semester = null) + { + $semester_id = $semester ? $semester->id : 'all'; + + if (!isset(self::$numbered_dates[$semester_id])) { + $db = DBManager::get(); + $numbered = array_flip($db->fetchFirst("SELECT termin_id FROM termine WHERE range_id = ?" . + ($semester ? " AND date BETWEEN ? AND ?" : "") . + " ORDER BY date", + $semester ? [$date->range_id, $semester->beginn, $semester->ende] : [$date->range_id])); + self::$numbered_dates[$semester_id] = $numbered; + } + return isset(self::$numbered_dates[$semester_id][$date->termin_id]) + ? self::$numbered_dates[$semester_id][$date->termin_id] + 1 + : null; + } + + /** + * Returns course dates by issue id. + * + * @param String $issue_id Id of the issue + * @return array with the associated dates + */ + public static function findByIssue_id($issue_id) + { + return self::findBySQL("INNER JOIN themen_termine USING (termin_id) + WHERE themen_termine.issue_id = ? + ORDER BY date ASC", + [$issue_id] + ); + } + + /** + * Returns course dates by course id + * + * @param String $seminar_id Id of the course + * @return array with the associated dates + */ + public static function findBySeminar_id($seminar_id) + { + return self::findByRange_id($seminar_id); + } + + /** + * Return course dates by range id (which is in many cases the course id) + * + * @param String $seminar_id Id of the course + * @param String $order_by Optional order definition + * @return array with the associated dates + */ + public static function findByRange_id($seminar_id, $order_by = 'ORDER BY date') + { + return parent::findByRange_id($seminar_id, $order_by); + } + + /** + * Returns course dates by issue id. + * + * @param String $issue_id Id of the issue + * @return array with the associated dates + */ + public static function findByStatusgruppe_id($group_id) + { + return self::findBySQL("INNER JOIN `termin_related_groups` USING (`termin_id`) + WHERE `termin_related_groups`.`statusgruppe_id` = ? + ORDER BY `date` ASC", + [$group_id] + ); + } + + /** + * Adds a topic to this date. + * + * @param mixed $topic Topic definition (might be an id, an array or an + * object) + * @return int|false number addition of all return values, false if none was called + */ + public function addTopic($topic) + { + $topic = CourseTopic::toObject($topic); + if (!$this->topics->find($topic->id)) { + $this->topics[] = $topic; + return $this->storeRelations('topics'); + } + return false; + } + + /** + * Removes a topic from this date. + * + * @param mixed $topic Topic definition (might be an id, an array or an + * object) + * @return number addition of all return values, false if none was called + */ + public function removeTopic($topic) + { + $this->topics->unsetByPk(is_string($topic) ? $topic : $topic->id); + return $this->storeRelations('topics'); + } + + /** + * Returns the name of the assigned room for this date. + * + * @return String containing the room name + */ + public function getRoomName() + { + if (Config::get()->RESOURCES_ENABLE && !empty($this->room_booking->resource)) { + return $this->room_booking->resource->name; + } + return $this['raum']; + } + + /** + * Returns the assigned room for this date as an object. + * + * @return Room Either the object or null if no room is assigned + */ + public function getRoom() + { + if (Config::get()->RESOURCES_ENABLE && !empty($this->room_booking->resource)) { + return $this->room_booking->resource->getDerivedClassInstance(); + } + return null; + } + + /** + * Returns the name of the type of this date. + * + * @param String containing the type name + */ + public function getTypeName() + { + return $GLOBALS['TERMIN_TYP'][$this->date_typ]['name'] ?? ''; + } + + /** + * Returns the full qualified name of this date. + * + * @param String $format Optional format type (only 'default', 'include-room' and + * 'verbose' are supported by now) + * @return String containing the full name of this date. + */ + public function getFullName($format = 'default') + { + if (!$this->date || !in_array($format, ['default', 'verbose', 'include-room'])) { + return ''; + } + + $latter_template = $format === 'verbose' ? _('%R Uhr') : '%R'; + + if (($this->end_time - $this->date) / 60 / 60 > 23) { + $string = strftime('%a., %x (' . _('ganztägig') . ')' , $this->date); + } else { + $string = strftime('%a., %x, %R', $this->date) . ' - ' + . strftime($latter_template, $this->end_time); + } + + if($format === 'include-room') { + $room = $this->getRoom(); + if($room) { + $string = sprintf('%s %s', + $string, + $room->getActionURL('booking_plan'), + htmlReady($room->name) + ); + } + } + return $string; + } + + /** + * Converts a CourseDate Entry to a CourseExDate Entry + * returns instance of the new CourseExDate or NULL + * + * @return Object CourseExDate + */ + public function cancelDate() + { + //NOTE: If you modify this method make sure the changes + //are also inserted in SingleDateDB::storeSingleDate + //and CourseExDate::unCancelDate to keep the behavior consistent + //across Stud.IP! + + //These statements are used below to update the relations + //of this date. + $db = DBManager::get(); + + $groups_stmt = $db->prepare( + "UPDATE termin_related_groups + SET termin_id = :ex_termin_id + WHERE termin_id = :termin_id;" + ); + + $persons_stmt = $db->prepare( + "UPDATE termin_related_persons + SET range_id = :ex_termin_id + WHERE range_id = :termin_id;" + ); + + $date = $this->toArray(); + + $ex_date = new CourseExDate(); + $ex_date->setData($date); + if ($room = $this->getRoom()) { + $ex_date['resource_id'] = $room->getId(); + } + $ex_date->setId($ex_date->getNewId()); + + if ($ex_date->store()) { + //Update some (but not all) relations to the date so that they + //use the ID of the new ex-date. + + $groups_stmt->execute( + [ + 'ex_termin_id' => $ex_date->id, + 'termin_id' => $this->id + ] + ); + + $persons_stmt->execute( + [ + 'ex_termin_id' => $ex_date->id, + 'termin_id' => $this->id + ] + ); + + //After we updated the relations so that they refer to the + //new ex-date we can delete this date and return the ex-date: + $this->delete(); + return $ex_date; + } + return null; + } + + /** + * saves this object and expires the cache + * + * @see SimpleORMap::store() + */ + public function store() + { + // load room-booking, if any + $this->room_booking; + + $cache = \Studip\Cache\Factory::getCache(); + $cache->expire('course/undecorated_data/'. $this->range_id); + return parent::store(); + } + + /** + * deletes this object and expires the cache + * + * @see SimpleORMap::delete() + */ + public function delete() + { + $cache = \Studip\Cache\Factory::getCache(); + $cache->expire('course/undecorated_data/'. $this->range_id); + return parent::delete(); + } + + /** + * @param $type string type of callback + */ + protected function cbStudipLog($type) + { + if (!$this->metadate_id) { + if ($type === 'after_create') { + StudipLog::log('SEM_ADD_SINGLEDATE', $this->range_id, $this->id, $this->getFullName()); + } + if ($type === 'before_store' && !$this->isNew() && ($this->isFieldDirty('date') || $this->isFieldDirty('end_time'))) { + $old_entry = self::build($this->content_db); + StudipLog::log('SINGLEDATE_CHANGE_TIME', $this->range_id, $this->id, $old_entry->getFullName() . ' -> ' . $this->getFullName()); + } + } + } + + /** + * Returns a list of all possible warnings that should be considered when + * this date is deleted. + * + * @return array of warnings + */ + public function getDeletionWarnings() + { + $warnings = []; + if (count($this->topics) > 0) { + $warnings[] = _('Diesem Termin ist ein Thema zugeordnet.'); + } + + if (Config::get()->RESOURCES_ENABLE && $this->getRoom()) { + $warnings[] = _('Dieser Termin hat eine Raumbuchung, welche mit dem Termin gelöscht wird.'); + } + + return $warnings; + } + + /** + * return all filerefs belonging to this date, permissions fpr given user are checked + * + * @param string|User $user_or_id + * @return mixed[] A mixed array with FolderType and FileRef objects. + */ + public function getAccessibleFolderFiles($user_or_id) + { + $user_id = $user_or_id instanceof User ? $user_or_id->id : $user_or_id; + $all_files = []; + $all_folders = []; + $folders = $this->folders->getArrayCopy(); + foreach ($this->topics as $topic) { + $folders = array_merge($folders, $topic->folders->getArrayCopy()); + } + foreach ($folders as $folder) { + [$files, $typed_folders] = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); + foreach ($files as $file) { + $all_files[$file->id] = $file; + } + $all_folders = array_merge($all_folders, $typed_folders); + } + return ['files' => $all_files, 'folders' => $all_folders]; + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL("autor_id = ?", [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Termine'), 'termine', $field_data); + } + } + } + + + /** + * @return string A string representation of the course date. + */ + public function __toString() : string + { + return sprintf( + _('Termin am %1$s, %2$s von %3$s bis %4$s Uhr'), + strftime('%A', $this->date), + strftime('%x', $this->date), + date('H:i', $this->date), + date('H:i', $this->end_time) + ); + } + + //Start of Event interface implementation. + + public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array + { + return self::findBySQL( + "JOIN `seminar_user` + ON `seminar_user`.`seminar_id` = `termine`.`range_id` + WHERE `seminar_user`.`user_id` = :user_id + AND `termine`.`date` BETWEEN :begin AND :end + AND ( + IFNULL(`termine`.`metadate_id`, '') = '' + OR `termine`.`metadate_id` NOT IN ( + SELECT `metadate_id` + FROM `schedule_seminare` + WHERE `user_id` = :user_id + AND `visible` = 0 + ) + ) + ORDER BY date", + [ + 'begin' => $begin->getTimestamp(), + 'end' => $end->getTimestamp(), + 'user_id' => $range_id + ] + ); + } + + //Event interface implementation: + + public function getObjectId() : string + { + return (string) $this->id; + } + + public function getPrimaryObjectID(): string + { + return $this->range_id; + } + + public function getObjectClass(): string + { + return static::class; + } + + public function getTitle(): string + { + return $this->course->name ?? ''; + } + + public function getBegin(): DateTime + { + $begin = new DateTime(); + $begin->setTimestamp($this->date); + return $begin; + } + + public function getEnd(): DateTime + { + $end = new DateTime(); + $end->setTimestamp($this->end_time); + return $end; + } + + public function getDuration(): DateInterval + { + $begin = $this->getBegin(); + $end = $this->getEnd(); + return $end->diff($begin); + } + + public function getLocation(): string + { + return $this->raum ?? ''; + } + + public function getUniqueId(): string + { + return sprintf('Stud.IP-SEM-%1$s@%2$s', $this->id, $_SERVER['SERVER_NAME']); + } + + public function getDescription(): string + { + $descriptions = $this->topics->map(function ($topic) { + $desc = $topic->title . "\n"; + $desc .= $topic->description; + + return $desc; + }); + return implode("\n\n", $descriptions); + } + + public function getAdditionalDescriptions(): array + { + $descriptions = []; + if (count($this->dozenten) > 0) { + $descriptions[_('Durchführende Lehrende')] = implode(', ', $this->dozenten->getFullName()); + } + if (count($this->statusgruppen) > 0) { + $descriptions[_('Beteiligte Gruppen')] = implode(', ', $this->statusgruppen->getValue('name')); + } + return $descriptions; + } + + public function isAllDayEvent(): bool + { + //Course dates are never all day events. + return false; + } + + public function isWritable(string $user_id): bool + { + return $GLOBALS['perm']->have_studip_perm('dozent', $this->range_id, $user_id); + } + + public function getCreationDate(): DateTime + { + $mkdate = new DateTime(); + $mkdate->setTimestamp($this->mkdate); + return $mkdate; + } + + public function getModificationDate(): DateTime + { + $chdate = new DateTime(); + $chdate->setTimestamp($this->chdate); + return $chdate; + } + + public function getImportDate(): DateTime + { + return $this->getCreationDate(); + } + + public function getAuthor(): ?User + { + return $this->author; + } + + public function getEditor(): ?User + { + return null; + } + + public function toEventData(string $user_id): \Studip\Calendar\EventData + { + $begin = new DateTime(); + $begin->setTimestamp($this->date); + $end = new DateTime(); + $end->setTimestamp($this->end_time); + + $membership = CourseMember::findOneBySQL( + 'seminar_id = :course_id AND user_id = :user_id', + ['course_id' => $this->range_id, 'user_id' => $user_id] + ); + $class_names = []; + if ($membership) { + $class_names[] = 'course-color-' . $membership->gruppe; + } + $studip_view_urls = []; + if ($GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user_id)) { + $studip_view_urls['show'] = URLHelper::getURL('dispatch.php/course/dates/details/' . $this->id, ['cid' => $this->range_id, 'extra_buttons' => '1']); + } + + return new \Studip\Calendar\EventData( + $begin, + $end, + $this->getTitle(), + $class_names, + '#000000', + '#aaaaaa', + $this->isWritable($user_id), + CourseDate::class, + $this->id, + Course::class, + $this->range_id, + 'course', + $this->range_id, + $studip_view_urls, + [], + 'seminar', + 'rgba(0,0,0,0)' + ); + } + + //End of Event interface implementation. +} diff --git a/lib/models/CourseExDate.class.php b/lib/models/CourseExDate.class.php deleted file mode 100644 index 2d3afea..0000000 --- a/lib/models/CourseExDate.class.php +++ /dev/null @@ -1,422 +0,0 @@ - - * @copyright 2014 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for termin_id - * @property string $termin_id database column - * @property string $range_id database column - * @property string $autor_id database column - * @property string $content database column - * @property int $date database column - * @property int $end_time database column - * @property int $mkdate database column - * @property int $chdate database column - * @property int $date_typ database column - * @property string|null $raum database column - * @property string|null $metadate_id database column - * @property string $resource_id database column - * @property User $author belongs_to User - * @property Course $course belongs_to Course - * @property SeminarCycleDate|null $cycle belongs_to SeminarCycleDate - * @property-read mixed $topics additional field - * @property-read mixed $statusgruppen additional field - * @property-read mixed $dozenten additional field - * @property-read mixed $room_booking additional field - * @property-read mixed $room_request additional field - */ - -class CourseExDate extends SimpleORMap implements PrivacyObject, Event -{ - /** - * Configures this model. - * - * @param Array $config Configuration array - */ - protected static function configure($config = []) - { - $config['db_table'] = 'ex_termine'; - $config['belongs_to']['author'] = [ - 'class_name' => User::class, - 'foreign_key' => 'autor_id' - ]; - $config['belongs_to']['course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'range_id' - ]; - $config['belongs_to']['cycle'] = [ - 'class_name' => SeminarCycleDate::class, - 'foreign_key' => 'metadate_id' - ]; - - $dummy_relation = function () { return new SimpleCollection(); }; - $dummy_null = function () { return null; }; - $config['additional_fields']['topics']['get'] = $dummy_relation; - $config['additional_fields']['statusgruppen']['get'] = $dummy_relation; - $config['additional_fields']['dozenten']['get'] = $dummy_relation; - $config['additional_fields']['room_booking']['get'] = $dummy_null; - $config['additional_fields']['room_request']['get'] = $dummy_null; - $config['default_values']['date_typ'] = 1; - parent::configure($config); - } - - const FORMAT_DEFAULT = 'default'; - const FORMAT_VERBOSE = 'verbose'; - - /** - * Returns course dates by course id - * - * @param String $seminar_id Id of the course - * @return array with the associated dates - */ - public static function findBySeminar_id($seminar_id) - { - return self::findByRange_id($seminar_id); - } - - /** - * Return course dates by range id (which is in many cases the course id) - * - * @param String $seminar_id Id of the course - * @param String $order_by Optional order definition - * @return array with the associated dates - */ - public static function findByRange_id($seminar_id, $order_by = 'ORDER BY date') - { - return parent::findByRange_id($seminar_id, $order_by); - } - /** - * Returns the name of the assigned room for this date. - * - * @return String that is always empty - */ - public function getRoomName() - { - return ''; - } - - /** - * Returns the assigned room for this date as an object. - * - * @return null. always. canceled dates need no room. - */ - public function getRoom() - { - return null; - } - - /** - * Returns the name of the type of this date. - * - * @param String containing the type name - */ - public function getTypeName() - { - return $GLOBALS['TERMIN_TYP'][$this->date_typ]['name']; - } - - /** - * Returns the full qualified name of this date. - * - * @param String $format Optional format type (only 'default' and - * 'verbose' are supported by now) - * @return String containing the full name of this date. - */ - public function getFullName($format = 'default') - { - if (!$this->date || !in_array($format, ['default', 'verbose'])) { - return ''; - } - - $latter_template = $format === 'verbose' - ? _('%R Uhr') - : '%R'; - - if (($this->end_time - $this->date) / 60 / 60 > 23) { - return strftime('%a., %x' . ' (' . _('ganztägig') . ')' , $this->date) . " (" . _("fällt aus") . ")"; - } - - return strftime('%a., %x, %R', $this->date) . ' - ' - . strftime($latter_template, $this->end_time) - . ' (' . _('fällt aus') . ')'; - } - - /** - * Converts a CourseExDate Entry to a CourseDate Entry - * returns instance of the new CourseDate or NULL - * @return Object CourseDate - */ - public function unCancelDate() - { - //NOTE: If you modify this method make sure the changes - //are also inserted in SingleDateDB::storeSingleDate - //and CourseDate::cancelDate to keep the behavior consistent - //across Stud.IP! - - //These statements are used below to update the relations - //of this ex-date. - $db = DBManager::get(); - - $groups_stmt = $db->prepare( - "UPDATE termin_related_groups - SET termin_id = :termin_id - WHERE termin_id = :ex_termin_id;" - ); - - $persons_stmt = $db->prepare( - "UPDATE termin_related_persons - SET range_id = :termin_id - WHERE range_id = :ex_termin_id;" - ); - - $ex_date = $this->toArray(); - - //REMOVE content - unset($ex_date['content']); - - $date = new CourseDate(); - $date->setData($ex_date); - $date->setId($date->getNewId()); - - if ($date->store()) { - //Update the relations to the ex-date so that they - //use the ID of the new date. - - $groups_stmt->execute( - [ - 'termin_id' => $date->id, - 'ex_termin_id' => $this->id - ] - ); - - $persons_stmt->execute( - [ - 'termin_id' => $date->id, - 'ex_termin_id' => $this->id - ] - ); - - //After we updated the relations so that they refer to the - //new date we can delete this ex-date and return the date: - - StudipLog::log('SEM_UNDELETE_SINGLEDATE', $this->termin_id, $this->range_id, 'Cycle_id: ' . $this->metadate_id); - $this->delete(); - return $date; - } - return null; - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL("autor_id = ?", [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('ausgefallende Termine'), 'ex_termine', $field_data); - } - } - } - - - /** - * @return string A string representation of the course date. - */ - public function __toString() : string - { - return sprintf( - _('Ausgefallener Termin am %1$s, %2$s von %3$s bis %4$s Uhr'), - strftime('%A', $this->date), - strftime('%x', $this->date), - date('H:i', $this->date), - date('H:i', $this->end_time) - ); - } - - //Start of Event interface implementation. - - public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array - { - return self::findBySQL( - "JOIN `seminar_user` - ON `seminar_user`.`seminar_id` = `ex_termine`.`range_id` - WHERE `seminar_user`.`user_id` = :user_id - AND `ex_termine`.`date` BETWEEN :begin AND :end - AND ( - IFNULL(`ex_termine`.`metadate_id`, '') = '' - OR `ex_termine`.`metadate_id` NOT IN ( - SELECT `metadate_id` - FROM `schedule_seminare` - WHERE `user_id` = :user_id - AND `visible` = 0 - ) - ) - ORDER BY date", - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp(), - 'user_id' => $range_id - ] - ); - } - - //Event interface implementation: - - public function getObjectId() : string - { - return (string) $this->id; - } - - public function getPrimaryObjectID(): string - { - return $this->range_id; - } - - public function getObjectClass(): string - { - return static::class; - } - - public function getTitle(): string - { - return sprintf('%s (fällt aus)', $this->course->name ?? ''); - } - - public function getBegin(): DateTime - { - $begin = new DateTime(); - $begin->setTimestamp($this->date); - return $begin; - } - - public function getEnd(): DateTime - { - $end = new DateTime(); - $end->setTimestamp($this->end_time); - return $end; - } - - public function getDuration(): DateInterval - { - $begin = $this->getBegin(); - $end = $this->getEnd(); - return $end->diff($begin); - } - - public function getLocation(): string - { - return ''; - } - - public function getUniqueId(): string - { - return sprintf('Stud.IP-SEM-%1$s@%2$s', $this->id, $_SERVER['SERVER_NAME']); - } - - public function getDescription(): string - { - return trim($this->getValue('content')); - } - - public function getAdditionalDescriptions(): array - { - $descriptions = []; - if (count($this->dozenten) > 0) { - $descriptions[_('Durchführende Lehrende')] = implode(', ', $this->dozenten->getFullName()); - } - if (count($this->statusgruppen) > 0) { - $descriptions[_('Beteiligte Gruppen')] = implode(', ', $this->statusgruppen->getValue('name')); - } - return $descriptions; - } - - public function isAllDayEvent(): bool - { - //Course dates are never all day events. - return false; - } - - public function isWritable(string $user_id): bool - { - return $GLOBALS['perm']->have_studip_perm('dozent', $this->range_id, $user_id); - } - - public function getCreationDate(): DateTime - { - $mkdate = new DateTime(); - $mkdate->setTimestamp($this->mkdate); - return $mkdate; - } - - public function getModificationDate(): DateTime - { - $chdate = new DateTime(); - $chdate->setTimestamp($this->chdate); - return $chdate; - } - - public function getImportDate(): DateTime - { - return $this->getCreationDate(); - } - - public function getAuthor(): ?User - { - return $this->author; - } - - public function getEditor(): ?User - { - return null; - } - - public function toEventData(string $user_id): \Studip\Calendar\EventData - { - $begin = new DateTime(); - $begin->setTimestamp($this->date); - $end = new DateTime(); - $end->setTimestamp($this->end_time); - - $studip_view_urls = []; - if ($GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user_id)) { - $studip_view_urls['show'] = URLHelper::getURL('dispatch.php/course/details', ['cid' => $this->range_id, 'link_to_course' => '1']); - } - - return new \Studip\Calendar\EventData( - $begin, - $end, - $this->getTitle(), - [], - '#000000', - '#aaaaaa', - $this->isWritable($user_id), - CourseExDate::class, - $this->id, - Course::class, - $this->range_id, - 'course', - $this->range_id, - $studip_view_urls, - [], - 'seminar', - 'rgba(0,0,0,0)' - ); - } - - //End of Event interface implementation. -} diff --git a/lib/models/CourseExDate.php b/lib/models/CourseExDate.php new file mode 100644 index 0000000..2d3afea --- /dev/null +++ b/lib/models/CourseExDate.php @@ -0,0 +1,422 @@ + + * @copyright 2014 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for termin_id + * @property string $termin_id database column + * @property string $range_id database column + * @property string $autor_id database column + * @property string $content database column + * @property int $date database column + * @property int $end_time database column + * @property int $mkdate database column + * @property int $chdate database column + * @property int $date_typ database column + * @property string|null $raum database column + * @property string|null $metadate_id database column + * @property string $resource_id database column + * @property User $author belongs_to User + * @property Course $course belongs_to Course + * @property SeminarCycleDate|null $cycle belongs_to SeminarCycleDate + * @property-read mixed $topics additional field + * @property-read mixed $statusgruppen additional field + * @property-read mixed $dozenten additional field + * @property-read mixed $room_booking additional field + * @property-read mixed $room_request additional field + */ + +class CourseExDate extends SimpleORMap implements PrivacyObject, Event +{ + /** + * Configures this model. + * + * @param Array $config Configuration array + */ + protected static function configure($config = []) + { + $config['db_table'] = 'ex_termine'; + $config['belongs_to']['author'] = [ + 'class_name' => User::class, + 'foreign_key' => 'autor_id' + ]; + $config['belongs_to']['course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'range_id' + ]; + $config['belongs_to']['cycle'] = [ + 'class_name' => SeminarCycleDate::class, + 'foreign_key' => 'metadate_id' + ]; + + $dummy_relation = function () { return new SimpleCollection(); }; + $dummy_null = function () { return null; }; + $config['additional_fields']['topics']['get'] = $dummy_relation; + $config['additional_fields']['statusgruppen']['get'] = $dummy_relation; + $config['additional_fields']['dozenten']['get'] = $dummy_relation; + $config['additional_fields']['room_booking']['get'] = $dummy_null; + $config['additional_fields']['room_request']['get'] = $dummy_null; + $config['default_values']['date_typ'] = 1; + parent::configure($config); + } + + const FORMAT_DEFAULT = 'default'; + const FORMAT_VERBOSE = 'verbose'; + + /** + * Returns course dates by course id + * + * @param String $seminar_id Id of the course + * @return array with the associated dates + */ + public static function findBySeminar_id($seminar_id) + { + return self::findByRange_id($seminar_id); + } + + /** + * Return course dates by range id (which is in many cases the course id) + * + * @param String $seminar_id Id of the course + * @param String $order_by Optional order definition + * @return array with the associated dates + */ + public static function findByRange_id($seminar_id, $order_by = 'ORDER BY date') + { + return parent::findByRange_id($seminar_id, $order_by); + } + /** + * Returns the name of the assigned room for this date. + * + * @return String that is always empty + */ + public function getRoomName() + { + return ''; + } + + /** + * Returns the assigned room for this date as an object. + * + * @return null. always. canceled dates need no room. + */ + public function getRoom() + { + return null; + } + + /** + * Returns the name of the type of this date. + * + * @param String containing the type name + */ + public function getTypeName() + { + return $GLOBALS['TERMIN_TYP'][$this->date_typ]['name']; + } + + /** + * Returns the full qualified name of this date. + * + * @param String $format Optional format type (only 'default' and + * 'verbose' are supported by now) + * @return String containing the full name of this date. + */ + public function getFullName($format = 'default') + { + if (!$this->date || !in_array($format, ['default', 'verbose'])) { + return ''; + } + + $latter_template = $format === 'verbose' + ? _('%R Uhr') + : '%R'; + + if (($this->end_time - $this->date) / 60 / 60 > 23) { + return strftime('%a., %x' . ' (' . _('ganztägig') . ')' , $this->date) . " (" . _("fällt aus") . ")"; + } + + return strftime('%a., %x, %R', $this->date) . ' - ' + . strftime($latter_template, $this->end_time) + . ' (' . _('fällt aus') . ')'; + } + + /** + * Converts a CourseExDate Entry to a CourseDate Entry + * returns instance of the new CourseDate or NULL + * @return Object CourseDate + */ + public function unCancelDate() + { + //NOTE: If you modify this method make sure the changes + //are also inserted in SingleDateDB::storeSingleDate + //and CourseDate::cancelDate to keep the behavior consistent + //across Stud.IP! + + //These statements are used below to update the relations + //of this ex-date. + $db = DBManager::get(); + + $groups_stmt = $db->prepare( + "UPDATE termin_related_groups + SET termin_id = :termin_id + WHERE termin_id = :ex_termin_id;" + ); + + $persons_stmt = $db->prepare( + "UPDATE termin_related_persons + SET range_id = :termin_id + WHERE range_id = :ex_termin_id;" + ); + + $ex_date = $this->toArray(); + + //REMOVE content + unset($ex_date['content']); + + $date = new CourseDate(); + $date->setData($ex_date); + $date->setId($date->getNewId()); + + if ($date->store()) { + //Update the relations to the ex-date so that they + //use the ID of the new date. + + $groups_stmt->execute( + [ + 'termin_id' => $date->id, + 'ex_termin_id' => $this->id + ] + ); + + $persons_stmt->execute( + [ + 'termin_id' => $date->id, + 'ex_termin_id' => $this->id + ] + ); + + //After we updated the relations so that they refer to the + //new date we can delete this ex-date and return the date: + + StudipLog::log('SEM_UNDELETE_SINGLEDATE', $this->termin_id, $this->range_id, 'Cycle_id: ' . $this->metadate_id); + $this->delete(); + return $date; + } + return null; + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL("autor_id = ?", [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('ausgefallende Termine'), 'ex_termine', $field_data); + } + } + } + + + /** + * @return string A string representation of the course date. + */ + public function __toString() : string + { + return sprintf( + _('Ausgefallener Termin am %1$s, %2$s von %3$s bis %4$s Uhr'), + strftime('%A', $this->date), + strftime('%x', $this->date), + date('H:i', $this->date), + date('H:i', $this->end_time) + ); + } + + //Start of Event interface implementation. + + public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array + { + return self::findBySQL( + "JOIN `seminar_user` + ON `seminar_user`.`seminar_id` = `ex_termine`.`range_id` + WHERE `seminar_user`.`user_id` = :user_id + AND `ex_termine`.`date` BETWEEN :begin AND :end + AND ( + IFNULL(`ex_termine`.`metadate_id`, '') = '' + OR `ex_termine`.`metadate_id` NOT IN ( + SELECT `metadate_id` + FROM `schedule_seminare` + WHERE `user_id` = :user_id + AND `visible` = 0 + ) + ) + ORDER BY date", + [ + 'begin' => $begin->getTimestamp(), + 'end' => $end->getTimestamp(), + 'user_id' => $range_id + ] + ); + } + + //Event interface implementation: + + public function getObjectId() : string + { + return (string) $this->id; + } + + public function getPrimaryObjectID(): string + { + return $this->range_id; + } + + public function getObjectClass(): string + { + return static::class; + } + + public function getTitle(): string + { + return sprintf('%s (fällt aus)', $this->course->name ?? ''); + } + + public function getBegin(): DateTime + { + $begin = new DateTime(); + $begin->setTimestamp($this->date); + return $begin; + } + + public function getEnd(): DateTime + { + $end = new DateTime(); + $end->setTimestamp($this->end_time); + return $end; + } + + public function getDuration(): DateInterval + { + $begin = $this->getBegin(); + $end = $this->getEnd(); + return $end->diff($begin); + } + + public function getLocation(): string + { + return ''; + } + + public function getUniqueId(): string + { + return sprintf('Stud.IP-SEM-%1$s@%2$s', $this->id, $_SERVER['SERVER_NAME']); + } + + public function getDescription(): string + { + return trim($this->getValue('content')); + } + + public function getAdditionalDescriptions(): array + { + $descriptions = []; + if (count($this->dozenten) > 0) { + $descriptions[_('Durchführende Lehrende')] = implode(', ', $this->dozenten->getFullName()); + } + if (count($this->statusgruppen) > 0) { + $descriptions[_('Beteiligte Gruppen')] = implode(', ', $this->statusgruppen->getValue('name')); + } + return $descriptions; + } + + public function isAllDayEvent(): bool + { + //Course dates are never all day events. + return false; + } + + public function isWritable(string $user_id): bool + { + return $GLOBALS['perm']->have_studip_perm('dozent', $this->range_id, $user_id); + } + + public function getCreationDate(): DateTime + { + $mkdate = new DateTime(); + $mkdate->setTimestamp($this->mkdate); + return $mkdate; + } + + public function getModificationDate(): DateTime + { + $chdate = new DateTime(); + $chdate->setTimestamp($this->chdate); + return $chdate; + } + + public function getImportDate(): DateTime + { + return $this->getCreationDate(); + } + + public function getAuthor(): ?User + { + return $this->author; + } + + public function getEditor(): ?User + { + return null; + } + + public function toEventData(string $user_id): \Studip\Calendar\EventData + { + $begin = new DateTime(); + $begin->setTimestamp($this->date); + $end = new DateTime(); + $end->setTimestamp($this->end_time); + + $studip_view_urls = []; + if ($GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user_id)) { + $studip_view_urls['show'] = URLHelper::getURL('dispatch.php/course/details', ['cid' => $this->range_id, 'link_to_course' => '1']); + } + + return new \Studip\Calendar\EventData( + $begin, + $end, + $this->getTitle(), + [], + '#000000', + '#aaaaaa', + $this->isWritable($user_id), + CourseExDate::class, + $this->id, + Course::class, + $this->range_id, + 'course', + $this->range_id, + $studip_view_urls, + [], + 'seminar', + 'rgba(0,0,0,0)' + ); + } + + //End of Event interface implementation. +} diff --git a/lib/models/CourseMember.class.php b/lib/models/CourseMember.class.php deleted file mode 100644 index cd7555f..0000000 --- a/lib/models/CourseMember.class.php +++ /dev/null @@ -1,465 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $seminar_id database column - * @property string $user_id database column - * @property string $status database column - * @property int $position database column - * @property int $gruppe database column - * @property int $mkdate database column - * @property string $comment database column - * @property string $visible database column - * @property string $label database column - * @property int $bind_calendar database column - * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel - * @property User $user belongs_to User - * @property Course $course belongs_to Course - * @property mixed $vorname additional field - * @property mixed $nachname additional field - * @property mixed $username additional field - * @property mixed $email additional field - * @property mixed $title_front additional field - * @property mixed $title_rear additional field - * @property mixed $course_name additional field - */ -class CourseMember extends SimpleORMap implements PrivacyObject -{ - protected static function configure($config = []) - { - $config['db_table'] = 'seminar_user'; - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - ]; - $config['belongs_to']['course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'seminar_id', - ]; - $config['has_many']['datafields'] = [ - 'class_name' => DatafieldEntryModel::class, - 'assoc_foreign_key' => - function($model, $params) { - list($sec_range_id, $range_id) = (array)$params[0]->getId(); - $model->setValue('range_id', $range_id); - $model->setValue('sec_range_id', $sec_range_id); - }, - 'assoc_func' => 'findByModel', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'foreign_key' => - function($course_member) { - return [$course_member]; - } - ]; - - $config['additional_fields']['vorname'] = ['user', 'vorname']; - $config['additional_fields']['nachname'] = ['user', 'nachname']; - $config['additional_fields']['username'] = ['user', 'username']; - $config['additional_fields']['email'] = ['user', 'email']; - $config['additional_fields']['title_front'] = ['user', 'title_front']; - $config['additional_fields']['title_rear'] = ['user', 'title_rear']; - - $config['additional_fields']['course_name'] = []; - - $config['registered_callbacks']['after_delete'][] = 'cbRemoveNotifications'; - - parent::configure($config); - } - - public static function findByCourse($course_id) - { - $query = "SELECT seminar_user.*, aum.Vorname, aum.Nachname, aum.Email, - aum.username, ui.title_front, ui.title_rear - FROM seminar_user - LEFT JOIN auth_user_md5 aum USING (user_id) - LEFT JOIN user_info ui USING (user_id) - WHERE seminar_id = ? - ORDER BY position, Nachname, Vorname"; - return DBManager::get()->fetchAll( - $query, - [$course_id], - __CLASS__ . '::buildExisting' - ); - } - - public static function findByCourseAndStatus($course_id, $status) - { - $query = "SELECT seminar_user.*, aum.Vorname, aum.Nachname, aum.Email, - aum.username, ui.title_front, ui.title_rear - FROM seminar_user - LEFT JOIN auth_user_md5 aum USING (user_id) - LEFT JOIN user_info ui USING (user_id) - WHERE seminar_id = ? - AND seminar_user.status IN (?) - ORDER BY status, position, Nachname, Vorname"; - return DBManager::get()->fetchAll( - $query, - [$course_id, is_array($status) ? $status : words($status)], - __CLASS__ . '::buildExisting' - ); - } - - public static function findByUser($user_id) - { - $query = "SELECT seminar_user.*, seminare.Name AS course_name - FROM seminar_user - LEFT JOIN seminare USING (seminar_id) - WHERE user_id = ? - ORDER BY seminare.Name"; - return DBManager::get()->fetchAll( - $query, - [$user_id], - __CLASS__ . '::buildExisting' - ); - } - - /** - * Retrieves the number of all members of a status - * - * @param String|Array $status the status to filter with - * - * @return int the number of all those members. - */ - public static function countByCourseAndStatus($course_id, $status) - { - return self::countBySql( - 'seminar_id = ? AND status IN(?)', - [$course_id, is_array($status) ? $status : words($status)] - ); - } - - public function getUserFullname($format = 'full') - { - return User::build(array_merge( - ['motto' => ''], - $this->toArray('vorname nachname username title_front title_rear') - ))->getFullName($format); - } - - public function cbRemoveNotifications() - { - CourseMemberNotification::deleteBySQL( - 'user_id = ? AND seminar_id = ?', - [$this->user_id, $this->seminar_id] - ); - } - - /** - * Get members of a course - * - * @param string $course_id - * @param string $sort_status - * @param string $order_by - * @return array - */ - public static function getMembers(string $course_id, string $sort_status = 'autor', string $order_by = 'nachname asc'): array - { - list($order, $asc) = explode(' ', $order_by); - if ($order === 'nachname') { - $order_by = "Nachname {$asc},Vorname {$asc}"; - } - - $query = "SELECT su.user_id, username, Vorname, Nachname, Email, status, - position, su.mkdate, su.visible, su.comment, - {$GLOBALS['_fullname_sql']['full_rev']} AS fullname - FROM seminar_user AS su - INNER JOIN auth_user_md5 USING (user_id) - INNER JOIN user_info USING (user_id) - WHERE seminar_id = ? - ORDER BY position, Nachname ASC"; - $st = DBManager::get()->prepare($query); - $st->execute([$course_id]); - $members = SimpleCollection::createFromArray($st->fetchAll(PDO::FETCH_ASSOC)); - $filtered_members = []; - - foreach (['user', 'autor', 'tutor', 'dozent'] as $status) { - $filtered_members[$status] = $members->findBy('status', $status); - if ($status === $sort_status) { - $filtered_members[$status]->orderBy($order_by, $order !== 'nachname' ? SORT_NUMERIC : SORT_LOCALE_STRING); - } else { - $filtered_members[$status]->orderBy(in_array($status, ['tutor', 'dozent']) ? 'position,Nachname,Vorname' : 'Nachname,Vorname'); - } - } - return $filtered_members; - } - - /** - * Get user informations by first and last name for csv-import - * @param string $course_id - * @param string $nachname - * @param string $vorname - * @return array - */ - public static function getMemberByIdentification(string $course_id, string $nachname, string $vorname = null): array - { - return DBManager::get()->fetchAll("SELECT - auth_user_md5.user_id, - auth_user_md5.username, - auth_user_md5.perms, - seminar_user.Seminar_id AS is_present, - {$GLOBALS['_fullname_sql']['full_rev']} AS fullname - FROM auth_user_md5 - LEFT JOIN user_info USING (user_id) - LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?) - WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent') - AND auth_user_md5.visible <> 'never' - AND auth_user_md5.Nachname LIKE ? AND (? IS NULL OR auth_user_md5.Vorname LIKE ?) - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname", - [$course_id, $nachname, $vorname, $vorname]); - } - - /** - * Get user informations by username for csv-import - * @param string $course_id - * @param string $username - * @return Array - */ - public static function getMemberByUsername(string $course_id, string $username): array - { - return DBManager::get()->fetchAll( - "SELECT auth_user_md5.user_id, - auth_user_md5.username, - auth_user_md5.perms, - seminar_user.Seminar_id AS is_present, - {$GLOBALS['_fullname_sql']['full_rev']} AS fullname - FROM auth_user_md5 - LEFT JOIN user_info USING (user_id) - LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?) - WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent') - AND auth_user_md5.visible <> 'never' - AND auth_user_md5.username LIKE ? - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname", - [$course_id, $username] - ); - } - - /** - * Get user informations by email for csv-import - * @param String $course_id - * @param String $email - * @return Array - */ - public static function getMemberByEmail($course_id, $email): array - { - return DBManager::get()->fetchAll( - "SELECT a.user_id, username, - perms, b.Seminar_id AS is_present - FROM auth_user_md5 AS a - LEFT JOIN user_info USING (user_id) - LEFT JOIN seminar_user AS b ON (b.user_id = a.user_id AND b.Seminar_id = ?) - WHERE a.perms IN ('autor', 'tutor', 'dozent') - AND a.visible <> 'never' - AND email LIKE ? - ORDER BY Nachname, Vorname", - [$course_id, $email] - ); - } - - /** - * Get user informations by generic datafields for csv-import - * @param string $course_id - * @param string $nachname - * @param string $datafield_id - * @return Array - */ - public static function getMemberByDatafield(string $course_id, string $nachname, string $datafield_id): array - { - // TODO Fullname - return DBManager::get()->fetchAll( - "SELECT - auth_user_md5.user_id, - auth_user_md5.username, - seminar_user.Seminar_id AS is_present, - {$GLOBALS['_fullname_sql']['full_rev']} AS fullname - FROM datafields_entries - LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = datafields_entries.range_id) - LEFT JOIN user_info USING (user_id) - LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?) - WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent') - AND auth_user_md5.visible <> 'never' - AND datafields_entries.datafield_id = ? AND datafields_entries.content = ? - ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname", - [$course_id, $datafield_id, $nachname] - ); - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL('user_id = ?', [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('SeminareUser'), 'seminar_user', $field_data); - } - } - } - - /** - * return the highest position-number increased by one for the - * passed user-group in the passed seminar - * - * @param string $status can be on of 'tutor', 'dozent', ... - * @param string $seminar_id the seminar to work on - * - * @return int the next available position - */ - public static function getNextPosition(string $status, string $seminar_id): int - { - return (int) DBManager::get()->fetchColumn( - "SELECT MAX(position) + 1 - FROM seminar_user - WHERE Seminar_id = ? AND status = ?", - [$seminar_id, $status] - ); - } - - /** - * reset the order-positions for the given status in the passed seminar, - * starting at the passed position - * - * @param string $course_id the seminar to work on - * @param int $position the position to start with - * - * @return void - */ - public static function resortMembership(string $course_id, int $position, string $status = 'tutor') - { - self::findEachBySQL( - function (CourseMember $membership) { - $membership->position = $membership->position - 1; - $membership->store(); - }, - "Seminar_id = ? AND position > ? AND status = ? ", - [$course_id, $position, $status] - ); - } - /** - * Insert a user into a seminar with optional log-message and contingent - * - * @param string $seminar_id - * @param string $user_id - * @param string $status status of user in the seminar (user, autor, tutor, dozent) - * @param boolean $copy_studycourse if true, the studycourse is copied from admission_seminar_user - * to seminar_user. Overrides the $contingent-parameter - * @param string $contingent optional studiengang_id, if no id is given, no contingent is considered - * @param string $log_message optional log-message. if no log-message is given a default one is used - * @return bool - */ - public static function insertCourseMember($seminar_id, $user_id, $status, $copy_studycourse = false, $contingent = false, $log_message = false): bool - { - if (!$user_id) { - return false; - } - // get the seminar-object - $sem = Seminar::GetInstance($seminar_id); - - $admission_status = ''; - $admission_comment = ''; - $mkdate = time(); - - $admission_user = AdmissionApplication::find([$user_id, $seminar_id]); - if ($admission_user) { - $admission_status = $admission_user->status; - $admission_comment = $admission_user->comment ?? ''; - $mkdate = $admission_user->mkdate; - } - - // check if there are places left in the submitted contingent (if any) - //ignore if preliminary - if ($admission_status !== 'accepted' && $contingent && $sem->isAdmissionEnabled() && !$sem->getFreeAdmissionSeats()) { - return false; - } - - // get coloured group as used on meine_seminare - $colour_group = $sem->getDefaultGroup(); - - // LOGGING - // if no log message is submitted use a default one - if (!$log_message) { - $log_message = 'Wurde in die Veranstaltung eingetragen, admission_status: '. $admission_status . ' Kontingent: ' . $contingent; - } - StudipLog::log('SEM_USER_ADD', $seminar_id, $user_id, $status, $log_message); - $membership = new self([$seminar_id, $user_id]); - $membership->setData([ - 'Seminar_id' => $seminar_id, - 'user_id' => $user_id, - 'status' => $status, - 'comment' => $admission_comment, - 'gruppe' => $colour_group, - 'mkdate' => $mkdate, - ]); - $membership->store(); - - NotificationCenter::postNotification('UserDidEnterCourse', $seminar_id, $user_id); - - if ($admission_status) { - $admission_user->delete(); - - //renumber the waiting/accepted/lot list, a user was deleted from it - AdmissionApplication::renumberAdmission($seminar_id); - } - $cs = $sem->getCourseSet(); - if ($cs) { - AdmissionPriority::unsetPriority($cs->getId(), $user_id, $sem->getId()); - } - - CalendarScheduleModel::deleteSeminarEntries($user_id, $seminar_id); - - // reload the seminar, the contingents have changed - $sem->restore(); - - // Check if a parent course exists and insert user there. - if ($sem->parent_course) { - self::insertCourseMember($sem->parent_course, $user_id, $status, $copy_studycourse, $contingent, $log_message); - } - - return true; - } - - public function getExportData(): array - { - $user = $this->user; - $studycourse = []; - $user->studycourses->map(function($sc) use (&$studycourse) { - $studycourse[]= $sc->studycourse->name . ',' . $sc->degree->name . ',' . $sc->semester; - }); - return [ - 'status' => $this->status, - 'salutation' => $user->salutation, - 'Titel' => $user->title_front, - 'Vorname' => $this->vorname, - 'Nachname' => $this->nachname, - 'Titel2' => $user->title_rear, - 'username' => $this->username, - 'privadr' => $user->privadr, - 'privatnr' => $user->privatnr, - 'Email' => $this->email, - 'Anmeldedatum' => date('d.m.Y H:i:s', $this->mkdate), - 'Matrikelnummer' => $this->user->matriculation_number, - 'studiengaenge' => implode(';', $studycourse), - 'position' => $this->position, - ]; - } -} diff --git a/lib/models/CourseMember.php b/lib/models/CourseMember.php new file mode 100644 index 0000000..cd7555f --- /dev/null +++ b/lib/models/CourseMember.php @@ -0,0 +1,465 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $seminar_id database column + * @property string $user_id database column + * @property string $status database column + * @property int $position database column + * @property int $gruppe database column + * @property int $mkdate database column + * @property string $comment database column + * @property string $visible database column + * @property string $label database column + * @property int $bind_calendar database column + * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel + * @property User $user belongs_to User + * @property Course $course belongs_to Course + * @property mixed $vorname additional field + * @property mixed $nachname additional field + * @property mixed $username additional field + * @property mixed $email additional field + * @property mixed $title_front additional field + * @property mixed $title_rear additional field + * @property mixed $course_name additional field + */ +class CourseMember extends SimpleORMap implements PrivacyObject +{ + protected static function configure($config = []) + { + $config['db_table'] = 'seminar_user'; + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + ]; + $config['belongs_to']['course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'seminar_id', + ]; + $config['has_many']['datafields'] = [ + 'class_name' => DatafieldEntryModel::class, + 'assoc_foreign_key' => + function($model, $params) { + list($sec_range_id, $range_id) = (array)$params[0]->getId(); + $model->setValue('range_id', $range_id); + $model->setValue('sec_range_id', $sec_range_id); + }, + 'assoc_func' => 'findByModel', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'foreign_key' => + function($course_member) { + return [$course_member]; + } + ]; + + $config['additional_fields']['vorname'] = ['user', 'vorname']; + $config['additional_fields']['nachname'] = ['user', 'nachname']; + $config['additional_fields']['username'] = ['user', 'username']; + $config['additional_fields']['email'] = ['user', 'email']; + $config['additional_fields']['title_front'] = ['user', 'title_front']; + $config['additional_fields']['title_rear'] = ['user', 'title_rear']; + + $config['additional_fields']['course_name'] = []; + + $config['registered_callbacks']['after_delete'][] = 'cbRemoveNotifications'; + + parent::configure($config); + } + + public static function findByCourse($course_id) + { + $query = "SELECT seminar_user.*, aum.Vorname, aum.Nachname, aum.Email, + aum.username, ui.title_front, ui.title_rear + FROM seminar_user + LEFT JOIN auth_user_md5 aum USING (user_id) + LEFT JOIN user_info ui USING (user_id) + WHERE seminar_id = ? + ORDER BY position, Nachname, Vorname"; + return DBManager::get()->fetchAll( + $query, + [$course_id], + __CLASS__ . '::buildExisting' + ); + } + + public static function findByCourseAndStatus($course_id, $status) + { + $query = "SELECT seminar_user.*, aum.Vorname, aum.Nachname, aum.Email, + aum.username, ui.title_front, ui.title_rear + FROM seminar_user + LEFT JOIN auth_user_md5 aum USING (user_id) + LEFT JOIN user_info ui USING (user_id) + WHERE seminar_id = ? + AND seminar_user.status IN (?) + ORDER BY status, position, Nachname, Vorname"; + return DBManager::get()->fetchAll( + $query, + [$course_id, is_array($status) ? $status : words($status)], + __CLASS__ . '::buildExisting' + ); + } + + public static function findByUser($user_id) + { + $query = "SELECT seminar_user.*, seminare.Name AS course_name + FROM seminar_user + LEFT JOIN seminare USING (seminar_id) + WHERE user_id = ? + ORDER BY seminare.Name"; + return DBManager::get()->fetchAll( + $query, + [$user_id], + __CLASS__ . '::buildExisting' + ); + } + + /** + * Retrieves the number of all members of a status + * + * @param String|Array $status the status to filter with + * + * @return int the number of all those members. + */ + public static function countByCourseAndStatus($course_id, $status) + { + return self::countBySql( + 'seminar_id = ? AND status IN(?)', + [$course_id, is_array($status) ? $status : words($status)] + ); + } + + public function getUserFullname($format = 'full') + { + return User::build(array_merge( + ['motto' => ''], + $this->toArray('vorname nachname username title_front title_rear') + ))->getFullName($format); + } + + public function cbRemoveNotifications() + { + CourseMemberNotification::deleteBySQL( + 'user_id = ? AND seminar_id = ?', + [$this->user_id, $this->seminar_id] + ); + } + + /** + * Get members of a course + * + * @param string $course_id + * @param string $sort_status + * @param string $order_by + * @return array + */ + public static function getMembers(string $course_id, string $sort_status = 'autor', string $order_by = 'nachname asc'): array + { + list($order, $asc) = explode(' ', $order_by); + if ($order === 'nachname') { + $order_by = "Nachname {$asc},Vorname {$asc}"; + } + + $query = "SELECT su.user_id, username, Vorname, Nachname, Email, status, + position, su.mkdate, su.visible, su.comment, + {$GLOBALS['_fullname_sql']['full_rev']} AS fullname + FROM seminar_user AS su + INNER JOIN auth_user_md5 USING (user_id) + INNER JOIN user_info USING (user_id) + WHERE seminar_id = ? + ORDER BY position, Nachname ASC"; + $st = DBManager::get()->prepare($query); + $st->execute([$course_id]); + $members = SimpleCollection::createFromArray($st->fetchAll(PDO::FETCH_ASSOC)); + $filtered_members = []; + + foreach (['user', 'autor', 'tutor', 'dozent'] as $status) { + $filtered_members[$status] = $members->findBy('status', $status); + if ($status === $sort_status) { + $filtered_members[$status]->orderBy($order_by, $order !== 'nachname' ? SORT_NUMERIC : SORT_LOCALE_STRING); + } else { + $filtered_members[$status]->orderBy(in_array($status, ['tutor', 'dozent']) ? 'position,Nachname,Vorname' : 'Nachname,Vorname'); + } + } + return $filtered_members; + } + + /** + * Get user informations by first and last name for csv-import + * @param string $course_id + * @param string $nachname + * @param string $vorname + * @return array + */ + public static function getMemberByIdentification(string $course_id, string $nachname, string $vorname = null): array + { + return DBManager::get()->fetchAll("SELECT + auth_user_md5.user_id, + auth_user_md5.username, + auth_user_md5.perms, + seminar_user.Seminar_id AS is_present, + {$GLOBALS['_fullname_sql']['full_rev']} AS fullname + FROM auth_user_md5 + LEFT JOIN user_info USING (user_id) + LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?) + WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent') + AND auth_user_md5.visible <> 'never' + AND auth_user_md5.Nachname LIKE ? AND (? IS NULL OR auth_user_md5.Vorname LIKE ?) + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname", + [$course_id, $nachname, $vorname, $vorname]); + } + + /** + * Get user informations by username for csv-import + * @param string $course_id + * @param string $username + * @return Array + */ + public static function getMemberByUsername(string $course_id, string $username): array + { + return DBManager::get()->fetchAll( + "SELECT auth_user_md5.user_id, + auth_user_md5.username, + auth_user_md5.perms, + seminar_user.Seminar_id AS is_present, + {$GLOBALS['_fullname_sql']['full_rev']} AS fullname + FROM auth_user_md5 + LEFT JOIN user_info USING (user_id) + LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?) + WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent') + AND auth_user_md5.visible <> 'never' + AND auth_user_md5.username LIKE ? + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname", + [$course_id, $username] + ); + } + + /** + * Get user informations by email for csv-import + * @param String $course_id + * @param String $email + * @return Array + */ + public static function getMemberByEmail($course_id, $email): array + { + return DBManager::get()->fetchAll( + "SELECT a.user_id, username, + perms, b.Seminar_id AS is_present + FROM auth_user_md5 AS a + LEFT JOIN user_info USING (user_id) + LEFT JOIN seminar_user AS b ON (b.user_id = a.user_id AND b.Seminar_id = ?) + WHERE a.perms IN ('autor', 'tutor', 'dozent') + AND a.visible <> 'never' + AND email LIKE ? + ORDER BY Nachname, Vorname", + [$course_id, $email] + ); + } + + /** + * Get user informations by generic datafields for csv-import + * @param string $course_id + * @param string $nachname + * @param string $datafield_id + * @return Array + */ + public static function getMemberByDatafield(string $course_id, string $nachname, string $datafield_id): array + { + // TODO Fullname + return DBManager::get()->fetchAll( + "SELECT + auth_user_md5.user_id, + auth_user_md5.username, + seminar_user.Seminar_id AS is_present, + {$GLOBALS['_fullname_sql']['full_rev']} AS fullname + FROM datafields_entries + LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = datafields_entries.range_id) + LEFT JOIN user_info USING (user_id) + LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?) + WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent') + AND auth_user_md5.visible <> 'never' + AND datafields_entries.datafield_id = ? AND datafields_entries.content = ? + ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname", + [$course_id, $datafield_id, $nachname] + ); + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL('user_id = ?', [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('SeminareUser'), 'seminar_user', $field_data); + } + } + } + + /** + * return the highest position-number increased by one for the + * passed user-group in the passed seminar + * + * @param string $status can be on of 'tutor', 'dozent', ... + * @param string $seminar_id the seminar to work on + * + * @return int the next available position + */ + public static function getNextPosition(string $status, string $seminar_id): int + { + return (int) DBManager::get()->fetchColumn( + "SELECT MAX(position) + 1 + FROM seminar_user + WHERE Seminar_id = ? AND status = ?", + [$seminar_id, $status] + ); + } + + /** + * reset the order-positions for the given status in the passed seminar, + * starting at the passed position + * + * @param string $course_id the seminar to work on + * @param int $position the position to start with + * + * @return void + */ + public static function resortMembership(string $course_id, int $position, string $status = 'tutor') + { + self::findEachBySQL( + function (CourseMember $membership) { + $membership->position = $membership->position - 1; + $membership->store(); + }, + "Seminar_id = ? AND position > ? AND status = ? ", + [$course_id, $position, $status] + ); + } + /** + * Insert a user into a seminar with optional log-message and contingent + * + * @param string $seminar_id + * @param string $user_id + * @param string $status status of user in the seminar (user, autor, tutor, dozent) + * @param boolean $copy_studycourse if true, the studycourse is copied from admission_seminar_user + * to seminar_user. Overrides the $contingent-parameter + * @param string $contingent optional studiengang_id, if no id is given, no contingent is considered + * @param string $log_message optional log-message. if no log-message is given a default one is used + * @return bool + */ + public static function insertCourseMember($seminar_id, $user_id, $status, $copy_studycourse = false, $contingent = false, $log_message = false): bool + { + if (!$user_id) { + return false; + } + // get the seminar-object + $sem = Seminar::GetInstance($seminar_id); + + $admission_status = ''; + $admission_comment = ''; + $mkdate = time(); + + $admission_user = AdmissionApplication::find([$user_id, $seminar_id]); + if ($admission_user) { + $admission_status = $admission_user->status; + $admission_comment = $admission_user->comment ?? ''; + $mkdate = $admission_user->mkdate; + } + + // check if there are places left in the submitted contingent (if any) + //ignore if preliminary + if ($admission_status !== 'accepted' && $contingent && $sem->isAdmissionEnabled() && !$sem->getFreeAdmissionSeats()) { + return false; + } + + // get coloured group as used on meine_seminare + $colour_group = $sem->getDefaultGroup(); + + // LOGGING + // if no log message is submitted use a default one + if (!$log_message) { + $log_message = 'Wurde in die Veranstaltung eingetragen, admission_status: '. $admission_status . ' Kontingent: ' . $contingent; + } + StudipLog::log('SEM_USER_ADD', $seminar_id, $user_id, $status, $log_message); + $membership = new self([$seminar_id, $user_id]); + $membership->setData([ + 'Seminar_id' => $seminar_id, + 'user_id' => $user_id, + 'status' => $status, + 'comment' => $admission_comment, + 'gruppe' => $colour_group, + 'mkdate' => $mkdate, + ]); + $membership->store(); + + NotificationCenter::postNotification('UserDidEnterCourse', $seminar_id, $user_id); + + if ($admission_status) { + $admission_user->delete(); + + //renumber the waiting/accepted/lot list, a user was deleted from it + AdmissionApplication::renumberAdmission($seminar_id); + } + $cs = $sem->getCourseSet(); + if ($cs) { + AdmissionPriority::unsetPriority($cs->getId(), $user_id, $sem->getId()); + } + + CalendarScheduleModel::deleteSeminarEntries($user_id, $seminar_id); + + // reload the seminar, the contingents have changed + $sem->restore(); + + // Check if a parent course exists and insert user there. + if ($sem->parent_course) { + self::insertCourseMember($sem->parent_course, $user_id, $status, $copy_studycourse, $contingent, $log_message); + } + + return true; + } + + public function getExportData(): array + { + $user = $this->user; + $studycourse = []; + $user->studycourses->map(function($sc) use (&$studycourse) { + $studycourse[]= $sc->studycourse->name . ',' . $sc->degree->name . ',' . $sc->semester; + }); + return [ + 'status' => $this->status, + 'salutation' => $user->salutation, + 'Titel' => $user->title_front, + 'Vorname' => $this->vorname, + 'Nachname' => $this->nachname, + 'Titel2' => $user->title_rear, + 'username' => $this->username, + 'privadr' => $user->privadr, + 'privatnr' => $user->privatnr, + 'Email' => $this->email, + 'Anmeldedatum' => date('d.m.Y H:i:s', $this->mkdate), + 'Matrikelnummer' => $this->user->matriculation_number, + 'studiengaenge' => implode(';', $studycourse), + 'position' => $this->position, + ]; + } +} diff --git a/lib/models/CourseTopic.class.php b/lib/models/CourseTopic.class.php deleted file mode 100644 index eb26efa..0000000 --- a/lib/models/CourseTopic.class.php +++ /dev/null @@ -1,261 +0,0 @@ - - * @copyright 2014 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for issue_id - * @property string $issue_id database column - * @property string $seminar_id database column - * @property string $author_id database column - * @property I18NString $title database column - * @property I18NString $description database column - * @property int $priority database column - * @property int $paper_related database column - * @property int $mkdate database column - * @property int $chdate database column - * @property SimpleORMapCollection|Folder[] $folders has_many Folder - * @property Course $course belongs_to Course - * @property User $author belongs_to User - * @property SimpleORMapCollection|CourseDate[] $dates has_and_belongs_to_many CourseDate - * @property-read mixed $forum_thread_url additional field - */ -class CourseTopic extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'themen'; - $config['has_and_belongs_to_many']['dates'] = [ - 'class_name' => CourseDate::class, - 'thru_table' => 'themen_termine', - 'order_by' => 'ORDER BY date', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - $config['has_many']['folders'] = [ - 'class_name' => Folder::class, - 'assoc_func' => 'findByTopic_id' - ]; - $config['belongs_to']['course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'seminar_id' - ]; - $config['belongs_to']['author'] = [ - 'class_name' => User::class, - 'foreign_key' => 'author_id' - ]; - - $config['additional_fields']['forum_thread_url']['get'] = 'getForumThreadURL'; - - $config['registered_callbacks']['before_create'][] = 'cbDefaultValues'; - $config['registered_callbacks']['after_store'][] = 'cbUpdateConnectedContentModules'; - $config['registered_callbacks']['before_delete'][] = 'cbUnlinkConnectedContentModules'; - - $config['i18n_fields']['title'] = true; - $config['i18n_fields']['description'] = true; - - parent::configure($config); - } - - public static function findByTermin_id($termin_id) - { - return self::findBySQL("INNER JOIN themen_termine USING (issue_id) - WHERE themen_termine.termin_id = ? - ORDER BY priority ASC", - [$termin_id] - ); - } - - public static function findBySeminar_id($seminar_id, $order_by = 'ORDER BY priority') - { - return parent::findBySeminar_id($seminar_id, $order_by); - } - - public static function findByTitle($seminar_id, $name) - { - return self::findOneBySQL("seminar_id = ? AND title = ?", [$seminar_id, $name]); - } - - public static function getMaxPriority($seminar_id) - { - return DBManager::get()->fetchColumn("SELECT MAX(priority) FROM themen WHERE seminar_id=?", [$seminar_id]); - } - - /** - * set or update connection with document folder - */ - public function connectWithDocumentFolder() - { - if ($this->seminar_id) { - $document_module = Seminar::getInstance($this->seminar_id)->getSlotModule('documents'); - if ($document_module) { - if (!$this->folders->count()) { - $folder = new Folder(); - $folder['range_id'] = $this['seminar_id']; - $folder['parent_id'] = Folder::findTopFolder($this['seminar_id'])->getId(); - $folder['range_type'] = "course"; - $folder['folder_type'] = "CourseTopicFolder"; - $folder['data_content']['topic_id'] = $this->getId(); - $folder['user_id'] = $GLOBALS['user']->id; - $folder['name'] = $this['title']; - $folder['description'] = $this['description']; - return $folder->store(); - } - } - } - return false; - } - - /** - * set or update connection with forum thread - */ - public function connectWithForumThread() - { - if ($this->seminar_id) { - $forum_module = Seminar::getInstance($this->seminar_id)->getSlotModule('forum'); - if ($forum_module instanceOf ForumModule) { - $forum_module->setThreadForIssue($this->id, $this->title, $this->description); - return true; - } - } - return false; - } - - public function getForumThreadURL() - { - if ($this->seminar_id) { - $forum_module = Seminar::getInstance($this->seminar_id)->getSlotModule('forum'); - if ($forum_module instanceOf ForumModule) { - return html_entity_decode($forum_module->getLinkToThread($this->id)); - } - } - return ''; - } - - protected function cbUpdateConnectedContentModules() - { - if ($this->isFieldDirty('title') || $this->isFieldDirty('description')) { - if ($this->forum_thread_url) { - $this->connectWithForumThread(); - } - } - } - - /** - * Removes link information for forum topic and remove forum topic as well - * if it is empty. - */ - protected function cbUnlinkConnectedContentModules() - { - $query = "DELETE fei, fe - FROM `forum_entries_issues` AS fei - LEFT JOIN `forum_entries` AS fe - ON fei.`topic_id` = fe.`topic_id` AND fe.`rgt` = fe.`lft` + 1 - WHERE `issue_id` = ?"; - DBManager::get()->execute($query, [$this->id]); - } - - protected function cbDefaultValues() - { - if (empty($this->content['priority'])) { - $this->content['priority'] = self::getMaxPriority($this->seminar_id) + 1; - } - } - - /** - * return all filerefs belonging to this topic, permissions fpr given user are checked - * - * @param string|User $user_or_id - * @return mixed[] A mixed array with FolderType and FileRef objects. - */ - public function getAccessibleFolderFiles($user_or_id) - { - $user_id = $user_or_id instanceof User ? $user_or_id->id : $user_or_id; - $all_files = []; - $all_folders = []; - $folders = $this->folders->getArrayCopy(); - foreach ($this->dates as $date) { - $folders = array_merge($folders, $date->folders->getArrayCopy()); - } - foreach ($folders as $folder) { - [$files, $typed_folders] = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); - foreach ($files as $file) { - $all_files[$file->id] = $file; - } - $all_folders = array_merge($all_folders, $typed_folders); - } - return ['files' => $all_files, 'folders' => $all_folders]; - } - - /** - * Increases the priority of this topic. Meaning the topic will be sorted further up. - * Be aware that this actually decreases the priority property since lower numbers - * mean higher priority. - * - * @return boolean - * @todo Deprecated, remove for Stud.IP 6.0 - */ - public function increasePriority() - { - // Update all the course's topics with a lower priority than this one - $query = "UPDATE `themen` - SET `priority` = `priority` + 1 - WHERE `seminar_id` = :course_id - AND `priority` < :current_priority - ORDER BY `priority` DESC - LIMIT 1"; - $changed = DBManager::get()->execute($query, [ - ':course_id' => $this->seminar_id, - ':current_priority' => $this->priority, - ]); - - // If anything has changed, decrease priority. Otherwise the current - // topic is already at top. - if ($changed) { - $this->priority -= 1; - $this->store(); - return true; - } - - return false; - } - - /** - * Decreases the priority of this topic. Meaning the topic will be sorted further down. - * Be aware that this actually increases the priority property since higher numbers - * mean lower priority. - * - * @todo Deprecated, remove for Stud.IP 6.0 - */ - public function decreasePriority() - { - // Update all the course's topics with a higher priority than this one - $query = "UPDATE `themen` - SET `priority` = `priority` - 1 - WHERE `seminar_id` = :course_id - AND `priority` > :current_priority - ORDER BY `priority` ASC - LIMIT 1"; - $changed = DBManager::get()->execute($query, [ - ':course_id' => $this->seminar_id, - ':current_priority' => $this->priority, - ]); - - // If anything has changed, increase priority. Otherwise the current - // topic is already at bottom. - if ($changed) { - $this->priority += 1; - $this->store(); - return true; - } - - return false; - - } -} diff --git a/lib/models/CourseTopic.php b/lib/models/CourseTopic.php new file mode 100644 index 0000000..eb26efa --- /dev/null +++ b/lib/models/CourseTopic.php @@ -0,0 +1,261 @@ + + * @copyright 2014 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for issue_id + * @property string $issue_id database column + * @property string $seminar_id database column + * @property string $author_id database column + * @property I18NString $title database column + * @property I18NString $description database column + * @property int $priority database column + * @property int $paper_related database column + * @property int $mkdate database column + * @property int $chdate database column + * @property SimpleORMapCollection|Folder[] $folders has_many Folder + * @property Course $course belongs_to Course + * @property User $author belongs_to User + * @property SimpleORMapCollection|CourseDate[] $dates has_and_belongs_to_many CourseDate + * @property-read mixed $forum_thread_url additional field + */ +class CourseTopic extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'themen'; + $config['has_and_belongs_to_many']['dates'] = [ + 'class_name' => CourseDate::class, + 'thru_table' => 'themen_termine', + 'order_by' => 'ORDER BY date', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + $config['has_many']['folders'] = [ + 'class_name' => Folder::class, + 'assoc_func' => 'findByTopic_id' + ]; + $config['belongs_to']['course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'seminar_id' + ]; + $config['belongs_to']['author'] = [ + 'class_name' => User::class, + 'foreign_key' => 'author_id' + ]; + + $config['additional_fields']['forum_thread_url']['get'] = 'getForumThreadURL'; + + $config['registered_callbacks']['before_create'][] = 'cbDefaultValues'; + $config['registered_callbacks']['after_store'][] = 'cbUpdateConnectedContentModules'; + $config['registered_callbacks']['before_delete'][] = 'cbUnlinkConnectedContentModules'; + + $config['i18n_fields']['title'] = true; + $config['i18n_fields']['description'] = true; + + parent::configure($config); + } + + public static function findByTermin_id($termin_id) + { + return self::findBySQL("INNER JOIN themen_termine USING (issue_id) + WHERE themen_termine.termin_id = ? + ORDER BY priority ASC", + [$termin_id] + ); + } + + public static function findBySeminar_id($seminar_id, $order_by = 'ORDER BY priority') + { + return parent::findBySeminar_id($seminar_id, $order_by); + } + + public static function findByTitle($seminar_id, $name) + { + return self::findOneBySQL("seminar_id = ? AND title = ?", [$seminar_id, $name]); + } + + public static function getMaxPriority($seminar_id) + { + return DBManager::get()->fetchColumn("SELECT MAX(priority) FROM themen WHERE seminar_id=?", [$seminar_id]); + } + + /** + * set or update connection with document folder + */ + public function connectWithDocumentFolder() + { + if ($this->seminar_id) { + $document_module = Seminar::getInstance($this->seminar_id)->getSlotModule('documents'); + if ($document_module) { + if (!$this->folders->count()) { + $folder = new Folder(); + $folder['range_id'] = $this['seminar_id']; + $folder['parent_id'] = Folder::findTopFolder($this['seminar_id'])->getId(); + $folder['range_type'] = "course"; + $folder['folder_type'] = "CourseTopicFolder"; + $folder['data_content']['topic_id'] = $this->getId(); + $folder['user_id'] = $GLOBALS['user']->id; + $folder['name'] = $this['title']; + $folder['description'] = $this['description']; + return $folder->store(); + } + } + } + return false; + } + + /** + * set or update connection with forum thread + */ + public function connectWithForumThread() + { + if ($this->seminar_id) { + $forum_module = Seminar::getInstance($this->seminar_id)->getSlotModule('forum'); + if ($forum_module instanceOf ForumModule) { + $forum_module->setThreadForIssue($this->id, $this->title, $this->description); + return true; + } + } + return false; + } + + public function getForumThreadURL() + { + if ($this->seminar_id) { + $forum_module = Seminar::getInstance($this->seminar_id)->getSlotModule('forum'); + if ($forum_module instanceOf ForumModule) { + return html_entity_decode($forum_module->getLinkToThread($this->id)); + } + } + return ''; + } + + protected function cbUpdateConnectedContentModules() + { + if ($this->isFieldDirty('title') || $this->isFieldDirty('description')) { + if ($this->forum_thread_url) { + $this->connectWithForumThread(); + } + } + } + + /** + * Removes link information for forum topic and remove forum topic as well + * if it is empty. + */ + protected function cbUnlinkConnectedContentModules() + { + $query = "DELETE fei, fe + FROM `forum_entries_issues` AS fei + LEFT JOIN `forum_entries` AS fe + ON fei.`topic_id` = fe.`topic_id` AND fe.`rgt` = fe.`lft` + 1 + WHERE `issue_id` = ?"; + DBManager::get()->execute($query, [$this->id]); + } + + protected function cbDefaultValues() + { + if (empty($this->content['priority'])) { + $this->content['priority'] = self::getMaxPriority($this->seminar_id) + 1; + } + } + + /** + * return all filerefs belonging to this topic, permissions fpr given user are checked + * + * @param string|User $user_or_id + * @return mixed[] A mixed array with FolderType and FileRef objects. + */ + public function getAccessibleFolderFiles($user_or_id) + { + $user_id = $user_or_id instanceof User ? $user_or_id->id : $user_or_id; + $all_files = []; + $all_folders = []; + $folders = $this->folders->getArrayCopy(); + foreach ($this->dates as $date) { + $folders = array_merge($folders, $date->folders->getArrayCopy()); + } + foreach ($folders as $folder) { + [$files, $typed_folders] = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); + foreach ($files as $file) { + $all_files[$file->id] = $file; + } + $all_folders = array_merge($all_folders, $typed_folders); + } + return ['files' => $all_files, 'folders' => $all_folders]; + } + + /** + * Increases the priority of this topic. Meaning the topic will be sorted further up. + * Be aware that this actually decreases the priority property since lower numbers + * mean higher priority. + * + * @return boolean + * @todo Deprecated, remove for Stud.IP 6.0 + */ + public function increasePriority() + { + // Update all the course's topics with a lower priority than this one + $query = "UPDATE `themen` + SET `priority` = `priority` + 1 + WHERE `seminar_id` = :course_id + AND `priority` < :current_priority + ORDER BY `priority` DESC + LIMIT 1"; + $changed = DBManager::get()->execute($query, [ + ':course_id' => $this->seminar_id, + ':current_priority' => $this->priority, + ]); + + // If anything has changed, decrease priority. Otherwise the current + // topic is already at top. + if ($changed) { + $this->priority -= 1; + $this->store(); + return true; + } + + return false; + } + + /** + * Decreases the priority of this topic. Meaning the topic will be sorted further down. + * Be aware that this actually increases the priority property since higher numbers + * mean lower priority. + * + * @todo Deprecated, remove for Stud.IP 6.0 + */ + public function decreasePriority() + { + // Update all the course's topics with a higher priority than this one + $query = "UPDATE `themen` + SET `priority` = `priority` - 1 + WHERE `seminar_id` = :course_id + AND `priority` > :current_priority + ORDER BY `priority` ASC + LIMIT 1"; + $changed = DBManager::get()->execute($query, [ + ':course_id' => $this->seminar_id, + ':current_priority' => $this->priority, + ]); + + // If anything has changed, increase priority. Otherwise the current + // topic is already at bottom. + if ($changed) { + $this->priority += 1; + $this->store(); + return true; + } + + return false; + + } +} diff --git a/lib/models/CronjobLog.class.php b/lib/models/CronjobLog.class.php deleted file mode 100644 index c9e92f7..0000000 --- a/lib/models/CronjobLog.class.php +++ /dev/null @@ -1,70 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** - * CronjobLog - Model for the database table "cronjobs_logs" - * - * @author Jan-Hendrik Willms - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.4 - * - * @property string $id alias column for log_id - * @property string $log_id database column - * @property string $schedule_id database column - * @property int $scheduled database column - * @property int $executed database column - * @property string|null $exception database column - * @property string|null $output database column - * @property float $duration database column - * @property CronjobSchedule $schedule belongs_to CronjobSchedule - */ - -class CronjobLog extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'cronjobs_logs'; - - $config['belongs_to']['schedule'] = [ - 'class_name' => CronjobSchedule::class, - 'foreign_key' => 'schedule_id', - ]; - - - parent::configure($config); - } - - public function setException($exception_or_string) - { - if ($exception_or_string instanceof Exception) { - $exception_as_string = display_exception($exception_or_string, false, true); - return $this->content['exception'] = $exception_as_string; - } - - if (is_string($exception_or_string)) { - return $this->content['exception'] = $exception_or_string; - } - - return $this->content['exception'] = null; - } - -} diff --git a/lib/models/CronjobLog.php b/lib/models/CronjobLog.php new file mode 100644 index 0000000..c9e92f7 --- /dev/null +++ b/lib/models/CronjobLog.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** + * CronjobLog - Model for the database table "cronjobs_logs" + * + * @author Jan-Hendrik Willms + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.4 + * + * @property string $id alias column for log_id + * @property string $log_id database column + * @property string $schedule_id database column + * @property int $scheduled database column + * @property int $executed database column + * @property string|null $exception database column + * @property string|null $output database column + * @property float $duration database column + * @property CronjobSchedule $schedule belongs_to CronjobSchedule + */ + +class CronjobLog extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'cronjobs_logs'; + + $config['belongs_to']['schedule'] = [ + 'class_name' => CronjobSchedule::class, + 'foreign_key' => 'schedule_id', + ]; + + + parent::configure($config); + } + + public function setException($exception_or_string) + { + if ($exception_or_string instanceof Exception) { + $exception_as_string = display_exception($exception_or_string, false, true); + return $this->content['exception'] = $exception_as_string; + } + + if (is_string($exception_or_string)) { + return $this->content['exception'] = $exception_or_string; + } + + return $this->content['exception'] = null; + } + +} diff --git a/lib/models/CronjobSchedule.class.php b/lib/models/CronjobSchedule.class.php deleted file mode 100644 index 0eb19ba..0000000 --- a/lib/models/CronjobSchedule.class.php +++ /dev/null @@ -1,272 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** - * CronjobSchedule - Model for the database table "cronjobs_schedules" - * - * @author Jan-Hendrik Willms - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.4 - * - * @property string $id alias column for schedule_id - * @property string $schedule_id database column - * @property string $task_id database column - * @property int $active database column - * @property string|null $title database column - * @property string|null $description database column - * @property string|null $parameters database column - * @property int|null $minute database column - * @property int|null $hour database column - * @property int|null $day database column - * @property int|null $month database column - * @property int|null $day_of_week database column - * @property int $next_execution database column - * @property int|null $last_execution database column - * @property string|null $last_result database column - * @property int $execution_count database column - * @property int $mkdate database column - * @property int $chdate database column - * @property SimpleORMapCollection|CronjobLog[] $logs has_many CronjobLog - * @property CronjobTask $task belongs_to CronjobTask - */ - -class CronjobSchedule extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'cronjobs_schedules'; - - $config['belongs_to']['task'] = [ - 'class_name' => CronjobTask::class, - 'foreign_key' => 'task_id', - ]; - $config['has_many']['logs'] = [ - 'class_name' => CronjobLog::class, - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - - $config['registered_callbacks']['before_store'][] = 'cbJsonifyParameters'; - $config['registered_callbacks']['after_store'][] = 'cbJsonifyParameters'; - $config['registered_callbacks']['after_initialize'][] = 'cbJsonifyParameters'; - - parent::configure($config); - } - - /** - * replaces title with task name if title is empty. - * - * @return string the title or the task name - */ - public function getTitle() - { - return $this->content['title'] ?: $this->task->name ?? ''; - } - - protected function cbJsonifyParameters($type) - { - if ($type === 'before_store' && !is_string($this->parameters)) { - $this->parameters = json_encode($this->parameters ?: null); - } - if (in_array($type, ['after_initialize', 'after_store']) && is_string($this->parameters)) { - $parameters = json_decode($this->parameters, true) ?: []; - if ($this->task && $this->task->valid) { - $default_parameters = $this->task->extractDefaultParameters(); - foreach ($default_parameters as $key => $value) { - if (!isset($parameters[$key])) { - $parameters[$key] = $value; - } - } - } - $this->parameters = $parameters; - } - } - - /** - * Stores the schedule in database. Will bail out with an exception if - * the provided task does not exists. Will also nullify the title if it - * matches the task name (see CronjobSchedule::getTitle()). - * - * @return CronjobSchedule Returns itself to allow chaining - */ - public function store() - { - if ($this->task === null) { - $message = sprintf('A task with the id "%s" does not exist.', $this->task_id); - throw new InvalidArgumentException($message); - } - - // Remove title if it is the default (task's name) - if ($this->title === $this->task->name) { - $this->title = null; - } - - parent::store(); - - return $this; - } - - /** - * Activates this schedule. - * - * @return CronjobSchedule Returns itself to allow chaining - */ - public function activate(bool $run_immediately = false) - { - $next_execution = $run_immediately ? strtotime('-1 minute') : $this->calculateNextExecution(); - - $this->active = true; - $this->next_execution = $next_execution; - $this->store(); - - return $this; - } - - /** - * Deactivates this schedule. - * - * @return CronjobSchedule Returns itself to allow chaining - */ - public function deactivate() - { - $this->active = false; - $this->store(); - - return $this; - } - - /** - * Executes this schedule. - * - * @param bool $force Pass true to force execution of the schedule even - * if it's not activated - * @return mixed The result of the execution - * @throws RuntimeException When either the schedule or the according is - * not activated - */ - public function execute($force = false) - { - if (!$force && !$this->active) { - throw new RuntimeException('Execution aborted. Schedule is not active'); - } - if (!$this->task->active) { - throw new RuntimeException('Execution aborted. Associated task is not active'); - } - - $this->last_execution = time(); - $this->execution_count += 1; - $this->next_execution = $this->calculateNextExecution(); - $this->store(); - - $this->task->execution_count += 1; - $this->task->store(); - - $result = $this->task->engage($this->last_result, $this->parameters); - - $this->last_result = $result; - $this->store(); - - return $result; - } - - /** - * Determines whether the schedule should execute given the provided - * timestamp. - * - * @param mixed $now Defines the temporal fix point - * @return bool Whether the schedule should execute or not. - */ - public function shouldExecute($now = null) - { - return ($now ?: time()) >= $this->next_execution; - } - - /** - * Calculates the next execution for this schedule. - * - * The next execution is calculated by increasing the current timestamp - * and testing whether all conditions match. This is not the best method - * to test and should be optimized sooner or later. - * - * @param mixed $now Defines the temporal fix point - * - * @return int Timestamp of calculated next execution - * @throws RuntimeException When calculation takes too long (you should - * check the conditions for validity in that case) - */ - public function calculateNextExecution($now = null) - { - $now = $now ?: time(); - - $result = $now; - $result -= $result % 60; - - $i = 366 * 24 * 60; // Maximum: A year - $offset = 60; - - do { - $result += $offset; - - // TODO: Performance - Adjust result according to conditions - // See http://coderzone.org/library/PHP-PHP-Cron-Parser-Class_1084.htm - $valid = $this->testTimestamp($result, $this->minute, 'i') - && $this->testTimestamp($result, $this->hour, 'H') - && $this->testTimestamp($result, $this->day, 'd') - && $this->testTimestamp($result, $this->month, 'm') - && $this->testTimestamp($result, $this->day_of_week, 'N'); - - } while (!$valid && $i-- > 0); - - if ($i <= 0) { - throw new RuntimeException('No result, current: ' . date('d.m.Y H:i', $result)); - } - - $this->next_execution = $result; - return $result; - } - - /** - * Tests a timestamp against the passed condition. - * - * @param int $timestamp The timestamp to test - * @param mixed $condition Can be either null for "don't care", a positive - * number for an exact moment or a negative number - * for a repeating moment - * @param String $format Format for date() to extract a portion of the - * timestamp - */ - protected function testTimestamp($timestamp, $condition, $format) - { - if ($condition === null) { - return true; - } - - $probe = (int) date($format, $timestamp); - $condition = (int) $condition; - - if ($condition < 0) { - return ($probe % abs($condition)) === 0; - } - - return $probe === $condition; - } -} diff --git a/lib/models/CronjobSchedule.php b/lib/models/CronjobSchedule.php new file mode 100644 index 0000000..0eb19ba --- /dev/null +++ b/lib/models/CronjobSchedule.php @@ -0,0 +1,272 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** + * CronjobSchedule - Model for the database table "cronjobs_schedules" + * + * @author Jan-Hendrik Willms + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.4 + * + * @property string $id alias column for schedule_id + * @property string $schedule_id database column + * @property string $task_id database column + * @property int $active database column + * @property string|null $title database column + * @property string|null $description database column + * @property string|null $parameters database column + * @property int|null $minute database column + * @property int|null $hour database column + * @property int|null $day database column + * @property int|null $month database column + * @property int|null $day_of_week database column + * @property int $next_execution database column + * @property int|null $last_execution database column + * @property string|null $last_result database column + * @property int $execution_count database column + * @property int $mkdate database column + * @property int $chdate database column + * @property SimpleORMapCollection|CronjobLog[] $logs has_many CronjobLog + * @property CronjobTask $task belongs_to CronjobTask + */ + +class CronjobSchedule extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'cronjobs_schedules'; + + $config['belongs_to']['task'] = [ + 'class_name' => CronjobTask::class, + 'foreign_key' => 'task_id', + ]; + $config['has_many']['logs'] = [ + 'class_name' => CronjobLog::class, + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + + $config['registered_callbacks']['before_store'][] = 'cbJsonifyParameters'; + $config['registered_callbacks']['after_store'][] = 'cbJsonifyParameters'; + $config['registered_callbacks']['after_initialize'][] = 'cbJsonifyParameters'; + + parent::configure($config); + } + + /** + * replaces title with task name if title is empty. + * + * @return string the title or the task name + */ + public function getTitle() + { + return $this->content['title'] ?: $this->task->name ?? ''; + } + + protected function cbJsonifyParameters($type) + { + if ($type === 'before_store' && !is_string($this->parameters)) { + $this->parameters = json_encode($this->parameters ?: null); + } + if (in_array($type, ['after_initialize', 'after_store']) && is_string($this->parameters)) { + $parameters = json_decode($this->parameters, true) ?: []; + if ($this->task && $this->task->valid) { + $default_parameters = $this->task->extractDefaultParameters(); + foreach ($default_parameters as $key => $value) { + if (!isset($parameters[$key])) { + $parameters[$key] = $value; + } + } + } + $this->parameters = $parameters; + } + } + + /** + * Stores the schedule in database. Will bail out with an exception if + * the provided task does not exists. Will also nullify the title if it + * matches the task name (see CronjobSchedule::getTitle()). + * + * @return CronjobSchedule Returns itself to allow chaining + */ + public function store() + { + if ($this->task === null) { + $message = sprintf('A task with the id "%s" does not exist.', $this->task_id); + throw new InvalidArgumentException($message); + } + + // Remove title if it is the default (task's name) + if ($this->title === $this->task->name) { + $this->title = null; + } + + parent::store(); + + return $this; + } + + /** + * Activates this schedule. + * + * @return CronjobSchedule Returns itself to allow chaining + */ + public function activate(bool $run_immediately = false) + { + $next_execution = $run_immediately ? strtotime('-1 minute') : $this->calculateNextExecution(); + + $this->active = true; + $this->next_execution = $next_execution; + $this->store(); + + return $this; + } + + /** + * Deactivates this schedule. + * + * @return CronjobSchedule Returns itself to allow chaining + */ + public function deactivate() + { + $this->active = false; + $this->store(); + + return $this; + } + + /** + * Executes this schedule. + * + * @param bool $force Pass true to force execution of the schedule even + * if it's not activated + * @return mixed The result of the execution + * @throws RuntimeException When either the schedule or the according is + * not activated + */ + public function execute($force = false) + { + if (!$force && !$this->active) { + throw new RuntimeException('Execution aborted. Schedule is not active'); + } + if (!$this->task->active) { + throw new RuntimeException('Execution aborted. Associated task is not active'); + } + + $this->last_execution = time(); + $this->execution_count += 1; + $this->next_execution = $this->calculateNextExecution(); + $this->store(); + + $this->task->execution_count += 1; + $this->task->store(); + + $result = $this->task->engage($this->last_result, $this->parameters); + + $this->last_result = $result; + $this->store(); + + return $result; + } + + /** + * Determines whether the schedule should execute given the provided + * timestamp. + * + * @param mixed $now Defines the temporal fix point + * @return bool Whether the schedule should execute or not. + */ + public function shouldExecute($now = null) + { + return ($now ?: time()) >= $this->next_execution; + } + + /** + * Calculates the next execution for this schedule. + * + * The next execution is calculated by increasing the current timestamp + * and testing whether all conditions match. This is not the best method + * to test and should be optimized sooner or later. + * + * @param mixed $now Defines the temporal fix point + * + * @return int Timestamp of calculated next execution + * @throws RuntimeException When calculation takes too long (you should + * check the conditions for validity in that case) + */ + public function calculateNextExecution($now = null) + { + $now = $now ?: time(); + + $result = $now; + $result -= $result % 60; + + $i = 366 * 24 * 60; // Maximum: A year + $offset = 60; + + do { + $result += $offset; + + // TODO: Performance - Adjust result according to conditions + // See http://coderzone.org/library/PHP-PHP-Cron-Parser-Class_1084.htm + $valid = $this->testTimestamp($result, $this->minute, 'i') + && $this->testTimestamp($result, $this->hour, 'H') + && $this->testTimestamp($result, $this->day, 'd') + && $this->testTimestamp($result, $this->month, 'm') + && $this->testTimestamp($result, $this->day_of_week, 'N'); + + } while (!$valid && $i-- > 0); + + if ($i <= 0) { + throw new RuntimeException('No result, current: ' . date('d.m.Y H:i', $result)); + } + + $this->next_execution = $result; + return $result; + } + + /** + * Tests a timestamp against the passed condition. + * + * @param int $timestamp The timestamp to test + * @param mixed $condition Can be either null for "don't care", a positive + * number for an exact moment or a negative number + * for a repeating moment + * @param String $format Format for date() to extract a portion of the + * timestamp + */ + protected function testTimestamp($timestamp, $condition, $format) + { + if ($condition === null) { + return true; + } + + $probe = (int) date($format, $timestamp); + $condition = (int) $condition; + + if ($condition < 0) { + return ($probe % abs($condition)) === 0; + } + + return $probe === $condition; + } +} diff --git a/lib/models/CronjobTask.class.php b/lib/models/CronjobTask.class.php deleted file mode 100644 index 8a56ca0..0000000 --- a/lib/models/CronjobTask.class.php +++ /dev/null @@ -1,239 +0,0 @@ - -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ - -/** - * CronjobTask - Model for the database table "cronjobs_tasks" - * - * @author Jan-Hendrik Willms - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.4 - * - * @property string $id alias column for task_id - * @property string $task_id database column - * @property string $filename database column - * @property string $class database column - * @property int $active database column - * @property int $execution_count database column - * @property int $assigned_count database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property SimpleORMapCollection|CronjobSchedule[] $schedules has_many CronjobSchedule - * @property-read mixed $description additional field - * @property-read mixed $name additional field - * @property-read mixed $parameters additional field - */ -class CronjobTask extends SimpleORMap -{ - /** - * Configures the model. - * - * @param Array $config Optional configuration passed from derived class - */ - protected static function configure($config = []) - { - $config['db_table'] = 'cronjobs_tasks'; - $config['has_many']['schedules'] = [ - 'class_name' => CronjobSchedule::class, - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - - $config['additional_fields'] = [ - 'description' => [ - 'get' => function (CronjobTask $task): string { - if ($task->valid) { - return $task->class::getDescription(); - } - return _('Unbekannt'); - }, - ], - 'name' => [ - 'get' => function (CronjobTask $task): string { - if ($task->valid) { - return $task->class::getName(); - } - $result = $task->filename; - if (strpos($result, 'public/plugins_packages') !== false) { - $result = preg_replace('/.*public\/plugins_packages\/(.+)(_Cronjob)?(\.class)?\.php$/', '$1', $result); - } else { - $result = preg_replace('/(_Cronjob)?(\.class)?\.php$/', '', basename($result)); - } - $result .= ' (' . _('fehlerhaft') . ')'; - return $result; - }, - ], - 'parameters' => [ - 'get' => function (CronjobTask $task): array { - if ($task->valid) { - return $task->class::getParameters(); - } - return []; - }, - ], - ]; - - $config['registered_callbacks']['after_initialize'][] = 'loadClass'; - - parent::configure($config); - } - - public $valid = false; - - /** - * Tries to load the underlying php class. This also determines the valid - * state of the task. If the class does not exists, the task is marked - * as not valid. - */ - protected function loadClass() - { - $this->valid = false; - - if (empty($this->class)) { - return; - } - - $filename = $GLOBALS['STUDIP_BASE_PATH'] . '/' . $this->filename; - if (!file_exists($filename)) { - return; - } - - require_once $filename; - - $this->valid = class_exists($this->class); - } - - /** - * Returns whether the task is defined in the core system or via a plugin. - * - * @return bool True if task is defined in core system - */ - public function isCore() - { - return mb_strpos($this->filename, 'plugins_packages') === false; - } - - /** - * Executes the task. - * - * @param String $last_result Result of last executions - * @param Array $parameters Parameters to pass to the task - */ - public function engage($last_result, $parameters = []) - { - if ($this->valid) { - $parameters = array_merge( - $this->extractDefaultParameters(), - $parameters - ); - - $task = new $this->class; - - $task->setUp(); - $result = $task->execute($last_result, $parameters); - $task->tearDown(); - } else { - $result = $last_result; - } - - return $result; - } - -// Convenience methods to ease the usage - - /** - * Schedules this task for periodic execution with the provided schedule. - * - * @param int|null $minute Minute part of the schedule: - * - null for "every minute" a.k.a. "don't care" - * - x < 0 for "every x minutes" - * - x >= 0 for "only at minute x" - * @param int|null $hour Hour part of the schedule: - * - null for "every hour" a.k.a. "don't care" - * - x < 0 for "every x hours" - * - x >= 0 for "only at hour x" - * @param int|null $day Day part of the schedule: - * - null for "every day" a.k.a. "don't care" - * - x < 0 for "every x days" - * - x > 0 for "only at day x" - * @param int|null $month Month part of the schedule: - * - null for "every month" a.k.a. "don't care" - * - x < 0 for "every x months" - * - x > 0 for "only at month x" - * @param int|null $day_of_week Day of week part of the schedule: - * - null for "every day" a.k.a. "don't care" - * - 1 >= x >= 7 for "exactly at day of week x" - * (x starts with monday at 1 and ends with - * sunday at 7) - * @param array $parameters Optional parameters passed to the task - * @return CronjobSchedule The generated schedule object. - */ - public function schedule( - ?int $minute = null, - ?int $hour = null, - ?int $day = null, - ?int $month = null, - ?int $day_of_week = null, - array $parameters = [] - ): CronjobSchedule { - return CronjobScheduler::getInstance()->schedule( - $this->id, - $minute, - $hour, - $day, - $month, - $day_of_week, - $parameters - ); - } - - /** - * An alias for schedule for backwards compatibility. - * - * @see CronjobTask::schedule() - * @return CronjobSchedule - */ - public function schedulePeriodic( - $minute = null, - $hour = null, - $day = null, - $month = null, - $day_of_week = null, - $priority = '', - $parameters = [] - ) { - return $this->schedule($minute, $hour, $day, $month, $day_of_week, $parameters); - } - - /** - * Extracts the default parameter values from the associated task. - * - * @return array - */ - public function extractDefaultParameters() - { - $parameters = call_user_func("{$this->class}::getParameters"); - return array_map(function ($parameter) { - // return $parameter['default'] ?? null; - return isset($parameter['default']) ? $parameter['default'] : null; - }, $parameters); - } -} diff --git a/lib/models/CronjobTask.php b/lib/models/CronjobTask.php new file mode 100644 index 0000000..8a56ca0 --- /dev/null +++ b/lib/models/CronjobTask.php @@ -0,0 +1,239 @@ + +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ + +/** + * CronjobTask - Model for the database table "cronjobs_tasks" + * + * @author Jan-Hendrik Willms + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.4 + * + * @property string $id alias column for task_id + * @property string $task_id database column + * @property string $filename database column + * @property string $class database column + * @property int $active database column + * @property int $execution_count database column + * @property int $assigned_count database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property SimpleORMapCollection|CronjobSchedule[] $schedules has_many CronjobSchedule + * @property-read mixed $description additional field + * @property-read mixed $name additional field + * @property-read mixed $parameters additional field + */ +class CronjobTask extends SimpleORMap +{ + /** + * Configures the model. + * + * @param Array $config Optional configuration passed from derived class + */ + protected static function configure($config = []) + { + $config['db_table'] = 'cronjobs_tasks'; + $config['has_many']['schedules'] = [ + 'class_name' => CronjobSchedule::class, + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + + $config['additional_fields'] = [ + 'description' => [ + 'get' => function (CronjobTask $task): string { + if ($task->valid) { + return $task->class::getDescription(); + } + return _('Unbekannt'); + }, + ], + 'name' => [ + 'get' => function (CronjobTask $task): string { + if ($task->valid) { + return $task->class::getName(); + } + $result = $task->filename; + if (strpos($result, 'public/plugins_packages') !== false) { + $result = preg_replace('/.*public\/plugins_packages\/(.+)(_Cronjob)?(\.class)?\.php$/', '$1', $result); + } else { + $result = preg_replace('/(_Cronjob)?(\.class)?\.php$/', '', basename($result)); + } + $result .= ' (' . _('fehlerhaft') . ')'; + return $result; + }, + ], + 'parameters' => [ + 'get' => function (CronjobTask $task): array { + if ($task->valid) { + return $task->class::getParameters(); + } + return []; + }, + ], + ]; + + $config['registered_callbacks']['after_initialize'][] = 'loadClass'; + + parent::configure($config); + } + + public $valid = false; + + /** + * Tries to load the underlying php class. This also determines the valid + * state of the task. If the class does not exists, the task is marked + * as not valid. + */ + protected function loadClass() + { + $this->valid = false; + + if (empty($this->class)) { + return; + } + + $filename = $GLOBALS['STUDIP_BASE_PATH'] . '/' . $this->filename; + if (!file_exists($filename)) { + return; + } + + require_once $filename; + + $this->valid = class_exists($this->class); + } + + /** + * Returns whether the task is defined in the core system or via a plugin. + * + * @return bool True if task is defined in core system + */ + public function isCore() + { + return mb_strpos($this->filename, 'plugins_packages') === false; + } + + /** + * Executes the task. + * + * @param String $last_result Result of last executions + * @param Array $parameters Parameters to pass to the task + */ + public function engage($last_result, $parameters = []) + { + if ($this->valid) { + $parameters = array_merge( + $this->extractDefaultParameters(), + $parameters + ); + + $task = new $this->class; + + $task->setUp(); + $result = $task->execute($last_result, $parameters); + $task->tearDown(); + } else { + $result = $last_result; + } + + return $result; + } + +// Convenience methods to ease the usage + + /** + * Schedules this task for periodic execution with the provided schedule. + * + * @param int|null $minute Minute part of the schedule: + * - null for "every minute" a.k.a. "don't care" + * - x < 0 for "every x minutes" + * - x >= 0 for "only at minute x" + * @param int|null $hour Hour part of the schedule: + * - null for "every hour" a.k.a. "don't care" + * - x < 0 for "every x hours" + * - x >= 0 for "only at hour x" + * @param int|null $day Day part of the schedule: + * - null for "every day" a.k.a. "don't care" + * - x < 0 for "every x days" + * - x > 0 for "only at day x" + * @param int|null $month Month part of the schedule: + * - null for "every month" a.k.a. "don't care" + * - x < 0 for "every x months" + * - x > 0 for "only at month x" + * @param int|null $day_of_week Day of week part of the schedule: + * - null for "every day" a.k.a. "don't care" + * - 1 >= x >= 7 for "exactly at day of week x" + * (x starts with monday at 1 and ends with + * sunday at 7) + * @param array $parameters Optional parameters passed to the task + * @return CronjobSchedule The generated schedule object. + */ + public function schedule( + ?int $minute = null, + ?int $hour = null, + ?int $day = null, + ?int $month = null, + ?int $day_of_week = null, + array $parameters = [] + ): CronjobSchedule { + return CronjobScheduler::getInstance()->schedule( + $this->id, + $minute, + $hour, + $day, + $month, + $day_of_week, + $parameters + ); + } + + /** + * An alias for schedule for backwards compatibility. + * + * @see CronjobTask::schedule() + * @return CronjobSchedule + */ + public function schedulePeriodic( + $minute = null, + $hour = null, + $day = null, + $month = null, + $day_of_week = null, + $priority = '', + $parameters = [] + ) { + return $this->schedule($minute, $hour, $day, $month, $day_of_week, $parameters); + } + + /** + * Extracts the default parameter values from the associated task. + * + * @return array + */ + public function extractDefaultParameters() + { + $parameters = call_user_func("{$this->class}::getParameters"); + return array_map(function ($parameter) { + // return $parameter['default'] ?? null; + return isset($parameter['default']) ? $parameter['default'] : null; + }, $parameters); + } +} diff --git a/lib/models/DataField.class.php b/lib/models/DataField.class.php deleted file mode 100644 index 8a0bdb6..0000000 --- a/lib/models/DataField.class.php +++ /dev/null @@ -1,289 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for datafield_id - * @property string $datafield_id database column - * @property I18NString|null $name database column - * @property string|null $object_type database column - * @property string|null $object_class database column - * @property string|null $edit_perms database column - * @property string|null $view_perms database column - * @property string|null $institut_id database column - * @property int $priority database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property string $type database column - * @property string $typeparam database column - * @property int $is_required database column - * @property string|null $default_value database column - * @property int $is_userfilter database column - * @property string $description database column - * @property int $system database column - * @property SimpleORMapCollection|DatafieldEntryModel[] $entries has_many DatafieldEntryModel - * @property SimpleORMapCollection|User_Visibility_Settings[] $visibility_settings has_many User_Visibility_Settings - * @property mixed $institution additional field - */ -class DataField extends SimpleORMap implements PrivacyObject -{ - /** - * Configures this model. - * - * @param Array $config Configuration array - */ - protected static function configure($config = []) - { - $config['db_table'] = 'datafields'; - $config['has_many']['entries'] = [ - 'class_name' => DatafieldEntryModel::class, - 'on_delete' => 'delete', - ]; - $config['has_many']['visibility_settings'] = [ - 'class_name' => User_Visibility_Settings::class, - 'assoc_foreign_key' => 'identifier', - 'on_delete' => 'delete', - ]; - $config['additional_fields']['institution'] = array( - 'get' => function ($object, $field) { - $institution = Institute::find($object->institut_id); - if (!$institution) { - return false; - } - return $institution; - }, - 'set' => false, - ); - - $config['i18n_fields']['name'] = true; - - parent::configure($config); - } - - protected static $permission_masks = [ - 'user' => 1, - 'autor' => 2, - 'tutor' => 4, - 'dozent' => 8, - 'admin' => 16, - 'root' => 32, - 'self' => 64, - ]; - /** - * Returns a collection of datafields filtered by objectType, - * objectClass and/or unassigned objectClasses. - * - * @param mixed $objectType Object type - * @param String $objectClass Object class - * @param bool $includeNullClass Should the object class "null" be - * included - * @return array of DataField instances - */ - public static function getDataFields($objectType = null, $objectClass = '', $includeNullClass = false) - { - $conditions = []; - $parameters = []; - - if ($objectType !== null) { - $conditions[] = 'object_type = ?'; - $parameters[] = $objectType; - } - - if ($objectClass) { - if (in_array($objectType, ['user', 'userinstrole', 'usersemdata', 'roleinstdata'])) { - $condition = ['object_class & ?']; - } else { - $condition = ['object_class = ?']; - } - if ($includeNullClass) { - $condition[] = 'object_class IS NULL'; - } - - $conditions[] = '(' . implode(' OR ', $condition) . ')'; - $parameters[] = $objectClass; - } - - $where = implode(' AND ', $conditions) ?: '1'; - - return self::findBySQL($where . " ORDER BY priority ASC, name ASC", $parameters); - } - - /** - * Returns a list of all datatype classes with an id as key and a name as - * value. - * - * @return array list of all datatype classes - */ - public static function getDataClass() - { - return [ - 'sem' => _('Veranstaltungen'), - 'inst' => _('Einrichtungen'), - 'user' => _('Benutzer'), - 'userinstrole' => _('Benutzerrollen in Einrichtungen'), - 'usersemdata' => _('Benutzer-Zusatzangaben in VA'), - 'roleinstdata' => _('Rollen in Einrichtungen'), - 'moduldeskriptor' => _('Moduldeskriptoren'), - 'modulteildeskriptor' => _('Modulteildeskriptoren'), - 'studycourse' => _('Studiengänge') - ]; - } - - /** - * Return the mask for the given permission - * - * @param string $perm the name of the permission - * @return integer the mask for the permission - * @static - */ - public static function permMask($perm) - { - return self::$permission_masks[$perm] ?? 0; - } - - /** - * liefert String zu gegebener user_class-Maske - * - * @param integer $class the user class mask - * @return string a string consisting of a comma separated list of - * permissions - */ - public static function getReadableUserClass($class) - { - $result = []; - foreach (self::$permission_masks as $perm => $mask) { - if ($class & $mask) { - $result[] = $perm; - } - } - return implode(', ', $result); - } - - /** - * Legacy handler for access via [get|set]VariableName(). - * - * @param String $method Called method - * @param Array $arguments Given arguments - * @return mixed Return value of the getter/setter - * @throws BadMethodCallException when the method does not match a - * valid pattern - */ - public function __call($method, array $arguments) - { - if (mb_substr($method, 0, 3) === 'get') { - return $this->getValue(mb_substr($method, 3)); - } - if (mb_substr($method, 0, 3) === 'set') { - return $this->setValue(mb_substr($method, 3), $arguments[0]); - } - throw new BadMethodCallException('Call to undefined method ' . __CLASS__ . '::' . $method); - } - - /** - * Sets the type and adjusts type param as well. - * - * @param String $type Type of this datafield - */ - public function setType($type) - { - $this->content['type'] = $type; - - if (!in_array($type, words('selectbox selectboxmultiple radio combo'))) { - $this->typeparam = ''; - } - } - - /** - * Returns whether a user may access this datafield. - * - * @param String $perm Permission of the user, optional defaults to - * current user - * @param String $watcher Current user - * @param String $user Associated user of the datafield - * @return bool indicating whether the datafield may be accessed. - */ - public function accessAllowed($perm = null, $watcher = '', $user = '') - { - if ($perm === null) { - $perm = $GLOBALS['user']->perms; - } - - $user_perms = self::permMask($perm); - $required_perms = self::permMask($this->view_perms); - - # permission is sufficient - if ($user_perms >= $required_perms) { - return true; - } - - // user may see his own data if this either no system field - // or the user may edit the field - if ($watcher && $user && $user === $watcher && - (!$this->system || $this->editAllowed($perm))) - { - return true; - } - - # nothing matched... - return false; - } - - /** - * Returns whether a user may edit this datafield. - * - * @param String $userPerms Permissions of the user - * @return bool indicating whether the datafield may be edited - */ - public function editAllowed($userPerms) - { - $user_perms = self::permMask($userPerms); - $required_perms = self::permMask($this->edit_perms); - - return $user_perms >= $required_perms; - } - - /** - * Specialized count method that returns the number of concrete entries. - * - * @return int number of entries - */ - public function count(): int - { - return DatafieldEntryModel::countBySQL('datafield_id = ?', [$this->id]); - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = DataField::findThru($storage->user_id, [ - 'thru_table' => 'datafields_entries', - 'thru_key' => 'range_id', - 'thru_assoc_key' => 'datafield_id', - 'assoc_foreign_key' => 'datafield_id', - ]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Datenfelder'), 'datafields', $field_data); - } - } - } -} diff --git a/lib/models/DataField.php b/lib/models/DataField.php new file mode 100644 index 0000000..8a0bdb6 --- /dev/null +++ b/lib/models/DataField.php @@ -0,0 +1,289 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for datafield_id + * @property string $datafield_id database column + * @property I18NString|null $name database column + * @property string|null $object_type database column + * @property string|null $object_class database column + * @property string|null $edit_perms database column + * @property string|null $view_perms database column + * @property string|null $institut_id database column + * @property int $priority database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property string $type database column + * @property string $typeparam database column + * @property int $is_required database column + * @property string|null $default_value database column + * @property int $is_userfilter database column + * @property string $description database column + * @property int $system database column + * @property SimpleORMapCollection|DatafieldEntryModel[] $entries has_many DatafieldEntryModel + * @property SimpleORMapCollection|User_Visibility_Settings[] $visibility_settings has_many User_Visibility_Settings + * @property mixed $institution additional field + */ +class DataField extends SimpleORMap implements PrivacyObject +{ + /** + * Configures this model. + * + * @param Array $config Configuration array + */ + protected static function configure($config = []) + { + $config['db_table'] = 'datafields'; + $config['has_many']['entries'] = [ + 'class_name' => DatafieldEntryModel::class, + 'on_delete' => 'delete', + ]; + $config['has_many']['visibility_settings'] = [ + 'class_name' => User_Visibility_Settings::class, + 'assoc_foreign_key' => 'identifier', + 'on_delete' => 'delete', + ]; + $config['additional_fields']['institution'] = array( + 'get' => function ($object, $field) { + $institution = Institute::find($object->institut_id); + if (!$institution) { + return false; + } + return $institution; + }, + 'set' => false, + ); + + $config['i18n_fields']['name'] = true; + + parent::configure($config); + } + + protected static $permission_masks = [ + 'user' => 1, + 'autor' => 2, + 'tutor' => 4, + 'dozent' => 8, + 'admin' => 16, + 'root' => 32, + 'self' => 64, + ]; + /** + * Returns a collection of datafields filtered by objectType, + * objectClass and/or unassigned objectClasses. + * + * @param mixed $objectType Object type + * @param String $objectClass Object class + * @param bool $includeNullClass Should the object class "null" be + * included + * @return array of DataField instances + */ + public static function getDataFields($objectType = null, $objectClass = '', $includeNullClass = false) + { + $conditions = []; + $parameters = []; + + if ($objectType !== null) { + $conditions[] = 'object_type = ?'; + $parameters[] = $objectType; + } + + if ($objectClass) { + if (in_array($objectType, ['user', 'userinstrole', 'usersemdata', 'roleinstdata'])) { + $condition = ['object_class & ?']; + } else { + $condition = ['object_class = ?']; + } + if ($includeNullClass) { + $condition[] = 'object_class IS NULL'; + } + + $conditions[] = '(' . implode(' OR ', $condition) . ')'; + $parameters[] = $objectClass; + } + + $where = implode(' AND ', $conditions) ?: '1'; + + return self::findBySQL($where . " ORDER BY priority ASC, name ASC", $parameters); + } + + /** + * Returns a list of all datatype classes with an id as key and a name as + * value. + * + * @return array list of all datatype classes + */ + public static function getDataClass() + { + return [ + 'sem' => _('Veranstaltungen'), + 'inst' => _('Einrichtungen'), + 'user' => _('Benutzer'), + 'userinstrole' => _('Benutzerrollen in Einrichtungen'), + 'usersemdata' => _('Benutzer-Zusatzangaben in VA'), + 'roleinstdata' => _('Rollen in Einrichtungen'), + 'moduldeskriptor' => _('Moduldeskriptoren'), + 'modulteildeskriptor' => _('Modulteildeskriptoren'), + 'studycourse' => _('Studiengänge') + ]; + } + + /** + * Return the mask for the given permission + * + * @param string $perm the name of the permission + * @return integer the mask for the permission + * @static + */ + public static function permMask($perm) + { + return self::$permission_masks[$perm] ?? 0; + } + + /** + * liefert String zu gegebener user_class-Maske + * + * @param integer $class the user class mask + * @return string a string consisting of a comma separated list of + * permissions + */ + public static function getReadableUserClass($class) + { + $result = []; + foreach (self::$permission_masks as $perm => $mask) { + if ($class & $mask) { + $result[] = $perm; + } + } + return implode(', ', $result); + } + + /** + * Legacy handler for access via [get|set]VariableName(). + * + * @param String $method Called method + * @param Array $arguments Given arguments + * @return mixed Return value of the getter/setter + * @throws BadMethodCallException when the method does not match a + * valid pattern + */ + public function __call($method, array $arguments) + { + if (mb_substr($method, 0, 3) === 'get') { + return $this->getValue(mb_substr($method, 3)); + } + if (mb_substr($method, 0, 3) === 'set') { + return $this->setValue(mb_substr($method, 3), $arguments[0]); + } + throw new BadMethodCallException('Call to undefined method ' . __CLASS__ . '::' . $method); + } + + /** + * Sets the type and adjusts type param as well. + * + * @param String $type Type of this datafield + */ + public function setType($type) + { + $this->content['type'] = $type; + + if (!in_array($type, words('selectbox selectboxmultiple radio combo'))) { + $this->typeparam = ''; + } + } + + /** + * Returns whether a user may access this datafield. + * + * @param String $perm Permission of the user, optional defaults to + * current user + * @param String $watcher Current user + * @param String $user Associated user of the datafield + * @return bool indicating whether the datafield may be accessed. + */ + public function accessAllowed($perm = null, $watcher = '', $user = '') + { + if ($perm === null) { + $perm = $GLOBALS['user']->perms; + } + + $user_perms = self::permMask($perm); + $required_perms = self::permMask($this->view_perms); + + # permission is sufficient + if ($user_perms >= $required_perms) { + return true; + } + + // user may see his own data if this either no system field + // or the user may edit the field + if ($watcher && $user && $user === $watcher && + (!$this->system || $this->editAllowed($perm))) + { + return true; + } + + # nothing matched... + return false; + } + + /** + * Returns whether a user may edit this datafield. + * + * @param String $userPerms Permissions of the user + * @return bool indicating whether the datafield may be edited + */ + public function editAllowed($userPerms) + { + $user_perms = self::permMask($userPerms); + $required_perms = self::permMask($this->edit_perms); + + return $user_perms >= $required_perms; + } + + /** + * Specialized count method that returns the number of concrete entries. + * + * @return int number of entries + */ + public function count(): int + { + return DatafieldEntryModel::countBySQL('datafield_id = ?', [$this->id]); + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = DataField::findThru($storage->user_id, [ + 'thru_table' => 'datafields_entries', + 'thru_key' => 'range_id', + 'thru_assoc_key' => 'datafield_id', + 'assoc_foreign_key' => 'datafield_id', + ]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Datenfelder'), 'datafields', $field_data); + } + } + } +} diff --git a/lib/models/DatafieldEntryModel.class.php b/lib/models/DatafieldEntryModel.class.php deleted file mode 100644 index 33503e3..0000000 --- a/lib/models/DatafieldEntryModel.class.php +++ /dev/null @@ -1,261 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $datafield_id database column - * @property string $range_id database column - * @property string|null $content database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property string $sec_range_id database column - * @property string $lang database column - * @property DataField $datafield belongs_to DataField - * @property mixed $name additional field - */ - -class DatafieldEntryModel extends SimpleORMap implements PrivacyObject -{ - protected static function configure($config = []) - { - $config['db_table'] = 'datafields_entries'; - $config['belongs_to']['datafield'] = [ - 'class_name' => DataField::class, - 'foreign_key' => 'datafield_id' - ]; - $config['additional_fields']['name'] = ['datafield', 'name']; - parent::configure($config); - } - - /** - * returns datafields belonging to given model - * if a datafield entry not exists yet, a new DatafieldEntryModel is returned - * second param filters for a given datafield id - * - * @param SimpleORMap $model Course,Institute,User,CourseMember or InstituteMember - * @param string $datafield_id - * @return array of DatafieldEntryModel - */ - public static function findByModel(SimpleORMap $model, $datafield_id = null) - { - $mask = [ - 'user' => 1, - 'autor' => 2, - 'tutor' => 4, - 'dozent' => 8, - 'admin' => 16, - 'root' => 32, - ]; - - $sec_range_id = null; - if ($model instanceof Course) { - $params[':institution_ids'] = $model->institutes->pluck('institut_id'); - $object_class = SeminarCategories::GetByTypeId($model->status)->id; - $object_type = 'sem'; - $range_id = $model->id; - } elseif ($model instanceof Institute) { - $params[':institution_ids'] = [$model->id]; - $object_class = $model->type; - $object_type = 'inst'; - $range_id = $model->id; - } elseif ($model instanceof User) { - $params[':institution_ids'] = $model->institute_memberships->pluck('institut_id'); - $object_class = $mask[$model->perms]; - $object_type = 'user'; - $range_id = $model->id; - } elseif($model instanceof CourseMember) { - $params[':institution_ids'] = $model->course->institutes->pluck('institut_id'); - $object_class = $mask[$model->status]; - $object_type = 'usersemdata'; - $range_id = $model->user_id; - $sec_range_id = $model->seminar_id; - } elseif($model instanceof InstituteMember) { - $params[':institution_ids'] = [$model->institut_id]; - $object_class = $mask[$model->inst_perms]; - $object_type = 'userinstrole'; - $range_id = $model->user_id; - $sec_range_id = $model->institut_id; - } elseif ($model instanceof ModulDeskriptor) { - $params[':institution_ids'] = ''; - if (!empty($model->modul->responsible_institute->institut_id)) { - $params[':institution_ids'] = [$model->modul->responsible_institute->institut_id]; - } - $object_class = $model->getVariant(); - $object_type = 'moduldeskriptor'; - $range_id = $model->deskriptor_id; - } elseif ($model instanceof ModulteilDeskriptor) { - $params[':institution_ids'] = [$model->modulteil->modul->responsible_institute->institut_id]; - $object_class = $model->getVariant(); - $object_type = 'modulteildeskriptor'; - $range_id = $model->deskriptor_id; - } elseif ($model instanceof StatusgruppeUser) { - if (isset($model->group->institute)) { - $params[':institution_ids'] = [$model->group->institute->id]; - } else { - $params[':institution_ids'] = []; - } - $object_class = 255; - $object_type = 'userinstrole'; - $range_id = $model->user_id; - $sec_range_id = $model->statusgruppe_id; - } elseif ($model instanceof Studiengang) { - $params[':institution_ids'] = [$model->institut_id]; - $object_class = $model->getVariant(); - $object_type = 'studycourse'; - $range_id = $model->studiengang_id; - } else { - throw new InvalidArgumentException('Wrong type of model: ' . get_class($model)); - } - - $query = "SELECT a.*, b.*, a.datafield_id, b.datafield_id AS isset_content - FROM datafields a - LEFT JOIN datafields_entries b - ON (a.datafield_id=b.datafield_id AND range_id = :range_id AND sec_range_id = :sec_range_id) - WHERE object_type = :object_type - AND (lang IS NULL OR lang = '') - AND (a.institut_id IS NULL OR a.institut_id IN (:institution_ids))"; - - if ($datafield_id !== null) { - $query .= ' AND a.datafield_id = :one_datafield_id'; - $params[':one_datafield_id'] = $datafield_id; - } - - if ($object_type === 'sem' || $object_type === 'inst') { - // find datafields by status (int) - $query .= " AND (object_class = :object_class OR object_class IS NULL) ORDER BY priority"; - $params = array_merge($params, [ - ':range_id' => (string) $range_id, - ':sec_range_id' => (string) $sec_range_id, - ':object_type' => $object_type, - ':object_class' => (int) $object_class - ]); - } else if ($object_type === 'studycourse') { - $query .= " AND (LOCATE(:object_class, object_class) OR LOCATE('all', object_class)) ORDER BY priority"; - $params = array_merge($params,[ - ':range_id' => (string) $range_id, - ':sec_range_id' => (string) $sec_range_id, - ':object_type' => $object_type, - ':object_class' => (string) $object_class, - ]); - } elseif ($object_type === 'moduldeskriptor' - || $object_type === 'modulteildeskriptor') { - // find datafields by language (string) - $query .= " AND (LOCATE(:object_class, object_class) OR object_class IS NULL) ORDER BY priority"; - $params = array_merge($params,[ - ':range_id' => (string) $range_id, - ':sec_range_id' => (string) $sec_range_id, - ':object_type' => $object_type, - ':object_class' => (string) $object_class, - ]); - } else { - // find datafields by perms or status (int) - $query .= " AND ((object_class & :object_class) OR object_class IS NULL) ORDER BY priority"; - $params = array_merge($params, [ - ':range_id' => (string) $range_id, - ':sec_range_id' => (string) $sec_range_id, - ':object_type' => $object_type, - ':object_class' => (int) $object_class, - ]); - } - - $st = DBManager::get()->prepare($query); - $st->execute($params); - $ret = []; - $c = 0; - $df_entry = new DatafieldEntryModel(); - $df_entry_i18n = new DatafieldEntryModelI18N(); - $df = new DataField(); - while ($row = $st->fetch(PDO::FETCH_ASSOC)) { - if (mb_strpos($row['type'], 'i18n') === false) { - $ret[$c] = clone $df_entry; - } else { - $ret[$c] = clone $df_entry_i18n; - $row['content'] = I18NStringDatafield::load([ - $row['datafield_id'], - $range_id, - (string) $sec_range_id - ]); - } - $ret[$c]->setData($row, true); - if (!$row['isset_content']) { - $ret[$c]->setValue('range_id', (string) $range_id); - $ret[$c]->setValue('sec_range_id', (string) $sec_range_id); - $ret[$c]->setValue('lang', ''); - } - $ret[$c]->setNew(!$row['isset_content']); - $cloned_df = clone $df; - $cloned_df->setData($row, true); - $cloned_df->setNew(false); - $ret[$c]->setValue('datafield', $cloned_df); - $c++; - } - return $ret; - } - - public function setContentLanguage($language) - { - if (!Config::get()->CONTENT_LANGUAGES[$language]) { - throw new InvalidArgumentException('Language not configured.'); - } - - $content_languages = array_keys(Config::get()->CONTENT_LANGUAGES); - if ($language == reset($content_languages)) { - $language = ''; - } - - $this->lang = $language; - } - - /** - * returns matching "old-style" DataFieldEntry object - * - * @return DataFieldEntry - */ - public function getTypedDatafield() - { - $range_id = $this->sec_range_id - ? [$this->range_id, $this->sec_range_id, $this->lang] - : [$this->range_id, '', $this->lang]; - - $df = DataFieldEntry::createDataFieldEntry($this->datafield, $range_id, $this->getValue('content')); - $observer = function ($event, $object, $user_data) { - if ($user_data['changed']) { - $this->restore(); - } - }; - NotificationCenter::addObserver($observer, '__invoke', 'DatafieldDidUpdate', $df); - - return $df; - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL("range_id = ?", [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Datenfeld Einträge'), 'datafields_entries', $field_data); - } - } - } -} diff --git a/lib/models/DatafieldEntryModel.php b/lib/models/DatafieldEntryModel.php new file mode 100644 index 0000000..33503e3 --- /dev/null +++ b/lib/models/DatafieldEntryModel.php @@ -0,0 +1,261 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $datafield_id database column + * @property string $range_id database column + * @property string|null $content database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property string $sec_range_id database column + * @property string $lang database column + * @property DataField $datafield belongs_to DataField + * @property mixed $name additional field + */ + +class DatafieldEntryModel extends SimpleORMap implements PrivacyObject +{ + protected static function configure($config = []) + { + $config['db_table'] = 'datafields_entries'; + $config['belongs_to']['datafield'] = [ + 'class_name' => DataField::class, + 'foreign_key' => 'datafield_id' + ]; + $config['additional_fields']['name'] = ['datafield', 'name']; + parent::configure($config); + } + + /** + * returns datafields belonging to given model + * if a datafield entry not exists yet, a new DatafieldEntryModel is returned + * second param filters for a given datafield id + * + * @param SimpleORMap $model Course,Institute,User,CourseMember or InstituteMember + * @param string $datafield_id + * @return array of DatafieldEntryModel + */ + public static function findByModel(SimpleORMap $model, $datafield_id = null) + { + $mask = [ + 'user' => 1, + 'autor' => 2, + 'tutor' => 4, + 'dozent' => 8, + 'admin' => 16, + 'root' => 32, + ]; + + $sec_range_id = null; + if ($model instanceof Course) { + $params[':institution_ids'] = $model->institutes->pluck('institut_id'); + $object_class = SeminarCategories::GetByTypeId($model->status)->id; + $object_type = 'sem'; + $range_id = $model->id; + } elseif ($model instanceof Institute) { + $params[':institution_ids'] = [$model->id]; + $object_class = $model->type; + $object_type = 'inst'; + $range_id = $model->id; + } elseif ($model instanceof User) { + $params[':institution_ids'] = $model->institute_memberships->pluck('institut_id'); + $object_class = $mask[$model->perms]; + $object_type = 'user'; + $range_id = $model->id; + } elseif($model instanceof CourseMember) { + $params[':institution_ids'] = $model->course->institutes->pluck('institut_id'); + $object_class = $mask[$model->status]; + $object_type = 'usersemdata'; + $range_id = $model->user_id; + $sec_range_id = $model->seminar_id; + } elseif($model instanceof InstituteMember) { + $params[':institution_ids'] = [$model->institut_id]; + $object_class = $mask[$model->inst_perms]; + $object_type = 'userinstrole'; + $range_id = $model->user_id; + $sec_range_id = $model->institut_id; + } elseif ($model instanceof ModulDeskriptor) { + $params[':institution_ids'] = ''; + if (!empty($model->modul->responsible_institute->institut_id)) { + $params[':institution_ids'] = [$model->modul->responsible_institute->institut_id]; + } + $object_class = $model->getVariant(); + $object_type = 'moduldeskriptor'; + $range_id = $model->deskriptor_id; + } elseif ($model instanceof ModulteilDeskriptor) { + $params[':institution_ids'] = [$model->modulteil->modul->responsible_institute->institut_id]; + $object_class = $model->getVariant(); + $object_type = 'modulteildeskriptor'; + $range_id = $model->deskriptor_id; + } elseif ($model instanceof StatusgruppeUser) { + if (isset($model->group->institute)) { + $params[':institution_ids'] = [$model->group->institute->id]; + } else { + $params[':institution_ids'] = []; + } + $object_class = 255; + $object_type = 'userinstrole'; + $range_id = $model->user_id; + $sec_range_id = $model->statusgruppe_id; + } elseif ($model instanceof Studiengang) { + $params[':institution_ids'] = [$model->institut_id]; + $object_class = $model->getVariant(); + $object_type = 'studycourse'; + $range_id = $model->studiengang_id; + } else { + throw new InvalidArgumentException('Wrong type of model: ' . get_class($model)); + } + + $query = "SELECT a.*, b.*, a.datafield_id, b.datafield_id AS isset_content + FROM datafields a + LEFT JOIN datafields_entries b + ON (a.datafield_id=b.datafield_id AND range_id = :range_id AND sec_range_id = :sec_range_id) + WHERE object_type = :object_type + AND (lang IS NULL OR lang = '') + AND (a.institut_id IS NULL OR a.institut_id IN (:institution_ids))"; + + if ($datafield_id !== null) { + $query .= ' AND a.datafield_id = :one_datafield_id'; + $params[':one_datafield_id'] = $datafield_id; + } + + if ($object_type === 'sem' || $object_type === 'inst') { + // find datafields by status (int) + $query .= " AND (object_class = :object_class OR object_class IS NULL) ORDER BY priority"; + $params = array_merge($params, [ + ':range_id' => (string) $range_id, + ':sec_range_id' => (string) $sec_range_id, + ':object_type' => $object_type, + ':object_class' => (int) $object_class + ]); + } else if ($object_type === 'studycourse') { + $query .= " AND (LOCATE(:object_class, object_class) OR LOCATE('all', object_class)) ORDER BY priority"; + $params = array_merge($params,[ + ':range_id' => (string) $range_id, + ':sec_range_id' => (string) $sec_range_id, + ':object_type' => $object_type, + ':object_class' => (string) $object_class, + ]); + } elseif ($object_type === 'moduldeskriptor' + || $object_type === 'modulteildeskriptor') { + // find datafields by language (string) + $query .= " AND (LOCATE(:object_class, object_class) OR object_class IS NULL) ORDER BY priority"; + $params = array_merge($params,[ + ':range_id' => (string) $range_id, + ':sec_range_id' => (string) $sec_range_id, + ':object_type' => $object_type, + ':object_class' => (string) $object_class, + ]); + } else { + // find datafields by perms or status (int) + $query .= " AND ((object_class & :object_class) OR object_class IS NULL) ORDER BY priority"; + $params = array_merge($params, [ + ':range_id' => (string) $range_id, + ':sec_range_id' => (string) $sec_range_id, + ':object_type' => $object_type, + ':object_class' => (int) $object_class, + ]); + } + + $st = DBManager::get()->prepare($query); + $st->execute($params); + $ret = []; + $c = 0; + $df_entry = new DatafieldEntryModel(); + $df_entry_i18n = new DatafieldEntryModelI18N(); + $df = new DataField(); + while ($row = $st->fetch(PDO::FETCH_ASSOC)) { + if (mb_strpos($row['type'], 'i18n') === false) { + $ret[$c] = clone $df_entry; + } else { + $ret[$c] = clone $df_entry_i18n; + $row['content'] = I18NStringDatafield::load([ + $row['datafield_id'], + $range_id, + (string) $sec_range_id + ]); + } + $ret[$c]->setData($row, true); + if (!$row['isset_content']) { + $ret[$c]->setValue('range_id', (string) $range_id); + $ret[$c]->setValue('sec_range_id', (string) $sec_range_id); + $ret[$c]->setValue('lang', ''); + } + $ret[$c]->setNew(!$row['isset_content']); + $cloned_df = clone $df; + $cloned_df->setData($row, true); + $cloned_df->setNew(false); + $ret[$c]->setValue('datafield', $cloned_df); + $c++; + } + return $ret; + } + + public function setContentLanguage($language) + { + if (!Config::get()->CONTENT_LANGUAGES[$language]) { + throw new InvalidArgumentException('Language not configured.'); + } + + $content_languages = array_keys(Config::get()->CONTENT_LANGUAGES); + if ($language == reset($content_languages)) { + $language = ''; + } + + $this->lang = $language; + } + + /** + * returns matching "old-style" DataFieldEntry object + * + * @return DataFieldEntry + */ + public function getTypedDatafield() + { + $range_id = $this->sec_range_id + ? [$this->range_id, $this->sec_range_id, $this->lang] + : [$this->range_id, '', $this->lang]; + + $df = DataFieldEntry::createDataFieldEntry($this->datafield, $range_id, $this->getValue('content')); + $observer = function ($event, $object, $user_data) { + if ($user_data['changed']) { + $this->restore(); + } + }; + NotificationCenter::addObserver($observer, '__invoke', 'DatafieldDidUpdate', $df); + + return $df; + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL("range_id = ?", [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Datenfeld Einträge'), 'datafields_entries', $field_data); + } + } + } +} diff --git a/lib/models/DatafieldEntryModelI18N.class.php b/lib/models/DatafieldEntryModelI18N.class.php deleted file mode 100644 index aa93a63..0000000 --- a/lib/models/DatafieldEntryModelI18N.class.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @copyright 2017 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.1 - * - * @property array $id alias for pk - * @property string $datafield_id database column - * @property string $range_id database column - * @property I18NString|null $content database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property string $sec_range_id database column - * @property string $lang database column - * @property DataField $datafield belongs_to DataField - * @property mixed $name additional field - */ - -class DatafieldEntryModelI18N extends DatafieldEntryModel -{ - protected static function configure($config = []) - { - $config['i18n_fields']['content'] = true; - parent::configure($config); - } -} diff --git a/lib/models/DatafieldEntryModelI18N.php b/lib/models/DatafieldEntryModelI18N.php new file mode 100644 index 0000000..aa93a63 --- /dev/null +++ b/lib/models/DatafieldEntryModelI18N.php @@ -0,0 +1,36 @@ + + * @copyright 2017 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.1 + * + * @property array $id alias for pk + * @property string $datafield_id database column + * @property string $range_id database column + * @property I18NString|null $content database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property string $sec_range_id database column + * @property string $lang database column + * @property DataField $datafield belongs_to DataField + * @property mixed $name additional field + */ + +class DatafieldEntryModelI18N extends DatafieldEntryModel +{ + protected static function configure($config = []) + { + $config['i18n_fields']['content'] = true; + parent::configure($config); + } +} diff --git a/lib/models/Degree.class.php b/lib/models/Degree.class.php deleted file mode 100644 index 6fef834..0000000 --- a/lib/models/Degree.class.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @copyright 2013 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for abschluss_id - * @property string $abschluss_id database column - * @property string $name database column - * @property string|null $name_kurz database column - * @property string|null $beschreibung database column - * @property string $author_id database column - * @property string $editor_id database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property SimpleORMapCollection|StudyCourse[] $professions has_and_belongs_to_many StudyCourse - * @property-read mixed $count_user additional field - */ -class Degree extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'abschluss'; - - $config['has_and_belongs_to_many']['professions'] = [ - 'class_name' => StudyCourse::class, - 'thru_table' => 'user_studiengang', - 'thru_key' => 'abschluss_id', - 'thru_assoc_key' => 'fach_id', - 'order_by' => 'GROUP BY fach_id ORDER BY name' - ]; - - $config['additional_fields']['count_user']['get'] = 'countUser'; - $config['registered_callbacks']['before_store'][] = "cbUpdateAuthorId"; - parent::configure($config); - } - - public function countUser() - { - $sql = 'SELECT COUNT(DISTINCT `user_id`) FROM `user_studiengang`'; - $parameters = [':degree_id' => $this->id]; - if (!$GLOBALS['perm']->have_perm('root')) { - $inst_ids = SimpleCollection::createFromArray(Institute::findBySQL('Institut_id IN (SELECT institut_id FROM roles_user WHERE userid = :user_id) - OR fakultaets_id IN (SELECT institut_id FROM roles_user WHERE userid = :user_id)', - [':user_id' => $GLOBALS['user']->user_id]))->pluck('institut_id'); - - $sql .= 'JOIN `mvv_fach_inst` as `fach_inst` ON (`user_studiengang`.`fach_id` = `fach_inst`.`fach_id`) - WHERE `user_studiengang`.`abschluss_id` = :degree_id AND `fach_inst`.`institut_id` IN (:inst_ids)'; - $parameters[':inst_ids'] = $inst_ids; - } else { - $sql .= ' WHERE `user_studiengang`.`abschluss_id` = :degree_id'; - } - - return DBManager::get()->fetchColumn($sql, $parameters); - } - - public function countUserByStudycourse($studycourse_id) - { - $stmt = DBManager::get()->prepare('SELECT COUNT(DISTINCT user_id) ' - . 'FROM user_studiengang ' - . 'WHERE fach_id = ? AND abschluss_id = ?'); - $stmt->execute([$studycourse_id, $this->id]); - return $stmt->fetchColumn(); - } - - public function cbUpdateAuthorId() - { - if ($this->isNew() || $this->isDirty()) { - $this->editor_id = $GLOBALS['user']->id; - if (!$this->getPristineValue('author_id')) { - $this->author_id = $GLOBALS['user']->id; - } - } - } -} diff --git a/lib/models/Degree.php b/lib/models/Degree.php new file mode 100644 index 0000000..6fef834 --- /dev/null +++ b/lib/models/Degree.php @@ -0,0 +1,84 @@ + + * @copyright 2013 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for abschluss_id + * @property string $abschluss_id database column + * @property string $name database column + * @property string|null $name_kurz database column + * @property string|null $beschreibung database column + * @property string $author_id database column + * @property string $editor_id database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property SimpleORMapCollection|StudyCourse[] $professions has_and_belongs_to_many StudyCourse + * @property-read mixed $count_user additional field + */ +class Degree extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'abschluss'; + + $config['has_and_belongs_to_many']['professions'] = [ + 'class_name' => StudyCourse::class, + 'thru_table' => 'user_studiengang', + 'thru_key' => 'abschluss_id', + 'thru_assoc_key' => 'fach_id', + 'order_by' => 'GROUP BY fach_id ORDER BY name' + ]; + + $config['additional_fields']['count_user']['get'] = 'countUser'; + $config['registered_callbacks']['before_store'][] = "cbUpdateAuthorId"; + parent::configure($config); + } + + public function countUser() + { + $sql = 'SELECT COUNT(DISTINCT `user_id`) FROM `user_studiengang`'; + $parameters = [':degree_id' => $this->id]; + if (!$GLOBALS['perm']->have_perm('root')) { + $inst_ids = SimpleCollection::createFromArray(Institute::findBySQL('Institut_id IN (SELECT institut_id FROM roles_user WHERE userid = :user_id) + OR fakultaets_id IN (SELECT institut_id FROM roles_user WHERE userid = :user_id)', + [':user_id' => $GLOBALS['user']->user_id]))->pluck('institut_id'); + + $sql .= 'JOIN `mvv_fach_inst` as `fach_inst` ON (`user_studiengang`.`fach_id` = `fach_inst`.`fach_id`) + WHERE `user_studiengang`.`abschluss_id` = :degree_id AND `fach_inst`.`institut_id` IN (:inst_ids)'; + $parameters[':inst_ids'] = $inst_ids; + } else { + $sql .= ' WHERE `user_studiengang`.`abschluss_id` = :degree_id'; + } + + return DBManager::get()->fetchColumn($sql, $parameters); + } + + public function countUserByStudycourse($studycourse_id) + { + $stmt = DBManager::get()->prepare('SELECT COUNT(DISTINCT user_id) ' + . 'FROM user_studiengang ' + . 'WHERE fach_id = ? AND abschluss_id = ?'); + $stmt->execute([$studycourse_id, $this->id]); + return $stmt->fetchColumn(); + } + + public function cbUpdateAuthorId() + { + if ($this->isNew() || $this->isDirty()) { + $this->editor_id = $GLOBALS['user']->id; + if (!$this->getPristineValue('author_id')) { + $this->author_id = $GLOBALS['user']->id; + } + } + } +} diff --git a/lib/models/Freetext.php b/lib/models/Freetext.php index 66b93c5..f241b5a 100644 --- a/lib/models/Freetext.php +++ b/lib/models/Freetext.php @@ -1,6 +1,6 @@ , -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ -//require_once 'lib/object.inc.php'; - -/** - * HelpContent.class.php - model class for Stud.IP help content - * - * @author Arne Schröder - * @access public - * - * @property string $id alias column for content_id - * @property string $global_content_id database column - * @property string $content_id database column - * @property string $language database column - * @property string $content database column - * @property string $route database column - * @property string $studip_version database column - * @property int $position database column - * @property int $custom database column - * @property int $visible database column - * @property string $author_email database column - * @property string $installation_id database column - * @property int $mkdate database column - * @property int $chdate database column - * @property string|null $comment database column - * @property User $author has_one User - */ -class HelpContent extends SimpleORMap -{ - /** - * configure SORM - * - * @param array $config configuration - */ - protected static function configure($config = []) - { - $config['db_table'] = 'help_content'; - - $config['has_one']['author'] = [ - 'class_name' => User::class, - 'foreign_key' => 'author_email', - 'assoc_func' => 'findOneByEmail', - ]; - - $config['registered_callbacks']['before_store'][] = 'cbUpdateStudipVersion'; - - parent::configure($config); - } - - /** - * fetches set of content from database for given route - * - * @param string $route route for help content - * @param string $language language - * @return array set of help content - */ - public static function GetContentByRoute($route = '', $language = '') - { - $language = $language ?: mb_substr($GLOBALS['user']->preferred_language, 0, 2); - if (!$language) { - $language = mb_substr(Config::get()->DEFAULT_LANGUAGE, 0, 2); - } - $route = get_route($route); - $query = "SELECT * - FROM help_content - WHERE route LIKE CONCAT(?, '%') AND language = ? AND visible = 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$route, $language]); - $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); - foreach ($ret as $index => $data) - if (! match_route($data['route'], $route)) - unset($ret[$index]); - return $ret; - } - - /** - * fetches content for given content_id - * - * @param string $id id of help content - * @return array help content object - */ - public static function GetContentByID($id = '') - { - $query = "SELECT content_id AS idx, help_content.* - FROM help_content - WHERE content_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$id]); - $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); - return current(HelpContent::GetContentObjects($ret)); - } - - /** - * fetches set of help content from database filtered by parameters - * - * @param string $term search term for content - * @param boolean $as_objects include HelpContent objects in result array - * @return array set of help content - */ - public static function GetContentByFilter($term = '') - { - $params = []; - $condition = ''; - if (mb_strlen(trim($term)) >= 3) { - $condition = "WHERE content LIKE CONCAT('%', ?, '%')"; - $params[] = $term; - } - $query = "SELECT content_id AS idx, help_content.* - FROM help_content - $condition - ORDER BY route ASC"; - $statement = DBManager::get()->prepare($query); - $statement->execute($params); - $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); - return HelpContent::GetContentObjects($ret); - } - - /** - * fetches help content conflicts - * - * @return array set of help content - */ - public static function GetConflicts() - { - $conflicts = []; - $query = "SELECT content_id AS idx, help_content.* - FROM help_content - WHERE installation_id = ? - ORDER BY route"; - $statement = DBManager::get()->prepare($query); - $statement->execute([Config::get()->STUDIP_INSTALLATION_ID]); - $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); - foreach ($ret as $index => $data) { - $query = "SELECT content_id AS idx, help_content.* - FROM help_content - WHERE global_content_id = ? AND language = ? AND studip_version >= ? AND installation_id <> ? - ORDER BY studip_version DESC LIMIT 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$data['global_content_id'], $data['language'], $data['studip_version'], Config::get()->STUDIP_INSTALLATION_ID]); - $ret2 = $statement->fetchGrouped(PDO::FETCH_ASSOC); - if (count($ret2)) { - $conflicts[] = HelpContent::GetContentObjects(array_merge([$index => $data], $ret2)); - } - } - return $conflicts; - } - - /** - * builds help content objects for given set of content data - * - * @param array $content_result content set - * @return array set of content objects - */ - public static function GetContentObjects($content_result) - { - $objects = []; - if (is_array($content_result)){ - foreach($content_result as $id => $result){ - $objects[$id] = new HelpContent(); - $objects[$id]->setData($result, true); - $objects[$id]->setNew(false); - } - } - return $objects; - } - - public function cbUpdateStudipVersion() - { - $this->studip_version = StudipVersion::getStudipVersion(); - } -} diff --git a/lib/models/HelpContent.php b/lib/models/HelpContent.php new file mode 100644 index 0000000..f1e61b1 --- /dev/null +++ b/lib/models/HelpContent.php @@ -0,0 +1,189 @@ +, +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ +//require_once 'lib/object.inc.php'; + +/** + * HelpContent.class.php - model class for Stud.IP help content + * + * @author Arne Schröder + * @access public + * + * @property string $id alias column for content_id + * @property string $global_content_id database column + * @property string $content_id database column + * @property string $language database column + * @property string $content database column + * @property string $route database column + * @property string $studip_version database column + * @property int $position database column + * @property int $custom database column + * @property int $visible database column + * @property string $author_email database column + * @property string $installation_id database column + * @property int $mkdate database column + * @property int $chdate database column + * @property string|null $comment database column + * @property User $author has_one User + */ +class HelpContent extends SimpleORMap +{ + /** + * configure SORM + * + * @param array $config configuration + */ + protected static function configure($config = []) + { + $config['db_table'] = 'help_content'; + + $config['has_one']['author'] = [ + 'class_name' => User::class, + 'foreign_key' => 'author_email', + 'assoc_func' => 'findOneByEmail', + ]; + + $config['registered_callbacks']['before_store'][] = 'cbUpdateStudipVersion'; + + parent::configure($config); + } + + /** + * fetches set of content from database for given route + * + * @param string $route route for help content + * @param string $language language + * @return array set of help content + */ + public static function GetContentByRoute($route = '', $language = '') + { + $language = $language ?: mb_substr($GLOBALS['user']->preferred_language, 0, 2); + if (!$language) { + $language = mb_substr(Config::get()->DEFAULT_LANGUAGE, 0, 2); + } + $route = get_route($route); + $query = "SELECT * + FROM help_content + WHERE route LIKE CONCAT(?, '%') AND language = ? AND visible = 1"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$route, $language]); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + foreach ($ret as $index => $data) + if (! match_route($data['route'], $route)) + unset($ret[$index]); + return $ret; + } + + /** + * fetches content for given content_id + * + * @param string $id id of help content + * @return array help content object + */ + public static function GetContentByID($id = '') + { + $query = "SELECT content_id AS idx, help_content.* + FROM help_content + WHERE content_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$id]); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + return current(HelpContent::GetContentObjects($ret)); + } + + /** + * fetches set of help content from database filtered by parameters + * + * @param string $term search term for content + * @param boolean $as_objects include HelpContent objects in result array + * @return array set of help content + */ + public static function GetContentByFilter($term = '') + { + $params = []; + $condition = ''; + if (mb_strlen(trim($term)) >= 3) { + $condition = "WHERE content LIKE CONCAT('%', ?, '%')"; + $params[] = $term; + } + $query = "SELECT content_id AS idx, help_content.* + FROM help_content + $condition + ORDER BY route ASC"; + $statement = DBManager::get()->prepare($query); + $statement->execute($params); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + return HelpContent::GetContentObjects($ret); + } + + /** + * fetches help content conflicts + * + * @return array set of help content + */ + public static function GetConflicts() + { + $conflicts = []; + $query = "SELECT content_id AS idx, help_content.* + FROM help_content + WHERE installation_id = ? + ORDER BY route"; + $statement = DBManager::get()->prepare($query); + $statement->execute([Config::get()->STUDIP_INSTALLATION_ID]); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + foreach ($ret as $index => $data) { + $query = "SELECT content_id AS idx, help_content.* + FROM help_content + WHERE global_content_id = ? AND language = ? AND studip_version >= ? AND installation_id <> ? + ORDER BY studip_version DESC LIMIT 1"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$data['global_content_id'], $data['language'], $data['studip_version'], Config::get()->STUDIP_INSTALLATION_ID]); + $ret2 = $statement->fetchGrouped(PDO::FETCH_ASSOC); + if (count($ret2)) { + $conflicts[] = HelpContent::GetContentObjects(array_merge([$index => $data], $ret2)); + } + } + return $conflicts; + } + + /** + * builds help content objects for given set of content data + * + * @param array $content_result content set + * @return array set of content objects + */ + public static function GetContentObjects($content_result) + { + $objects = []; + if (is_array($content_result)){ + foreach($content_result as $id => $result){ + $objects[$id] = new HelpContent(); + $objects[$id]->setData($result, true); + $objects[$id]->setNew(false); + } + } + return $objects; + } + + public function cbUpdateStudipVersion() + { + $this->studip_version = StudipVersion::getStudipVersion(); + } +} diff --git a/lib/models/HelpTour.class.php b/lib/models/HelpTour.class.php deleted file mode 100644 index 68bd830..0000000 --- a/lib/models/HelpTour.class.php +++ /dev/null @@ -1,407 +0,0 @@ -, -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ -require_once 'lib/object.inc.php'; - -/** - * HelpTour.class.php - model class for Stud.IP tours - * - * - * - * - * @author Arne Schröder - * @access public - * - * @property string $id alias column for tour_id - * @property string $global_tour_id database column - * @property string $tour_id database column - * @property string $name database column - * @property string $description database column - * @property string $type database column - * @property string $roles database column - * @property int $version database column - * @property string $language database column - * @property string $studip_version database column - * @property string $installation_id database column - * @property string $author_email database column - * @property int $mkdate database column - * @property int $chdate database column - * @property SimpleORMapCollection|HelpTourStep[] $steps has_many HelpTourStep - * @property SimpleORMapCollection|HelpTourAudience[] $audiences has_many HelpTourAudience - * @property HelpTourSettings $settings has_one HelpTourSettings - * @property User $author has_one User - */ -class HelpTour extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'help_tours'; - - $config['has_one']['settings'] = [ - 'class_name' => HelpTourSettings::class, - 'assoc_foreign_key' => 'tour_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['steps'] = [ - 'class_name' => HelpTourStep::class, - 'assoc_foreign_key' => 'tour_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['audiences'] = [ - 'class_name' => HelpTourAudience::class, - 'assoc_foreign_key' => 'tour_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_one']['author'] = [ - 'class_name' => User::class, - 'foreign_key' => 'author_email', - 'assoc_func' => 'findOneByEmail', - ]; - - $config['registered_callbacks']['before_store'][] = 'cbUpdateStudipVersion'; - - parent::configure($config); - } - - - public function cbUpdateStudipVersion() - { - $this->studip_version = StudipVersion::getStudipVersion(); - } - - - /** - * get visible tours for helpbar - * - * @return array set of tours - */ - public static function GetHelpbarTourData() - { - $visible_tours = []; - $active_tour_id = null; - $active_tour_step_nr = 0; - $route = get_route(); - $tours = HelpTour::getToursByRoute($route); - foreach($tours as $index => $tour) { - if ($tour->isVisible() && ($tour->settings->access !== 'link')) { - $visible_tours[$index] = $tour; - if (in_array($tour->settings->access, ['autostart', 'autostart_once']) && !$GLOBALS['user']->cfg->TOUR_AUTOSTART_DISABLE) { - $user_visit = new HelpTourUser([$tour->tour_id, $GLOBALS['user']->user_id]); - if ($tour->settings->access === 'autostart_once' && $user_visit->isNew()) { - $active_tour_id = $tour->tour_id; - $active_tour_step_nr = 1; - } elseif ($tour->settings->access === 'autostart' && !$user_visit->completed) { - $active_tour_id = $tour->tour_id; - $active_tour_step_nr = 1; - } - } - } - } - - //if there is an active tour, initialize it - if (isset($_SESSION['active_tour']['tour_id']) - && ($_SESSION['active_tour']['last_route'] === $route - || $_SESSION['active_tour']['next_route'] === $route)) - { - $active_tour = new HelpTour($_SESSION['active_tour']['tour_id']); - $step_nr = $_SESSION['active_tour']['step_nr']; - if ($_SESSION['active_tour']['last_route'] !== $route && $_SESSION['active_tour']['next_route'] === $route) { - while ($_SESSION['active_tour']['last_route'] === $active_tour->steps[$step_nr - 1]->route) { - $step_nr += 1; - } - } - if ($route === $active_tour->steps[$step_nr - 1]->route) { - $_SESSION['active_tour']['step_nr'] = $step_nr; - $active_tour_id = $_SESSION['active_tour']['tour_id']; - $active_tour_step_nr = $step_nr; - } - } - return [ - 'tours' => $visible_tours, - 'active_tour_id' => $active_tour_id, - 'active_tour_step_nr' => $active_tour_step_nr, - ]; - } - - /** - * fetches set of tours from database for given route - * - * @param string $route route for tours to begin - * @param boolean $as_objects include HelpTour objects in result array - * @return array set of tours - */ - public static function GetToursByRoute($route = '') - { - if (!$route) { - $route = get_route(); - } - $query = "SELECT tour_id AS idx, help_tours.* - FROM help_tour_steps - INNER JOIN help_tours USING (tour_id) - WHERE route = ? AND step = 1 - ORDER BY name ASC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$route]); - $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); - - return HelpTour::GetTourObjects($ret); - } - - /** - * fetches set of tours from database filtered by parameters - * - * @param string $term search term for tour name - * @param boolean $as_objects include HelpTour objects in result array - * @return array set of tours - */ - public static function GetToursByFilter($term = '') - { - $params = []; - $condition = ''; - if (mb_strlen(trim($term)) >= 3) { - $condition = "WHERE name LIKE CONCAT('%', ?, '%')"; - $params[] = $term; - } - $query = "SELECT tour_id AS idx, help_tours.* - FROM help_tours - {$condition} - ORDER BY name ASC"; - $statement = DBManager::get()->prepare($query); - $statement->execute($params); - $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); - - return HelpTour::GetTourObjects($ret); - } - - /** - * fetches tour conflicts - * - * @return array set of tour objects - */ - public static function GetConflicts() - { - $conflicts = []; - $query = "SELECT tour_id AS idx, help_tours.* - FROM help_tours - WHERE installation_id = ? - ORDER BY name ASC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([Config::get()->STUDIP_INSTALLATION_ID]); - $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); - foreach ($ret as $index => $data) { - $query = "SELECT tour_id AS idx, help_tours.* - FROM help_tours - WHERE global_tour_id = ? - AND language = ? - AND studip_version >= ? - AND installation_id <> ? - ORDER BY studip_version DESC - LIMIT 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $data['global_tour_id'], - $data['language'], - $data['studip_version'], - Config::get()->STUDIP_INSTALLATION_ID, - ]); - $ret2 = $statement->fetchGrouped(PDO::FETCH_ASSOC); - if (count($ret2) > 0) { - $conflicts[] = HelpTour::GetTourObjects(array_merge([$index => $data], $ret2)); - } - } - return $conflicts; - } - - /** - * builds tour objects for given set of tour data - * - * @param array $tour_result tour set - * @return array set of tour objects - */ - public static function GetTourObjects($tour_result) - { - $objects = []; - if (is_array($tour_result)){ - foreach($tour_result as $id => $result){ - $objects[$id] = new HelpTour(); - $objects[$id]->setData($result, true); - $objects[$id]->setNew(false); - } - } - return $objects; - } - - /** - * checks if tour is visible for current user - */ - public function isVisible() - { - if (!$this->settings->active) { - return false; - } - - if (!$GLOBALS['user']->preferred_language) { - $language = mb_substr(Config::get()->DEFAULT_LANGUAGE, 0, 2); - } else { - $language = mb_substr($GLOBALS['user']->preferred_language, 0, 2); - } - if ($language !== $this->language) { - return false; - } - - $current_role = User::findCurrent() ? User::findCurrent()->perms : 'nobody'; - if (mb_strpos($this->roles, $current_role) === false) { - return false; - } - foreach ($this->audiences as $audience) { - switch ($audience->type) { - case 'inst': - $table_name = 'user_inst'; - $field_name = 'Institut_id'; - break; - case 'sem': - $table_name = 'seminar_user'; - $field_name = 'Seminar_id'; - break; - case 'studiengang': - $table_name = 'user_studiengang'; - $field_name = 'fach_id'; - break; - case 'abschluss': - $table_name = 'user_studiengang'; - $field_name = 'abschluss_id'; - break; - case 'userdomain': - $table_name = 'user_userdomains'; - $field_name = 'userdomain_id'; - break; - } - if ($audience->range_id && $table_name) { - $query = "SELECT * - FROM {$table_name} - WHERE user_id = ? - AND {$field_name} = ?"; - $items = [$GLOBALS['user']->user_id, $audience->range_id]; - $statement = DBManager::get()->prepare($query); - $statement->execute($items); - $ret = $statement->fetchOne(PDO::FETCH_ASSOC); - if (!count($ret)) { - return false; - } - } elseif ($table_name) { - $query = "SELECT * FROM {$table_name} WHERE user_id = ?"; - $items = [$GLOBALS['user']->user_id]; - $statement = DBManager::get()->prepare($query); - $statement->execute($items); - $ret = $statement->fetchOne(PDO::FETCH_ASSOC); - if (count($ret)) { - return false; - } - } - } - return true; - } - - /** - * adds step to the tour and rearranges existing steps - */ - public function addStep($data, $position = 0) - { - $step = new HelpTourStep(); - $step->setData($data, true); - if ($position) { - $step->step = $position; - } else { - $step->step = count($this->steps) + 1; - } - $step->tour_id = $this->tour_id; - if ($step->validate()) { - if ($position && $position <= count($this->steps)) { - $query = "UPDATE help_tour_steps - SET step = step + 1 - WHERE tour_id = ? AND step >= ? - ORDER BY step DESC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->tour_id, $position]); - } - $step->store(); - $this->restore(); - return true; - } - - return false; - } - - /** - * deletes step and rearranges existing steps - */ - public function deleteStep($position = 0) - { - if (!$position || count($this->steps) < 2) { - PageLayout::postError(_('Löschen nicht möglich. Die Tour muss mindestens einen Schritt enthalten.')); - return false; - } - $query = "DELETE FROM help_tour_steps - WHERE tour_id = ? AND step = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->tour_id, $position]); - - $query = "UPDATE help_tour_steps - SET step = step - 1 - WHERE tour_id = ? AND step > ? - ORDER BY step ASC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->tour_id, $position]); - $this->restore(); - return true; - } - - /** - * checks, if basic tour data is complete - * - * @return boolean true or false - */ - public function validate() - { - if (!$this->name || !$this->description) { - PageLayout::postError(_('Die Tour muss einen Namen und eine Beschreibung haben.')); - return false; - } - if (!$this->type) { - PageLayout::postError(_('Ungültige oder fehlende Angabe zur Art der Tour.')); - return false; - } - if (!$this->roles) { - PageLayout::postError(_('Angabe des Nutzendenstatus fehlt.')); - return false; - } - if (!$this->version) { - $this->version = 1; - } - if (!$this->isNew() && count($this->steps) === 0) { - PageLayout::postError(_('Die Tour muss mindestens einen Schritt enthalten.')); - return false; - } - return true; - } -} diff --git a/lib/models/HelpTour.php b/lib/models/HelpTour.php new file mode 100644 index 0000000..68bd830 --- /dev/null +++ b/lib/models/HelpTour.php @@ -0,0 +1,407 @@ +, +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ +require_once 'lib/object.inc.php'; + +/** + * HelpTour.class.php - model class for Stud.IP tours + * + * + * + * + * @author Arne Schröder + * @access public + * + * @property string $id alias column for tour_id + * @property string $global_tour_id database column + * @property string $tour_id database column + * @property string $name database column + * @property string $description database column + * @property string $type database column + * @property string $roles database column + * @property int $version database column + * @property string $language database column + * @property string $studip_version database column + * @property string $installation_id database column + * @property string $author_email database column + * @property int $mkdate database column + * @property int $chdate database column + * @property SimpleORMapCollection|HelpTourStep[] $steps has_many HelpTourStep + * @property SimpleORMapCollection|HelpTourAudience[] $audiences has_many HelpTourAudience + * @property HelpTourSettings $settings has_one HelpTourSettings + * @property User $author has_one User + */ +class HelpTour extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'help_tours'; + + $config['has_one']['settings'] = [ + 'class_name' => HelpTourSettings::class, + 'assoc_foreign_key' => 'tour_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['steps'] = [ + 'class_name' => HelpTourStep::class, + 'assoc_foreign_key' => 'tour_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['audiences'] = [ + 'class_name' => HelpTourAudience::class, + 'assoc_foreign_key' => 'tour_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_one']['author'] = [ + 'class_name' => User::class, + 'foreign_key' => 'author_email', + 'assoc_func' => 'findOneByEmail', + ]; + + $config['registered_callbacks']['before_store'][] = 'cbUpdateStudipVersion'; + + parent::configure($config); + } + + + public function cbUpdateStudipVersion() + { + $this->studip_version = StudipVersion::getStudipVersion(); + } + + + /** + * get visible tours for helpbar + * + * @return array set of tours + */ + public static function GetHelpbarTourData() + { + $visible_tours = []; + $active_tour_id = null; + $active_tour_step_nr = 0; + $route = get_route(); + $tours = HelpTour::getToursByRoute($route); + foreach($tours as $index => $tour) { + if ($tour->isVisible() && ($tour->settings->access !== 'link')) { + $visible_tours[$index] = $tour; + if (in_array($tour->settings->access, ['autostart', 'autostart_once']) && !$GLOBALS['user']->cfg->TOUR_AUTOSTART_DISABLE) { + $user_visit = new HelpTourUser([$tour->tour_id, $GLOBALS['user']->user_id]); + if ($tour->settings->access === 'autostart_once' && $user_visit->isNew()) { + $active_tour_id = $tour->tour_id; + $active_tour_step_nr = 1; + } elseif ($tour->settings->access === 'autostart' && !$user_visit->completed) { + $active_tour_id = $tour->tour_id; + $active_tour_step_nr = 1; + } + } + } + } + + //if there is an active tour, initialize it + if (isset($_SESSION['active_tour']['tour_id']) + && ($_SESSION['active_tour']['last_route'] === $route + || $_SESSION['active_tour']['next_route'] === $route)) + { + $active_tour = new HelpTour($_SESSION['active_tour']['tour_id']); + $step_nr = $_SESSION['active_tour']['step_nr']; + if ($_SESSION['active_tour']['last_route'] !== $route && $_SESSION['active_tour']['next_route'] === $route) { + while ($_SESSION['active_tour']['last_route'] === $active_tour->steps[$step_nr - 1]->route) { + $step_nr += 1; + } + } + if ($route === $active_tour->steps[$step_nr - 1]->route) { + $_SESSION['active_tour']['step_nr'] = $step_nr; + $active_tour_id = $_SESSION['active_tour']['tour_id']; + $active_tour_step_nr = $step_nr; + } + } + return [ + 'tours' => $visible_tours, + 'active_tour_id' => $active_tour_id, + 'active_tour_step_nr' => $active_tour_step_nr, + ]; + } + + /** + * fetches set of tours from database for given route + * + * @param string $route route for tours to begin + * @param boolean $as_objects include HelpTour objects in result array + * @return array set of tours + */ + public static function GetToursByRoute($route = '') + { + if (!$route) { + $route = get_route(); + } + $query = "SELECT tour_id AS idx, help_tours.* + FROM help_tour_steps + INNER JOIN help_tours USING (tour_id) + WHERE route = ? AND step = 1 + ORDER BY name ASC"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$route]); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + + return HelpTour::GetTourObjects($ret); + } + + /** + * fetches set of tours from database filtered by parameters + * + * @param string $term search term for tour name + * @param boolean $as_objects include HelpTour objects in result array + * @return array set of tours + */ + public static function GetToursByFilter($term = '') + { + $params = []; + $condition = ''; + if (mb_strlen(trim($term)) >= 3) { + $condition = "WHERE name LIKE CONCAT('%', ?, '%')"; + $params[] = $term; + } + $query = "SELECT tour_id AS idx, help_tours.* + FROM help_tours + {$condition} + ORDER BY name ASC"; + $statement = DBManager::get()->prepare($query); + $statement->execute($params); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + + return HelpTour::GetTourObjects($ret); + } + + /** + * fetches tour conflicts + * + * @return array set of tour objects + */ + public static function GetConflicts() + { + $conflicts = []; + $query = "SELECT tour_id AS idx, help_tours.* + FROM help_tours + WHERE installation_id = ? + ORDER BY name ASC"; + $statement = DBManager::get()->prepare($query); + $statement->execute([Config::get()->STUDIP_INSTALLATION_ID]); + $ret = $statement->fetchGrouped(PDO::FETCH_ASSOC); + foreach ($ret as $index => $data) { + $query = "SELECT tour_id AS idx, help_tours.* + FROM help_tours + WHERE global_tour_id = ? + AND language = ? + AND studip_version >= ? + AND installation_id <> ? + ORDER BY studip_version DESC + LIMIT 1"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $data['global_tour_id'], + $data['language'], + $data['studip_version'], + Config::get()->STUDIP_INSTALLATION_ID, + ]); + $ret2 = $statement->fetchGrouped(PDO::FETCH_ASSOC); + if (count($ret2) > 0) { + $conflicts[] = HelpTour::GetTourObjects(array_merge([$index => $data], $ret2)); + } + } + return $conflicts; + } + + /** + * builds tour objects for given set of tour data + * + * @param array $tour_result tour set + * @return array set of tour objects + */ + public static function GetTourObjects($tour_result) + { + $objects = []; + if (is_array($tour_result)){ + foreach($tour_result as $id => $result){ + $objects[$id] = new HelpTour(); + $objects[$id]->setData($result, true); + $objects[$id]->setNew(false); + } + } + return $objects; + } + + /** + * checks if tour is visible for current user + */ + public function isVisible() + { + if (!$this->settings->active) { + return false; + } + + if (!$GLOBALS['user']->preferred_language) { + $language = mb_substr(Config::get()->DEFAULT_LANGUAGE, 0, 2); + } else { + $language = mb_substr($GLOBALS['user']->preferred_language, 0, 2); + } + if ($language !== $this->language) { + return false; + } + + $current_role = User::findCurrent() ? User::findCurrent()->perms : 'nobody'; + if (mb_strpos($this->roles, $current_role) === false) { + return false; + } + foreach ($this->audiences as $audience) { + switch ($audience->type) { + case 'inst': + $table_name = 'user_inst'; + $field_name = 'Institut_id'; + break; + case 'sem': + $table_name = 'seminar_user'; + $field_name = 'Seminar_id'; + break; + case 'studiengang': + $table_name = 'user_studiengang'; + $field_name = 'fach_id'; + break; + case 'abschluss': + $table_name = 'user_studiengang'; + $field_name = 'abschluss_id'; + break; + case 'userdomain': + $table_name = 'user_userdomains'; + $field_name = 'userdomain_id'; + break; + } + if ($audience->range_id && $table_name) { + $query = "SELECT * + FROM {$table_name} + WHERE user_id = ? + AND {$field_name} = ?"; + $items = [$GLOBALS['user']->user_id, $audience->range_id]; + $statement = DBManager::get()->prepare($query); + $statement->execute($items); + $ret = $statement->fetchOne(PDO::FETCH_ASSOC); + if (!count($ret)) { + return false; + } + } elseif ($table_name) { + $query = "SELECT * FROM {$table_name} WHERE user_id = ?"; + $items = [$GLOBALS['user']->user_id]; + $statement = DBManager::get()->prepare($query); + $statement->execute($items); + $ret = $statement->fetchOne(PDO::FETCH_ASSOC); + if (count($ret)) { + return false; + } + } + } + return true; + } + + /** + * adds step to the tour and rearranges existing steps + */ + public function addStep($data, $position = 0) + { + $step = new HelpTourStep(); + $step->setData($data, true); + if ($position) { + $step->step = $position; + } else { + $step->step = count($this->steps) + 1; + } + $step->tour_id = $this->tour_id; + if ($step->validate()) { + if ($position && $position <= count($this->steps)) { + $query = "UPDATE help_tour_steps + SET step = step + 1 + WHERE tour_id = ? AND step >= ? + ORDER BY step DESC"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->tour_id, $position]); + } + $step->store(); + $this->restore(); + return true; + } + + return false; + } + + /** + * deletes step and rearranges existing steps + */ + public function deleteStep($position = 0) + { + if (!$position || count($this->steps) < 2) { + PageLayout::postError(_('Löschen nicht möglich. Die Tour muss mindestens einen Schritt enthalten.')); + return false; + } + $query = "DELETE FROM help_tour_steps + WHERE tour_id = ? AND step = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->tour_id, $position]); + + $query = "UPDATE help_tour_steps + SET step = step - 1 + WHERE tour_id = ? AND step > ? + ORDER BY step ASC"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->tour_id, $position]); + $this->restore(); + return true; + } + + /** + * checks, if basic tour data is complete + * + * @return boolean true or false + */ + public function validate() + { + if (!$this->name || !$this->description) { + PageLayout::postError(_('Die Tour muss einen Namen und eine Beschreibung haben.')); + return false; + } + if (!$this->type) { + PageLayout::postError(_('Ungültige oder fehlende Angabe zur Art der Tour.')); + return false; + } + if (!$this->roles) { + PageLayout::postError(_('Angabe des Nutzendenstatus fehlt.')); + return false; + } + if (!$this->version) { + $this->version = 1; + } + if (!$this->isNew() && count($this->steps) === 0) { + PageLayout::postError(_('Die Tour muss mindestens einen Schritt enthalten.')); + return false; + } + return true; + } +} diff --git a/lib/models/HelpTourAudience.class.php b/lib/models/HelpTourAudience.class.php deleted file mode 100644 index 6255a46..0000000 --- a/lib/models/HelpTourAudience.class.php +++ /dev/null @@ -1,48 +0,0 @@ -, -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ -/** - * HelpTourSteps.class.php - model class for tour audiences - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Arne Schröder - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $tour_id database column - * @property string $range_id database column - * @property string $type database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - */ -class HelpTourAudience extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'help_tour_audiences'; - - parent::configure($config); - } -} diff --git a/lib/models/HelpTourAudience.php b/lib/models/HelpTourAudience.php new file mode 100644 index 0000000..6255a46 --- /dev/null +++ b/lib/models/HelpTourAudience.php @@ -0,0 +1,48 @@ +, +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ +/** + * HelpTourSteps.class.php - model class for tour audiences + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Arne Schröder + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $tour_id database column + * @property string $range_id database column + * @property string $type database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + */ +class HelpTourAudience extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'help_tour_audiences'; + + parent::configure($config); + } +} diff --git a/lib/models/HelpTourSettings.class.php b/lib/models/HelpTourSettings.class.php deleted file mode 100644 index f24c86b..0000000 --- a/lib/models/HelpTourSettings.class.php +++ /dev/null @@ -1,48 +0,0 @@ -, -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ -/** - * HelpTourSteps.class.php - model class for tour steps - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Arne Schröder - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for tour_id - * @property string $tour_id database column - * @property int $active database column - * @property string|null $access database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - */ -class HelpTourSettings extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'help_tour_settings'; - - parent::configure($config); - } -} diff --git a/lib/models/HelpTourSettings.php b/lib/models/HelpTourSettings.php new file mode 100644 index 0000000..f24c86b --- /dev/null +++ b/lib/models/HelpTourSettings.php @@ -0,0 +1,48 @@ +, +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ +/** + * HelpTourSteps.class.php - model class for tour steps + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Arne Schröder + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for tour_id + * @property string $tour_id database column + * @property int $active database column + * @property string|null $access database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + */ +class HelpTourSettings extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'help_tour_settings'; + + parent::configure($config); + } +} diff --git a/lib/models/HelpTourStep.class.php b/lib/models/HelpTourStep.class.php deleted file mode 100644 index 6f31326..0000000 --- a/lib/models/HelpTourStep.class.php +++ /dev/null @@ -1,107 +0,0 @@ -, -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ -/** - * HelpTourSteps.class.php - model class for tour steps - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Arne Schröder - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $tour_id database column - * @property int $step database column - * @property string $title database column - * @property string $tip database column - * @property string $orientation database column - * @property int $interactive database column - * @property string $css_selector database column - * @property string $route database column - * @property string $action_prev database column - * @property string $action_next database column - * @property string $author_email database column - * @property int $mkdate database column - * @property int $chdate database column - * @property HelpTour $help_tour belongs_to HelpTour - * @property User $author has_one User - */ -class HelpTourStep extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'help_tour_steps'; - - $config['belongs_to']['help_tour'] = [ - 'class_name' => HelpTour::class, - 'foreign_key' => 'tour_id', - ]; - - $config['has_one']['author'] = [ - 'class_name' => User::class, - 'foreign_key' => 'author_email', - 'assoc_func' => 'findOneByEmail', - ]; - - $config['registered_callbacks']['after_store'][] = 'cbUpdateTour'; - - parent::configure($config); - } - - - public function cbUpdateTour() - { - if (!$this->help_tour) { - return; - } - - $this->help_tour->author_email = $this->author_email; - $this->help_tour->chdate = $this->chdate; - if ($this->help_tour->isDirty()) { - $this->help_tour->store(); - } - } - - - /** - * checks, if tour step data is complete - * - * @todo Das Model sollte nix über PageLayout wissen, das sollte anders raus transportiert werden - * @return boolean true or false - */ - public function validate() { - if (!$this->orientation) - $this->orientation = 'B'; - if (!$this->title && !$this->tip) { - PageLayout::postMessage(MessageBox::error(_('Der Schritt muss einen Titel oder Inhalt besitzen.'))); - return false; - } - if (!$this->route) { - PageLayout::postMessage(MessageBox::error(_('Ungültige oder fehlende Angabe zur Seite, für die der Schritt angezeigt werden soll.'))); - return false; - } - return true; - } -} - diff --git a/lib/models/HelpTourStep.php b/lib/models/HelpTourStep.php new file mode 100644 index 0000000..6f31326 --- /dev/null +++ b/lib/models/HelpTourStep.php @@ -0,0 +1,107 @@ +, +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ +/** + * HelpTourSteps.class.php - model class for tour steps + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Arne Schröder + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $tour_id database column + * @property int $step database column + * @property string $title database column + * @property string $tip database column + * @property string $orientation database column + * @property int $interactive database column + * @property string $css_selector database column + * @property string $route database column + * @property string $action_prev database column + * @property string $action_next database column + * @property string $author_email database column + * @property int $mkdate database column + * @property int $chdate database column + * @property HelpTour $help_tour belongs_to HelpTour + * @property User $author has_one User + */ +class HelpTourStep extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'help_tour_steps'; + + $config['belongs_to']['help_tour'] = [ + 'class_name' => HelpTour::class, + 'foreign_key' => 'tour_id', + ]; + + $config['has_one']['author'] = [ + 'class_name' => User::class, + 'foreign_key' => 'author_email', + 'assoc_func' => 'findOneByEmail', + ]; + + $config['registered_callbacks']['after_store'][] = 'cbUpdateTour'; + + parent::configure($config); + } + + + public function cbUpdateTour() + { + if (!$this->help_tour) { + return; + } + + $this->help_tour->author_email = $this->author_email; + $this->help_tour->chdate = $this->chdate; + if ($this->help_tour->isDirty()) { + $this->help_tour->store(); + } + } + + + /** + * checks, if tour step data is complete + * + * @todo Das Model sollte nix über PageLayout wissen, das sollte anders raus transportiert werden + * @return boolean true or false + */ + public function validate() { + if (!$this->orientation) + $this->orientation = 'B'; + if (!$this->title && !$this->tip) { + PageLayout::postMessage(MessageBox::error(_('Der Schritt muss einen Titel oder Inhalt besitzen.'))); + return false; + } + if (!$this->route) { + PageLayout::postMessage(MessageBox::error(_('Ungültige oder fehlende Angabe zur Seite, für die der Schritt angezeigt werden soll.'))); + return false; + } + return true; + } +} + diff --git a/lib/models/HelpTourUser.class.php b/lib/models/HelpTourUser.class.php deleted file mode 100644 index aa2869d..0000000 --- a/lib/models/HelpTourUser.class.php +++ /dev/null @@ -1,69 +0,0 @@ -, -// Suchi & Berg GmbH -// +---------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +---------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +---------------------------------------------------------------------------+ -/** - * HelpTourUser.class.php - model class for tour user visits - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Arne Schröder - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $tour_id database column - * @property string $user_id database column - * @property int $step_nr database column - * @property int $completed database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - */ -class HelpTourUser extends SimpleORMap implements PrivacyObject -{ - protected static function configure($config = []) - { - $config['db_table'] = 'help_tour_user'; - - parent::configure($config); - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL("user_id = ?", [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Hilfetouren'), 'help_tour_user', $field_data); - } - } - } -} diff --git a/lib/models/HelpTourUser.php b/lib/models/HelpTourUser.php new file mode 100644 index 0000000..aa2869d --- /dev/null +++ b/lib/models/HelpTourUser.php @@ -0,0 +1,69 @@ +, +// Suchi & Berg GmbH +// +---------------------------------------------------------------------------+ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or any later version. +// +---------------------------------------------------------------------------+ +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +---------------------------------------------------------------------------+ +/** + * HelpTourUser.class.php - model class for tour user visits + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Arne Schröder + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $tour_id database column + * @property string $user_id database column + * @property int $step_nr database column + * @property int $completed database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + */ +class HelpTourUser extends SimpleORMap implements PrivacyObject +{ + protected static function configure($config = []) + { + $config['db_table'] = 'help_tour_user'; + + parent::configure($config); + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL("user_id = ?", [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Hilfetouren'), 'help_tour_user', $field_data); + } + } + } +} diff --git a/lib/models/Institute.class.php b/lib/models/Institute.class.php deleted file mode 100644 index 47cf271..0000000 --- a/lib/models/Institute.class.php +++ /dev/null @@ -1,368 +0,0 @@ - - * @author Suchi & Berg GmbH - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.0 - * - * @property string $id alias column for institut_id - * @property string $institut_id database column - * @property I18NString $name database column - * @property string $fakultaets_id database column - * @property string $strasse database column - * @property string $plz database column - * @property I18NString $url database column - * @property string $telefon database column - * @property string $email database column - * @property string $fax database column - * @property int $type database column - * @property int $mkdate database column - * @property int $chdate database column - * @property string|null $lit_plugin_name database column - * @property int $srienabled database column - * @property string $lock_rule database column - * @property SimpleORMapCollection|InstituteMember[] $members has_many InstituteMember - * @property SimpleORMapCollection|Course[] $home_courses has_many Course - * @property SimpleORMapCollection|Institute[] $sub_institutes has_many Institute - * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel - * @property SimpleORMapCollection|StudipScmEntry[] $scm has_many StudipScmEntry - * @property SimpleORMapCollection|Statusgruppen[] $status_groups has_many Statusgruppen - * @property SimpleORMapCollection|BlubberThread[] $blubberthreads has_many BlubberThread - * @property SimpleORMapCollection|ConsultationBlock[] $consultation_blocks has_many ConsultationBlock - * @property SimpleORMapCollection|ConsultationResponsibility[] $consultation_responsibilities has_many ConsultationResponsibility - * @property SimpleORMapCollection|ToolActivation[] $tools has_many ToolActivation - * @property Institute $faculty belongs_to Institute - * @property SimpleORMapCollection|Course[] $courses has_and_belongs_to_many Course - * @property-read mixed $is_fak additional field - * @property-read mixed $all_status_groups additional field - */ - -class Institute extends SimpleORMap implements Range -{ - protected static function configure($config = []) - { - $config['db_table'] = 'Institute'; - $config['additional_fields']['is_fak']['get'] = 'isFaculty'; - - $config['has_many']['members'] = [ - 'class_name' => InstituteMember::class, - 'assoc_func' => 'findByInstitute', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['home_courses'] = [ - 'class_name' => Course::class, - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['sub_institutes'] = [ - 'class_name' => Institute::class, - 'assoc_foreign_key' => 'fakultaets_id', - 'assoc_func' => 'findByFaculty', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['datafields'] = [ - 'class_name' => DatafieldEntryModel::class, - 'assoc_foreign_key' => - function($model,$params) { - $model->setValue('range_id', $params[0]->id); - }, - 'assoc_func' => 'findByModel', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'foreign_key' => - function($i) { - return [$i]; - } - ]; - $config['belongs_to']['faculty'] = [ - 'class_name' => Institute::class, - 'foreign_key' => 'fakultaets_id', - ]; - $config['has_and_belongs_to_many']['courses'] = [ - 'class_name' => Course::class, - 'thru_table' => 'seminar_inst', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['scm'] = [ - 'class_name' => StudipScmEntry::class, - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['status_groups'] = [ - 'class_name' => Statusgruppen::class, - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'order_by' => 'ORDER BY position ASC', - ]; - $config['has_many']['blubberthreads'] = [ - 'class_name' => BlubberThread::class, - 'assoc_func' => 'findByInstitut', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['consultation_blocks'] = [ - 'class_name' => ConsultationBlock::class, - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - ]; - $config['has_many']['consultation_responsibilities'] = [ - 'class_name' => ConsultationResponsibility::class, - 'assoc_func' => 'findByInstituteId', - 'on_delete' => 'delete', - ]; - $config['has_many']['tools'] = [ - 'class_name' => ToolActivation::class, - 'assoc_foreign_key' => 'range_id', - 'order_by' => 'ORDER BY position', - 'on_delete' => 'delete', - ]; - $config['additional_fields']['all_status_groups']['get'] = function ($institute) { - return Statusgruppen::findAllByRangeId($institute->id, true); - }; - - $config['i18n_fields'] = ['name', 'url']; - $config['registered_callbacks']['after_create'][] = 'setDefaultTools'; - - parent::configure($config); - } - - /** - * Returns the currently active course or false if none is active. - * - * @return Institute object of currently active institute - * @since 3.0 - */ - public static function findCurrent() - { - if (Context::isInstitute()) { - return Context::get(); - } - return null; - } - - /** - * returns array of instances of Institutes belonging to given faculty - * @param string $fakultaets_id - * @return array - */ - public static function findByFaculty($fakultaets_id) - { - return self::findBySQL("fakultaets_id=? AND fakultaets_id <> institut_id ORDER BY Name ASC", [$fakultaets_id]); - } - - /** - * returns an array of all institutes ordered by faculties and name - * @return array - */ - public static function getInstitutes() - { - $db = DBManager::get(); - $result = $db->query("SELECT Institute.Institut_id, Institute.Name, IF(Institute.Institut_id=Institute.fakultaets_id,1,0) AS is_fak " . - "FROM Institute " . - "LEFT JOIN Institute as fakultaet ON (Institute.fakultaets_id = fakultaet.Institut_id) " . - "ORDER BY fakultaet.Name ASC, is_fak DESC, Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); - return $result; - } - - /** - * returns an array of all institutes to which the given user belongs, - * ordered by faculties and name. The user role for each institute is included - * - * @param string $user_id if omitted, the current user is used - * - * @return array - */ - public static function getMyInstitutes($user_id = NULL) - { - global $perm, $user; - if (!$user_id) { - $user_id = $user->id; - } - $db = DBManager::get(); - if (!$perm->have_perm("admin", $user_id)) { - $result = $db->query("SELECT user_inst.Institut_id, Institute.Name, Institute.fakultaets_id, IF(user_inst.Institut_id=Institute.fakultaets_id,1,0) AS is_fak, user_inst.inst_perms " . - "FROM user_inst " . - "LEFT JOIN Institute USING (institut_id) " . - "WHERE (user_id = ".$db->quote($user_id)." " . - "AND (inst_perms = 'dozent' OR inst_perms = 'tutor')) " . - "ORDER BY Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); - } else if (!$perm->have_perm("root", $user_id)) { - $result = $db->query("SELECT user_inst.Institut_id, Institute.Name, Institute.fakultaets_id, IF(user_inst.Institut_id=Institute.fakultaets_id,1,0) AS is_fak, user_inst.inst_perms " . - "FROM user_inst " . - "LEFT JOIN Institute USING (institut_id) " . - "WHERE (user_id = ".$db->quote($user_id)." AND inst_perms = 'admin') " . - "ORDER BY Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); - if ($perm->is_fak_admin($user_id)) { - foreach($result as $fak) { - $combined_result[] = $fak; - $institutes = $db->query("SELECT Institut_id, Name, fakultaets_id, 0 as is_fak, 'admin' as inst_perms - FROM Institute WHERE Institut_id <> fakultaets_id AND fakultaets_id = " . $db->quote($fak['Institut_id']) - . " ORDER BY Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); - $combined_result = array_merge($combined_result, $institutes); - } - $result = $combined_result; - } - - } else { - $result = $db->query("SELECT Institute.Institut_id, Institute.Name, Institute.fakultaets_id, IF(Institute.Institut_id=Institute.fakultaets_id,1,0) AS is_fak, 'admin' AS inst_perms " . - "FROM Institute " . - "LEFT JOIN Institute as fakultaet ON (Institute.fakultaets_id = fakultaet.Institut_id) " . - "ORDER BY fakultaet.Name ASC, is_fak DESC, Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); - } - return $result; - } - - public function isFaculty() - { - return $this->fakultaets_id === $this->institut_id; - } - - /** - * Returns the full name of an institute. - * - * @param string formatting template name - * @return string Fullname - */ - public function getFullName($format = 'default'): string - { - $template['type-name'] = '%2$s: %1$s'; - if ($format === 'default' || !isset($template[$format])) { - $format = 'type-name'; - } - $type = $GLOBALS['INST_TYPE'][$this['type']]['name']; - if (!$type) { - $type = _('Einrichtung'); - } - $data[0] = $this['name']; - $data[1] = $type; - return trim(vsprintf($template[$format], array_map('trim', $data))); - } - - /** - * Returns a descriptive text for the range type. - * - * @return string - */ - public function describeRange(): string - { - return _('Einrichtung'); - } - - /** - * Returns a unique identificator for the range type. - * - * @return string - */ - public function getRangeType(): string - { - return 'institute'; - } - - /** - * Returns the id of the current range - * - * @return mixed (string|int) - */ - public function getRangeId() - { - return $this->id; - } - - /** - * {@inheritdoc} - */ - public function getConfiguration() - { - return InstituteConfig::get($this); - } - - /** - * Decides whether the user may access the range. - * - * @param string|null $user_id Optional id of a user, defaults to current user - * @return bool - * @todo Check permissions - */ - public function isAccessibleToUser($user_id = null): bool - { - return true; - } - - /** - * Decides whether the user may edit/alter the range. - * - * @param string|null $user_id Optional id of a user, defaults to current user - * @return bool - * @todo Check permissions - */ - public function isEditableByUser($user_id = null): bool - { - if ($user_id === null) { - $user_id = $GLOBALS['user']->id; - } - $member = $this->members->findOneBy('user_id', $user_id); - return ($member && in_array($member->inst_perms, ['tutor', 'dozent', 'admin'])) - || User::find($user_id)->perms === 'root'; - } - - /** - * @return SemClass - */ - public function getSemClass() - { - return SemClass::getDefaultInstituteClass($this->type); - } - - /** - * - */ - public function setDefaultTools() - { - $this->tools = []; - foreach (array_values($this->getSemClass()->getActivatedModuleObjects()) as $module) { - PluginManager::getInstance()->setPluginActivated($module->getPluginId(), $this->id, true); - $this->tools[] = ToolActivation::find([$this->id, $module->getPluginId()]); - } - } - - /** - * @param $name string name of tool / plugin - * @return bool - */ - public function isToolActive($name): bool - { - $plugin = PluginEngine::getPlugin($name); - return $plugin && $this->tools->findOneby('plugin_id', $plugin->getPluginId()); - } - - /** - * returns all activated plugins/modules for this course - * @return StudipModule[] - */ - public function getActivatedTools() - { - return array_filter($this->tools->getStudipModule()); - } - - - /** - * @see Range::__toString() - */ - public function __toString() : string - { - return $this->getFullName(); - } -} diff --git a/lib/models/Institute.php b/lib/models/Institute.php new file mode 100644 index 0000000..47cf271 --- /dev/null +++ b/lib/models/Institute.php @@ -0,0 +1,368 @@ + + * @author Suchi & Berg GmbH + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.0 + * + * @property string $id alias column for institut_id + * @property string $institut_id database column + * @property I18NString $name database column + * @property string $fakultaets_id database column + * @property string $strasse database column + * @property string $plz database column + * @property I18NString $url database column + * @property string $telefon database column + * @property string $email database column + * @property string $fax database column + * @property int $type database column + * @property int $mkdate database column + * @property int $chdate database column + * @property string|null $lit_plugin_name database column + * @property int $srienabled database column + * @property string $lock_rule database column + * @property SimpleORMapCollection|InstituteMember[] $members has_many InstituteMember + * @property SimpleORMapCollection|Course[] $home_courses has_many Course + * @property SimpleORMapCollection|Institute[] $sub_institutes has_many Institute + * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel + * @property SimpleORMapCollection|StudipScmEntry[] $scm has_many StudipScmEntry + * @property SimpleORMapCollection|Statusgruppen[] $status_groups has_many Statusgruppen + * @property SimpleORMapCollection|BlubberThread[] $blubberthreads has_many BlubberThread + * @property SimpleORMapCollection|ConsultationBlock[] $consultation_blocks has_many ConsultationBlock + * @property SimpleORMapCollection|ConsultationResponsibility[] $consultation_responsibilities has_many ConsultationResponsibility + * @property SimpleORMapCollection|ToolActivation[] $tools has_many ToolActivation + * @property Institute $faculty belongs_to Institute + * @property SimpleORMapCollection|Course[] $courses has_and_belongs_to_many Course + * @property-read mixed $is_fak additional field + * @property-read mixed $all_status_groups additional field + */ + +class Institute extends SimpleORMap implements Range +{ + protected static function configure($config = []) + { + $config['db_table'] = 'Institute'; + $config['additional_fields']['is_fak']['get'] = 'isFaculty'; + + $config['has_many']['members'] = [ + 'class_name' => InstituteMember::class, + 'assoc_func' => 'findByInstitute', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['home_courses'] = [ + 'class_name' => Course::class, + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['sub_institutes'] = [ + 'class_name' => Institute::class, + 'assoc_foreign_key' => 'fakultaets_id', + 'assoc_func' => 'findByFaculty', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['datafields'] = [ + 'class_name' => DatafieldEntryModel::class, + 'assoc_foreign_key' => + function($model,$params) { + $model->setValue('range_id', $params[0]->id); + }, + 'assoc_func' => 'findByModel', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'foreign_key' => + function($i) { + return [$i]; + } + ]; + $config['belongs_to']['faculty'] = [ + 'class_name' => Institute::class, + 'foreign_key' => 'fakultaets_id', + ]; + $config['has_and_belongs_to_many']['courses'] = [ + 'class_name' => Course::class, + 'thru_table' => 'seminar_inst', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['scm'] = [ + 'class_name' => StudipScmEntry::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['status_groups'] = [ + 'class_name' => Statusgruppen::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'order_by' => 'ORDER BY position ASC', + ]; + $config['has_many']['blubberthreads'] = [ + 'class_name' => BlubberThread::class, + 'assoc_func' => 'findByInstitut', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['consultation_blocks'] = [ + 'class_name' => ConsultationBlock::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + ]; + $config['has_many']['consultation_responsibilities'] = [ + 'class_name' => ConsultationResponsibility::class, + 'assoc_func' => 'findByInstituteId', + 'on_delete' => 'delete', + ]; + $config['has_many']['tools'] = [ + 'class_name' => ToolActivation::class, + 'assoc_foreign_key' => 'range_id', + 'order_by' => 'ORDER BY position', + 'on_delete' => 'delete', + ]; + $config['additional_fields']['all_status_groups']['get'] = function ($institute) { + return Statusgruppen::findAllByRangeId($institute->id, true); + }; + + $config['i18n_fields'] = ['name', 'url']; + $config['registered_callbacks']['after_create'][] = 'setDefaultTools'; + + parent::configure($config); + } + + /** + * Returns the currently active course or false if none is active. + * + * @return Institute object of currently active institute + * @since 3.0 + */ + public static function findCurrent() + { + if (Context::isInstitute()) { + return Context::get(); + } + return null; + } + + /** + * returns array of instances of Institutes belonging to given faculty + * @param string $fakultaets_id + * @return array + */ + public static function findByFaculty($fakultaets_id) + { + return self::findBySQL("fakultaets_id=? AND fakultaets_id <> institut_id ORDER BY Name ASC", [$fakultaets_id]); + } + + /** + * returns an array of all institutes ordered by faculties and name + * @return array + */ + public static function getInstitutes() + { + $db = DBManager::get(); + $result = $db->query("SELECT Institute.Institut_id, Institute.Name, IF(Institute.Institut_id=Institute.fakultaets_id,1,0) AS is_fak " . + "FROM Institute " . + "LEFT JOIN Institute as fakultaet ON (Institute.fakultaets_id = fakultaet.Institut_id) " . + "ORDER BY fakultaet.Name ASC, is_fak DESC, Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); + return $result; + } + + /** + * returns an array of all institutes to which the given user belongs, + * ordered by faculties and name. The user role for each institute is included + * + * @param string $user_id if omitted, the current user is used + * + * @return array + */ + public static function getMyInstitutes($user_id = NULL) + { + global $perm, $user; + if (!$user_id) { + $user_id = $user->id; + } + $db = DBManager::get(); + if (!$perm->have_perm("admin", $user_id)) { + $result = $db->query("SELECT user_inst.Institut_id, Institute.Name, Institute.fakultaets_id, IF(user_inst.Institut_id=Institute.fakultaets_id,1,0) AS is_fak, user_inst.inst_perms " . + "FROM user_inst " . + "LEFT JOIN Institute USING (institut_id) " . + "WHERE (user_id = ".$db->quote($user_id)." " . + "AND (inst_perms = 'dozent' OR inst_perms = 'tutor')) " . + "ORDER BY Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); + } else if (!$perm->have_perm("root", $user_id)) { + $result = $db->query("SELECT user_inst.Institut_id, Institute.Name, Institute.fakultaets_id, IF(user_inst.Institut_id=Institute.fakultaets_id,1,0) AS is_fak, user_inst.inst_perms " . + "FROM user_inst " . + "LEFT JOIN Institute USING (institut_id) " . + "WHERE (user_id = ".$db->quote($user_id)." AND inst_perms = 'admin') " . + "ORDER BY Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); + if ($perm->is_fak_admin($user_id)) { + foreach($result as $fak) { + $combined_result[] = $fak; + $institutes = $db->query("SELECT Institut_id, Name, fakultaets_id, 0 as is_fak, 'admin' as inst_perms + FROM Institute WHERE Institut_id <> fakultaets_id AND fakultaets_id = " . $db->quote($fak['Institut_id']) + . " ORDER BY Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); + $combined_result = array_merge($combined_result, $institutes); + } + $result = $combined_result; + } + + } else { + $result = $db->query("SELECT Institute.Institut_id, Institute.Name, Institute.fakultaets_id, IF(Institute.Institut_id=Institute.fakultaets_id,1,0) AS is_fak, 'admin' AS inst_perms " . + "FROM Institute " . + "LEFT JOIN Institute as fakultaet ON (Institute.fakultaets_id = fakultaet.Institut_id) " . + "ORDER BY fakultaet.Name ASC, is_fak DESC, Institute.Name ASC")->fetchAll(PDO::FETCH_ASSOC); + } + return $result; + } + + public function isFaculty() + { + return $this->fakultaets_id === $this->institut_id; + } + + /** + * Returns the full name of an institute. + * + * @param string formatting template name + * @return string Fullname + */ + public function getFullName($format = 'default'): string + { + $template['type-name'] = '%2$s: %1$s'; + if ($format === 'default' || !isset($template[$format])) { + $format = 'type-name'; + } + $type = $GLOBALS['INST_TYPE'][$this['type']]['name']; + if (!$type) { + $type = _('Einrichtung'); + } + $data[0] = $this['name']; + $data[1] = $type; + return trim(vsprintf($template[$format], array_map('trim', $data))); + } + + /** + * Returns a descriptive text for the range type. + * + * @return string + */ + public function describeRange(): string + { + return _('Einrichtung'); + } + + /** + * Returns a unique identificator for the range type. + * + * @return string + */ + public function getRangeType(): string + { + return 'institute'; + } + + /** + * Returns the id of the current range + * + * @return mixed (string|int) + */ + public function getRangeId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() + { + return InstituteConfig::get($this); + } + + /** + * Decides whether the user may access the range. + * + * @param string|null $user_id Optional id of a user, defaults to current user + * @return bool + * @todo Check permissions + */ + public function isAccessibleToUser($user_id = null): bool + { + return true; + } + + /** + * Decides whether the user may edit/alter the range. + * + * @param string|null $user_id Optional id of a user, defaults to current user + * @return bool + * @todo Check permissions + */ + public function isEditableByUser($user_id = null): bool + { + if ($user_id === null) { + $user_id = $GLOBALS['user']->id; + } + $member = $this->members->findOneBy('user_id', $user_id); + return ($member && in_array($member->inst_perms, ['tutor', 'dozent', 'admin'])) + || User::find($user_id)->perms === 'root'; + } + + /** + * @return SemClass + */ + public function getSemClass() + { + return SemClass::getDefaultInstituteClass($this->type); + } + + /** + * + */ + public function setDefaultTools() + { + $this->tools = []; + foreach (array_values($this->getSemClass()->getActivatedModuleObjects()) as $module) { + PluginManager::getInstance()->setPluginActivated($module->getPluginId(), $this->id, true); + $this->tools[] = ToolActivation::find([$this->id, $module->getPluginId()]); + } + } + + /** + * @param $name string name of tool / plugin + * @return bool + */ + public function isToolActive($name): bool + { + $plugin = PluginEngine::getPlugin($name); + return $plugin && $this->tools->findOneby('plugin_id', $plugin->getPluginId()); + } + + /** + * returns all activated plugins/modules for this course + * @return StudipModule[] + */ + public function getActivatedTools() + { + return array_filter($this->tools->getStudipModule()); + } + + + /** + * @see Range::__toString() + */ + public function __toString() : string + { + return $this->getFullName(); + } +} diff --git a/lib/models/InstituteMember.class.php b/lib/models/InstituteMember.class.php deleted file mode 100644 index ec1c026..0000000 --- a/lib/models/InstituteMember.class.php +++ /dev/null @@ -1,221 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property int $id database column - * @property string $user_id database column - * @property string $institut_id database column - * @property string $inst_perms database column - * @property I18NString $sprechzeiten database column - * @property I18NString $raum database column - * @property I18NString $telefon database column - * @property I18NString $fax database column - * @property int $externdefault database column - * @property int $priority database column - * @property int $visible database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel - * @property User $user belongs_to User - * @property Institute $institute belongs_to Institute - * @property mixed $vorname additional field - * @property mixed $nachname additional field - * @property mixed $username additional field - * @property mixed $email additional field - * @property mixed $title_front additional field - * @property mixed $title_rear additional field - * @property mixed $user_info additional field - * @property mixed $institute_name additional field - */ -class InstituteMember extends SimpleORMap implements PrivacyObject -{ - protected static function configure($config = []) - { - $config['db_table'] = 'user_inst'; - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id', - ]; - $config['belongs_to']['institute'] = [ - 'class_name' => Institute::class, - 'foreign_key' => 'institut_id', - ]; - $config['has_many']['datafields'] = [ - 'class_name' => DatafieldEntryModel::class, - 'assoc_foreign_key' => - function($model, $params) { - $model->setValue('range_id', $params[0]->user_id); - $model->setValue('sec_range_id', $params[0]->institut_id); - }, - 'assoc_func' => 'findByModel', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'foreign_key' => - function($institute_member) { - return [$institute_member]; - } - ]; - - $config['additional_fields']['vorname'] = ['user', 'vorname']; - $config['additional_fields']['nachname'] = ['user', 'nachname']; - $config['additional_fields']['username'] = ['user', 'username']; - $config['additional_fields']['email'] = ['user', 'email']; - $config['additional_fields']['title_front'] = ['user', 'title_front']; - $config['additional_fields']['title_rear'] = ['user', 'title_rear']; - $config['additional_fields']['user_info'] = ['user', 'info']; - - $config['additional_fields']['institute_name'] = []; - - $config['i18n_fields']['raum'] = true; - $config['i18n_fields']['sprechzeiten'] = true; - $config['i18n_fields']['telefon'] = true; - $config['i18n_fields']['fax'] = true; - - $config['registered_callbacks']['after_delete'][] = function ($member) { - $institute = $member->institute; - $user_id = $member->user_id; - - if ($institute) { - $institute->status_groups->removeUser($user_id, true); - } - }; - - parent::configure($config); - } - - public static function findByInstitute($institute_id) - { - $query = "SELECT user_inst.*, aum.Vorname, aum.Nachname, aum.Email, - aum.username, ui.title_front, ui.title_rear - FROM user_inst - LEFT JOIN auth_user_md5 aum USING (user_id) - LEFT JOIN user_info ui USING (user_id) - WHERE institut_id = ? - AND inst_perms <> 'user' - ORDER BY inst_perms, Nachname, Vorname"; - return DBManager::get()->fetchAll( - $query, - [$institute_id], - __CLASS__ . '::buildExisting' - ); - } - - public static function findByInstituteAndStatus($institute_id, $status) - { - $query = "SELECT user_inst.*, aum.Vorname, aum.Nachname, aum.Email, - aum.username, ui.title_front, ui.title_rear - FROM user_inst - LEFT JOIN auth_user_md5 aum USING (user_id) - LEFT JOIN user_info ui USING (user_id) - WHERE institut_id = ? - AND user_inst.inst_perms IN (?) - ORDER BY inst_perms, Nachname, Vorname"; - return DBManager::get()->fetchAll( - $query, - [$institute_id, is_array($status) ? $status : words($status)], - __CLASS__ . '::buildExisting' - ); - } - - public static function findByUser($user_id) - { - $query = "SELECT user_inst.*, Institute.Name AS institute_name - FROM user_inst - JOIN Institute USING (institut_id) - WHERE user_id = ? - ORDER BY priority, Institute.Name"; - return DBManager::get()->fetchAll( - $query, - [$user_id], - __CLASS__ . '::buildExisting' - ); - } - - /** - * Finds an institute membership by user and institute id. - * - * @param string $user_id - * @param string $institute_id - * @return InstituteMember|null - */ - public static function findByUserAndInstitute($user_id, $institute_id) - { - return self::findOneBySQL('user_id = ? AND institut_id = ?', [$user_id, $institute_id]); - } - - public function getUserFullname($format = 'full') - { - return User::build(array_merge( - ['motto' => ''], - $this->toArray('vorname nachname username title_front title_rear') - ))->getFullName($format); - } - - /** - * Returns the id of the default institute for a user or false if none is set. - * - * @param string $user_id Id of the user - * @return string institute id or bool false - */ - public static function getDefaultInstituteIdForUser($user_id) - { - $institute = self::findOneBySQL( - "user_id = ? AND inst_perms != 'user' AND externdefault = 1", - [$user_id] - ); - return $institute ? $institute->institut_id : false; - } - - /** - * Returns the id of the default institute for a user or false if none is set. - * - * @param string $user_id Id of the user - * @return bool true if institute was updated, false otherwise - */ - public static function ensureDefaultInstituteForUser($user_id) - { - $institute = self::findOneBySQL( - "user_id = ? AND inst_perms != 'user' ORDER BY externdefault = 1 DESC, priority", - [$user_id] - ); - if (!$institute || $institute->externdefault) { - return false; - } - - $institute->externdefault = true; - $institute->store(); - - return true; - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL('user_id = ?', [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Einrichtungs Informationen'), 'user_inst', $field_data); - } - } - } -} diff --git a/lib/models/InstituteMember.php b/lib/models/InstituteMember.php new file mode 100644 index 0000000..ec1c026 --- /dev/null +++ b/lib/models/InstituteMember.php @@ -0,0 +1,221 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property int $id database column + * @property string $user_id database column + * @property string $institut_id database column + * @property string $inst_perms database column + * @property I18NString $sprechzeiten database column + * @property I18NString $raum database column + * @property I18NString $telefon database column + * @property I18NString $fax database column + * @property int $externdefault database column + * @property int $priority database column + * @property int $visible database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel + * @property User $user belongs_to User + * @property Institute $institute belongs_to Institute + * @property mixed $vorname additional field + * @property mixed $nachname additional field + * @property mixed $username additional field + * @property mixed $email additional field + * @property mixed $title_front additional field + * @property mixed $title_rear additional field + * @property mixed $user_info additional field + * @property mixed $institute_name additional field + */ +class InstituteMember extends SimpleORMap implements PrivacyObject +{ + protected static function configure($config = []) + { + $config['db_table'] = 'user_inst'; + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + ]; + $config['belongs_to']['institute'] = [ + 'class_name' => Institute::class, + 'foreign_key' => 'institut_id', + ]; + $config['has_many']['datafields'] = [ + 'class_name' => DatafieldEntryModel::class, + 'assoc_foreign_key' => + function($model, $params) { + $model->setValue('range_id', $params[0]->user_id); + $model->setValue('sec_range_id', $params[0]->institut_id); + }, + 'assoc_func' => 'findByModel', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'foreign_key' => + function($institute_member) { + return [$institute_member]; + } + ]; + + $config['additional_fields']['vorname'] = ['user', 'vorname']; + $config['additional_fields']['nachname'] = ['user', 'nachname']; + $config['additional_fields']['username'] = ['user', 'username']; + $config['additional_fields']['email'] = ['user', 'email']; + $config['additional_fields']['title_front'] = ['user', 'title_front']; + $config['additional_fields']['title_rear'] = ['user', 'title_rear']; + $config['additional_fields']['user_info'] = ['user', 'info']; + + $config['additional_fields']['institute_name'] = []; + + $config['i18n_fields']['raum'] = true; + $config['i18n_fields']['sprechzeiten'] = true; + $config['i18n_fields']['telefon'] = true; + $config['i18n_fields']['fax'] = true; + + $config['registered_callbacks']['after_delete'][] = function ($member) { + $institute = $member->institute; + $user_id = $member->user_id; + + if ($institute) { + $institute->status_groups->removeUser($user_id, true); + } + }; + + parent::configure($config); + } + + public static function findByInstitute($institute_id) + { + $query = "SELECT user_inst.*, aum.Vorname, aum.Nachname, aum.Email, + aum.username, ui.title_front, ui.title_rear + FROM user_inst + LEFT JOIN auth_user_md5 aum USING (user_id) + LEFT JOIN user_info ui USING (user_id) + WHERE institut_id = ? + AND inst_perms <> 'user' + ORDER BY inst_perms, Nachname, Vorname"; + return DBManager::get()->fetchAll( + $query, + [$institute_id], + __CLASS__ . '::buildExisting' + ); + } + + public static function findByInstituteAndStatus($institute_id, $status) + { + $query = "SELECT user_inst.*, aum.Vorname, aum.Nachname, aum.Email, + aum.username, ui.title_front, ui.title_rear + FROM user_inst + LEFT JOIN auth_user_md5 aum USING (user_id) + LEFT JOIN user_info ui USING (user_id) + WHERE institut_id = ? + AND user_inst.inst_perms IN (?) + ORDER BY inst_perms, Nachname, Vorname"; + return DBManager::get()->fetchAll( + $query, + [$institute_id, is_array($status) ? $status : words($status)], + __CLASS__ . '::buildExisting' + ); + } + + public static function findByUser($user_id) + { + $query = "SELECT user_inst.*, Institute.Name AS institute_name + FROM user_inst + JOIN Institute USING (institut_id) + WHERE user_id = ? + ORDER BY priority, Institute.Name"; + return DBManager::get()->fetchAll( + $query, + [$user_id], + __CLASS__ . '::buildExisting' + ); + } + + /** + * Finds an institute membership by user and institute id. + * + * @param string $user_id + * @param string $institute_id + * @return InstituteMember|null + */ + public static function findByUserAndInstitute($user_id, $institute_id) + { + return self::findOneBySQL('user_id = ? AND institut_id = ?', [$user_id, $institute_id]); + } + + public function getUserFullname($format = 'full') + { + return User::build(array_merge( + ['motto' => ''], + $this->toArray('vorname nachname username title_front title_rear') + ))->getFullName($format); + } + + /** + * Returns the id of the default institute for a user or false if none is set. + * + * @param string $user_id Id of the user + * @return string institute id or bool false + */ + public static function getDefaultInstituteIdForUser($user_id) + { + $institute = self::findOneBySQL( + "user_id = ? AND inst_perms != 'user' AND externdefault = 1", + [$user_id] + ); + return $institute ? $institute->institut_id : false; + } + + /** + * Returns the id of the default institute for a user or false if none is set. + * + * @param string $user_id Id of the user + * @return bool true if institute was updated, false otherwise + */ + public static function ensureDefaultInstituteForUser($user_id) + { + $institute = self::findOneBySQL( + "user_id = ? AND inst_perms != 'user' ORDER BY externdefault = 1 DESC, priority", + [$user_id] + ); + if (!$institute || $institute->externdefault) { + return false; + } + + $institute->externdefault = true; + $institute->store(); + + return true; + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL('user_id = ?', [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Einrichtungs Informationen'), 'user_inst', $field_data); + } + } + } +} diff --git a/lib/models/InstitutePlanColumn.class.php b/lib/models/InstitutePlanColumn.class.php deleted file mode 100644 index b5ec876..0000000 --- a/lib/models/InstitutePlanColumn.class.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @license GPL2 or any version - * @since Stud.IP 4.5 - * * - * - * @property array $id alias for pk - * @property string $range_id database column - * @property int $column database column - * @property string|null $name database column - * @property int $visible database column - * @property int $mkdate database column - * @property int $chdate database column - */ -class InstitutePlanColumn extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'institute_plan_columns'; - parent::configure($config); - } - - public static function findByInstitute($institute_id) - { - return self::findBySQL('range_id=?', [$institute_id]); - } - - /** - * returns the last column for given institute - * - * @param string $institut_id - * - * @return InstitutePlanColumn last column - */ - public static function getLastColumnOfInstitute($institute_id) - { - return self::findOneBySQL('range_id=? ORDER BY `column` DESC', [$institute_id]); - } - - /** - * Sets the visibility of multiple columns for given institute - * - * @param string $institut_id - * @param array $columns_change column number to be changed - * @param int $visibility 0 or 1 - * - * @return int number of changes - */ - public static function setVisbilities($institute_id, $columns_change, $visibility) - { - $changes = 0; - foreach (self::findBySQL('range_id=?', [$institute_id]) as $plan_column) { - if (in_array($plan_column->column, $columns_change)) { - $plan_column->visible = $visibility; - if ($plan_column->store()) { - $changes++; - } - } - } - return $changes; - } - -} diff --git a/lib/models/InstitutePlanColumn.php b/lib/models/InstitutePlanColumn.php new file mode 100644 index 0000000..b5ec876 --- /dev/null +++ b/lib/models/InstitutePlanColumn.php @@ -0,0 +1,72 @@ + + * @license GPL2 or any version + * @since Stud.IP 4.5 + * * + * + * @property array $id alias for pk + * @property string $range_id database column + * @property int $column database column + * @property string|null $name database column + * @property int $visible database column + * @property int $mkdate database column + * @property int $chdate database column + */ +class InstitutePlanColumn extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'institute_plan_columns'; + parent::configure($config); + } + + public static function findByInstitute($institute_id) + { + return self::findBySQL('range_id=?', [$institute_id]); + } + + /** + * returns the last column for given institute + * + * @param string $institut_id + * + * @return InstitutePlanColumn last column + */ + public static function getLastColumnOfInstitute($institute_id) + { + return self::findOneBySQL('range_id=? ORDER BY `column` DESC', [$institute_id]); + } + + /** + * Sets the visibility of multiple columns for given institute + * + * @param string $institut_id + * @param array $columns_change column number to be changed + * @param int $visibility 0 or 1 + * + * @return int number of changes + */ + public static function setVisbilities($institute_id, $columns_change, $visibility) + { + $changes = 0; + foreach (self::findBySQL('range_id=?', [$institute_id]) as $plan_column) { + if (in_array($plan_column->column, $columns_change)) { + $plan_column->visible = $visibility; + if ($plan_column->store()) { + $changes++; + } + } + } + return $changes; + } + +} diff --git a/lib/models/Kategorie.class.php b/lib/models/Kategorie.class.php deleted file mode 100644 index a43629f..0000000 --- a/lib/models/Kategorie.class.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 2.4 - * - * @property string $id alias column for kategorie_id - * @property string $kategorie_id database column - * @property string $range_id database column - * @property I18NString $name database column - * @property I18NString $content database column - * @property int $mkdate database column - * @property int $chdate database column - * @property int $priority database column - * @property User $user belongs_to User - */ - -class Kategorie extends SimpleORMap -{ - /** - * Configures the model. - * - * @param array $config Configuration array - */ - protected static function configure($config = []) - { - $config['db_table'] = 'kategorien'; - - $config['belongs_to'] = [ - 'user' => [ - 'class_name' => User::class, - 'foreign_key' => 'range_id', - ], - ]; - - $config['i18n_fields'] = [ - 'name' => true, - 'content' => true, - ]; - - parent::configure($config); - } - - /** - * Finds all categories of a specific user - * - * @param string $user_id Id of the user - * @return Kategorie[] of category objects - */ - public static function findByUserId(string $user_id): array - { - return self::findByRange_id($user_id, 'ORDER BY priority'); - } - - /** - * Increases all category priorities of a user - * - * @param string $user_id Id of the user - * @return bool indicating if anything has changed - */ - public static function increasePrioritiesByUserId(string $user_id): bool - { - $query = "UPDATE kategorien SET priority = priority + 1 WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$user_id]); - return $statement->rowCount() > 0; - } -} diff --git a/lib/models/Kategorie.php b/lib/models/Kategorie.php new file mode 100644 index 0000000..a43629f --- /dev/null +++ b/lib/models/Kategorie.php @@ -0,0 +1,76 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 2.4 + * + * @property string $id alias column for kategorie_id + * @property string $kategorie_id database column + * @property string $range_id database column + * @property I18NString $name database column + * @property I18NString $content database column + * @property int $mkdate database column + * @property int $chdate database column + * @property int $priority database column + * @property User $user belongs_to User + */ + +class Kategorie extends SimpleORMap +{ + /** + * Configures the model. + * + * @param array $config Configuration array + */ + protected static function configure($config = []) + { + $config['db_table'] = 'kategorien'; + + $config['belongs_to'] = [ + 'user' => [ + 'class_name' => User::class, + 'foreign_key' => 'range_id', + ], + ]; + + $config['i18n_fields'] = [ + 'name' => true, + 'content' => true, + ]; + + parent::configure($config); + } + + /** + * Finds all categories of a specific user + * + * @param string $user_id Id of the user + * @return Kategorie[] of category objects + */ + public static function findByUserId(string $user_id): array + { + return self::findByRange_id($user_id, 'ORDER BY priority'); + } + + /** + * Increases all category priorities of a user + * + * @param string $user_id Id of the user + * @return bool indicating if anything has changed + */ + public static function increasePrioritiesByUserId(string $user_id): bool + { + $query = "UPDATE kategorien SET priority = priority + 1 WHERE range_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_id]); + return $statement->rowCount() > 0; + } +} diff --git a/lib/models/LikertScale.php b/lib/models/LikertScale.php index 833d794..3a055f3 100644 --- a/lib/models/LikertScale.php +++ b/lib/models/LikertScale.php @@ -1,6 +1,4 @@ store(); - * @endcode - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author André Noack - * @copyright 2011 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property string $id alias column for lock_id - * @property string $lock_id database column - * @property string $permission database column - * @property string $name database column - * @property string $description database column - * @property JSONArrayObject $attributes database column - * @property string $object_type database column - * @property string $user_id database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - */ - -class LockRule extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'lock_rules'; - - $config['serialized_fields']['attributes'] = JSONArrayObject::class; - - parent::configure($config); - } - - /** - * returns the lockrule for a course - * - * @param string $seminar_id id of a course - * @return LockRule - */ - static function findBySeminar($seminar_id) - { - $db = DBManager::get(); - $lock_rule_id = $db->query("SELECT lock_rule FROM seminare WHERE seminar_id = " . $db->quote($seminar_id)) - ->fetchColumn(); - return self::find($lock_rule_id); - } - - /** - * returns the lockrule for an institute - * - * @param string $institute_id id of an institute - * @return LockRule - */ - static function findByInstitute($institute_id) - { - $db = DBManager::get(); - $lock_rule_id = $db->query("SELECT lock_rule FROM Institute WHERE Institut_id = " . $db->quote($institute_id)) - ->fetchColumn(); - return self::find($lock_rule_id); - } - - /** - * returns the lockrule for a user - * - * @param string $user_id id of a user - * @return LockRule - */ - static function findByUser($user_id) - { - $db = DBManager::get(); - $lock_rule_id = $db->query("SELECT lock_rule FROM user_info WHERE user_id = " . $db->quote($user_id)) - ->fetchColumn(); - return self::find($lock_rule_id); - } - - /** - * returns all exisiting lockrules for a given entity type - * - * @param string $type entity type, one of [sem,inst,user] - * @return array of LockRule objects - */ - static function findAllByType($type) - { - return self::findByObject_type($type, " ORDER BY name"); - } - /** - * @see SimpleORMap::delete() - */ - function delete() - { - $id = $this->getId(); - $object_type = $this->object_type; - $ret = parent::delete(); - - $db = DBManager::get(); - $tables['sem'] = 'seminare'; - $tables['inst'] = 'Institute'; - $tables['user'] = 'user_info'; - $db->exec("UPDATE " . $tables[$object_type] . " SET lock_rule='' WHERE lock_rule = " . $db->quote($id)); - return $ret; - } - - /** - * returns the number of uses for this lockrule - * - * @return integer - */ - function getUsage() - { - if (!$this->isNew()) { - $db = DBManager::get(); - $tables['sem'] = 'seminare'; - $tables['inst'] = 'Institute'; - $tables['user'] = 'user_info'; - return $db->query("SELECT COUNT(*) FROM " . $tables[$this->object_type] . " WHERE lock_rule = " . $db->quote($this->getId()))->fetchColumn(); - } else { - return 0; - } - } -} diff --git a/lib/models/LockRule.php b/lib/models/LockRule.php new file mode 100644 index 0000000..a3ef271 --- /dev/null +++ b/lib/models/LockRule.php @@ -0,0 +1,135 @@ +store(); + * @endcode + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author André Noack + * @copyright 2011 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property string $id alias column for lock_id + * @property string $lock_id database column + * @property string $permission database column + * @property string $name database column + * @property string $description database column + * @property JSONArrayObject $attributes database column + * @property string $object_type database column + * @property string $user_id database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + */ + +class LockRule extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'lock_rules'; + + $config['serialized_fields']['attributes'] = JSONArrayObject::class; + + parent::configure($config); + } + + /** + * returns the lockrule for a course + * + * @param string $seminar_id id of a course + * @return LockRule + */ + static function findBySeminar($seminar_id) + { + $db = DBManager::get(); + $lock_rule_id = $db->query("SELECT lock_rule FROM seminare WHERE seminar_id = " . $db->quote($seminar_id)) + ->fetchColumn(); + return self::find($lock_rule_id); + } + + /** + * returns the lockrule for an institute + * + * @param string $institute_id id of an institute + * @return LockRule + */ + static function findByInstitute($institute_id) + { + $db = DBManager::get(); + $lock_rule_id = $db->query("SELECT lock_rule FROM Institute WHERE Institut_id = " . $db->quote($institute_id)) + ->fetchColumn(); + return self::find($lock_rule_id); + } + + /** + * returns the lockrule for a user + * + * @param string $user_id id of a user + * @return LockRule + */ + static function findByUser($user_id) + { + $db = DBManager::get(); + $lock_rule_id = $db->query("SELECT lock_rule FROM user_info WHERE user_id = " . $db->quote($user_id)) + ->fetchColumn(); + return self::find($lock_rule_id); + } + + /** + * returns all exisiting lockrules for a given entity type + * + * @param string $type entity type, one of [sem,inst,user] + * @return array of LockRule objects + */ + static function findAllByType($type) + { + return self::findByObject_type($type, " ORDER BY name"); + } + /** + * @see SimpleORMap::delete() + */ + function delete() + { + $id = $this->getId(); + $object_type = $this->object_type; + $ret = parent::delete(); + + $db = DBManager::get(); + $tables['sem'] = 'seminare'; + $tables['inst'] = 'Institute'; + $tables['user'] = 'user_info'; + $db->exec("UPDATE " . $tables[$object_type] . " SET lock_rule='' WHERE lock_rule = " . $db->quote($id)); + return $ret; + } + + /** + * returns the number of uses for this lockrule + * + * @return integer + */ + function getUsage() + { + if (!$this->isNew()) { + $db = DBManager::get(); + $tables['sem'] = 'seminare'; + $tables['inst'] = 'Institute'; + $tables['user'] = 'user_info'; + return $db->query("SELECT COUNT(*) FROM " . $tables[$this->object_type] . " WHERE lock_rule = " . $db->quote($this->getId()))->fetchColumn(); + } else { + return 0; + } + } +} diff --git a/lib/models/LoginBackground.class.php b/lib/models/LoginBackground.class.php deleted file mode 100644 index 3ede275..0000000 --- a/lib/models/LoginBackground.class.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property int $id alias column for background_id - * @property int $background_id database column - * @property string $filename database column - * @property int $mobile database column - * @property int $desktop database column - * @property int $in_release database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - */ -class LoginBackground extends SimpleORMap -{ - /** - * Configures this model. - * - * @param Array $config - */ - protected static function configure($config = []) - { - $config['db_table'] = 'loginbackgrounds'; - - $config['registered_callbacks']['after_delete'][] = function ($pic) { - if (file_exists($pic->getPath())) { - unlink($pic->getPath()); - } - }; - - parent::configure($config); - } - - /** - * @return string The full URL to this picture. - */ - public function getURL() - { - return URLHelper::getURL( - $GLOBALS['DYNAMIC_CONTENT_URL'] - . self::getRelativePath() - . "/{$this->id}.{$this->getExtension()}", - null, - true - ); - } - - /** - * @return string the full file system path to this picture. - */ - public function getPath() - { - return self::getPictureDirectory() . "/{$this->id}.{$this->getExtension()}"; - } - - /** - * @return int The file size in bytes. - */ - public function getFilesize() - { - if (file_exists($this->getPath())) { - return filesize($this->getPath()); - } - return false; - } - - /** - * @return int The picture dimensions as provided by getimagesize. - */ - public function getDimensions() - { - if (file_exists($this->getPath())) { - return getimagesize($this->getPath()); - } - return false; - } - - /** - * @return string The picture's extension - */ - public function getExtension() - { - return pathinfo($this->filename, PATHINFO_EXTENSION); - } - - /** - * Provides a random picture for the given view. - * @param string $view one of 'desktop', 'mobile'. - * @return LoginBackground One of the available pictures. - */ - public static function getRandomPicture($view = 'desktop') - { - if (!in_array($view, ['desktop', 'mobile'])) { - throw new Exception('Unknown view mode'); - } - - $pic = self::findOneBySQL("{$view} = 1 ORDER BY RAND() LIMIT 1"); - return $pic; - } - - /** - * @return string The relative path to the Stud.IP web root. - */ - public static function getRelativePath() - { - return '/loginbackgrounds'; - } - - /** - * @return The directory where all available background pictures live. - */ - public static function getPictureDirectory() - { - return $GLOBALS['DYNAMIC_CONTENT_PATH'] . self::getRelativePath(); - } - -} diff --git a/lib/models/LoginBackground.php b/lib/models/LoginBackground.php new file mode 100644 index 0000000..3ede275 --- /dev/null +++ b/lib/models/LoginBackground.php @@ -0,0 +1,127 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property int $id alias column for background_id + * @property int $background_id database column + * @property string $filename database column + * @property int $mobile database column + * @property int $desktop database column + * @property int $in_release database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + */ +class LoginBackground extends SimpleORMap +{ + /** + * Configures this model. + * + * @param Array $config + */ + protected static function configure($config = []) + { + $config['db_table'] = 'loginbackgrounds'; + + $config['registered_callbacks']['after_delete'][] = function ($pic) { + if (file_exists($pic->getPath())) { + unlink($pic->getPath()); + } + }; + + parent::configure($config); + } + + /** + * @return string The full URL to this picture. + */ + public function getURL() + { + return URLHelper::getURL( + $GLOBALS['DYNAMIC_CONTENT_URL'] + . self::getRelativePath() + . "/{$this->id}.{$this->getExtension()}", + null, + true + ); + } + + /** + * @return string the full file system path to this picture. + */ + public function getPath() + { + return self::getPictureDirectory() . "/{$this->id}.{$this->getExtension()}"; + } + + /** + * @return int The file size in bytes. + */ + public function getFilesize() + { + if (file_exists($this->getPath())) { + return filesize($this->getPath()); + } + return false; + } + + /** + * @return int The picture dimensions as provided by getimagesize. + */ + public function getDimensions() + { + if (file_exists($this->getPath())) { + return getimagesize($this->getPath()); + } + return false; + } + + /** + * @return string The picture's extension + */ + public function getExtension() + { + return pathinfo($this->filename, PATHINFO_EXTENSION); + } + + /** + * Provides a random picture for the given view. + * @param string $view one of 'desktop', 'mobile'. + * @return LoginBackground One of the available pictures. + */ + public static function getRandomPicture($view = 'desktop') + { + if (!in_array($view, ['desktop', 'mobile'])) { + throw new Exception('Unknown view mode'); + } + + $pic = self::findOneBySQL("{$view} = 1 ORDER BY RAND() LIMIT 1"); + return $pic; + } + + /** + * @return string The relative path to the Stud.IP web root. + */ + public static function getRelativePath() + { + return '/loginbackgrounds'; + } + + /** + * @return The directory where all available background pictures live. + */ + public static function getPictureDirectory() + { + return $GLOBALS['DYNAMIC_CONTENT_PATH'] . self::getRelativePath(); + } + +} diff --git a/lib/models/LoginFaq.class.php b/lib/models/LoginFaq.class.php deleted file mode 100644 index d6cad40..0000000 --- a/lib/models/LoginFaq.class.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @copyright 2023 data-quest - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 5.5 -*/ -class LoginFaq extends SimpleORMap -{ - /** - * @param array $config - */ - protected static function configure($config = []) - { - $config['db_table'] = 'login_faq'; - - $config['i18n_fields']['title'] = true; - $config['i18n_fields']['description'] = true; - - parent::configure($config); - } -} diff --git a/lib/models/LoginFaq.php b/lib/models/LoginFaq.php new file mode 100644 index 0000000..d6cad40 --- /dev/null +++ b/lib/models/LoginFaq.php @@ -0,0 +1,33 @@ + + * @copyright 2023 data-quest + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 5.5 +*/ +class LoginFaq extends SimpleORMap +{ + /** + * @param array $config + */ + protected static function configure($config = []) + { + $config['db_table'] = 'login_faq'; + + $config['i18n_fields']['title'] = true; + $config['i18n_fields']['description'] = true; + + parent::configure($config); + } +} diff --git a/lib/models/MailQueueEntry.class.php b/lib/models/MailQueueEntry.class.php deleted file mode 100644 index d0f9685..0000000 --- a/lib/models/MailQueueEntry.class.php +++ /dev/null @@ -1,141 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -/** - * Class to handle entries in the mail-queue in Stud.IP. - * Use MailQueueEntry::add($mail, $message_id, $user_id) to add a mail to the queue - * and MailQueueEntry::sendAll() or MailQueueEntry::sendNew() to flush the queue - * and send the mails. - * - * @property string $id alias column for mail_queue_id - * @property string $mail_queue_id database column - * @property JSONArrayObject $mail database column - * @property string|null $message_id database column - * @property string|null $user_id database column - * @property int $tries database column - * @property int $last_try database column - * @property int $mkdate database column - * @property int $chdate database column - */ -class MailQueueEntry extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'mail_queue_entries'; - - $config['serialized_fields']['mail'] = JSONArrayObject::class; - - parent::configure($config); - } - - /** - * Add an email to the queue. - * @param StudipMail $mail : the mailobject that should be added and sent later. - * @param string|null $message_id : the id of the Stud.IP internal message the - * mail is related to. Leave this null if it isn't related to any internal message. - * @param string|null $user_id : user_id of the receiver. Leave null if the - * receiver has no account in Stud.IP. - * @return MailQueueEntry : object in the mailqueue. - */ - public static function add(StudipMail $mail, $message_id = null, $user_id = null) - { - $queue_entry = new self(); - $queue_entry['mail'] = $mail->toArray(); - $queue_entry['message_id'] = $message_id; - $queue_entry['user_id'] = $user_id; - $queue_entry['tries'] = 0; - $queue_entry->store(); - - return $queue_entry; - } - - /** - * Sends all new mails in the mailqueue (which means they haven't been sent yet). - */ - public static function sendNew() - { - self::findEachBySQL(function ($m) { - $m->send(); - }, "tries = 0 ORDER BY mkdate"); - } - - /** - * Sends all mails in the mailqueue. Stud.IP will give each mail 24 tries to - * deliver it. If the mail could not be sent after 24 tries (which are 24 - * hours) it will stay in the mailqueue table but won't be sent anymore. - * Each mail will only be tried to deliver once per hour. So if it fails - * Stud.IP will try again next hour. - * - * @param int $limit The maximum amount of messages to be sent. - * @return array An empty array if no status messages are output - * or an array with status messages, one for each mail. - */ - public static function sendAll($limit = null) - { - //The status messages will be returned - $status_messages = []; - - self::findEachBySQL(function ($m) use (&$status_messages) { - // Reconstruct the StudipMail object - $mail = new StudipMail($m->mail); - $status_message = sprintf( - 'sending message %1$s (sender: %2$s, %3$u recipient(s))...', - $m->message_id, - $mail->getSenderEmail(), - count($mail->getRecipients()) - ); - - $was_sent = $m->send(); - $status_message .= $was_sent ? 'DONE' : 'FAILURE'; - - if ($m->tries > 0) { - // If sending the message has failed at least once - // we add the amount of tries to the status message. - $status_message .= "(t={$m->tries})"; - } - - $status_messages[] = $status_message; - }, "tries = 0 " . - "OR (last_try > (UNIX_TIMESTAMP() - 60 * 60) AND tries < 25) ORDER BY tries, mkdate". - ($limit > 0 ? " LIMIT ". (int) $limit : "") - ); - - return $status_messages; - } - - /** - * Sends the object in the mailqueue. If this succeeds, the object will be - * deleted immediately. Otherwise the field "tries" in the mailqueue table - * will be incremented by one. - * - * @return bool True, if the mail in the mailqueue entry could be sent, - * false otherwise. - */ - public function send() - { - $mail = new StudipMail($this->mail); - - if ($mail->getRecipients()) { - $success = $mail->send(); - if ($success) { - $this->delete(); - } else { - $this['tries'] = $this['tries'] + 1; - $this['last_try'] = time(); - $this->store(); - } - } else { - $success = false; - $this->delete(); - } - return $success; - } -} diff --git a/lib/models/MailQueueEntry.php b/lib/models/MailQueueEntry.php new file mode 100644 index 0000000..d0f9685 --- /dev/null +++ b/lib/models/MailQueueEntry.php @@ -0,0 +1,141 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +/** + * Class to handle entries in the mail-queue in Stud.IP. + * Use MailQueueEntry::add($mail, $message_id, $user_id) to add a mail to the queue + * and MailQueueEntry::sendAll() or MailQueueEntry::sendNew() to flush the queue + * and send the mails. + * + * @property string $id alias column for mail_queue_id + * @property string $mail_queue_id database column + * @property JSONArrayObject $mail database column + * @property string|null $message_id database column + * @property string|null $user_id database column + * @property int $tries database column + * @property int $last_try database column + * @property int $mkdate database column + * @property int $chdate database column + */ +class MailQueueEntry extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'mail_queue_entries'; + + $config['serialized_fields']['mail'] = JSONArrayObject::class; + + parent::configure($config); + } + + /** + * Add an email to the queue. + * @param StudipMail $mail : the mailobject that should be added and sent later. + * @param string|null $message_id : the id of the Stud.IP internal message the + * mail is related to. Leave this null if it isn't related to any internal message. + * @param string|null $user_id : user_id of the receiver. Leave null if the + * receiver has no account in Stud.IP. + * @return MailQueueEntry : object in the mailqueue. + */ + public static function add(StudipMail $mail, $message_id = null, $user_id = null) + { + $queue_entry = new self(); + $queue_entry['mail'] = $mail->toArray(); + $queue_entry['message_id'] = $message_id; + $queue_entry['user_id'] = $user_id; + $queue_entry['tries'] = 0; + $queue_entry->store(); + + return $queue_entry; + } + + /** + * Sends all new mails in the mailqueue (which means they haven't been sent yet). + */ + public static function sendNew() + { + self::findEachBySQL(function ($m) { + $m->send(); + }, "tries = 0 ORDER BY mkdate"); + } + + /** + * Sends all mails in the mailqueue. Stud.IP will give each mail 24 tries to + * deliver it. If the mail could not be sent after 24 tries (which are 24 + * hours) it will stay in the mailqueue table but won't be sent anymore. + * Each mail will only be tried to deliver once per hour. So if it fails + * Stud.IP will try again next hour. + * + * @param int $limit The maximum amount of messages to be sent. + * @return array An empty array if no status messages are output + * or an array with status messages, one for each mail. + */ + public static function sendAll($limit = null) + { + //The status messages will be returned + $status_messages = []; + + self::findEachBySQL(function ($m) use (&$status_messages) { + // Reconstruct the StudipMail object + $mail = new StudipMail($m->mail); + $status_message = sprintf( + 'sending message %1$s (sender: %2$s, %3$u recipient(s))...', + $m->message_id, + $mail->getSenderEmail(), + count($mail->getRecipients()) + ); + + $was_sent = $m->send(); + $status_message .= $was_sent ? 'DONE' : 'FAILURE'; + + if ($m->tries > 0) { + // If sending the message has failed at least once + // we add the amount of tries to the status message. + $status_message .= "(t={$m->tries})"; + } + + $status_messages[] = $status_message; + }, "tries = 0 " . + "OR (last_try > (UNIX_TIMESTAMP() - 60 * 60) AND tries < 25) ORDER BY tries, mkdate". + ($limit > 0 ? " LIMIT ". (int) $limit : "") + ); + + return $status_messages; + } + + /** + * Sends the object in the mailqueue. If this succeeds, the object will be + * deleted immediately. Otherwise the field "tries" in the mailqueue table + * will be incremented by one. + * + * @return bool True, if the mail in the mailqueue entry could be sent, + * false otherwise. + */ + public function send() + { + $mail = new StudipMail($this->mail); + + if ($mail->getRecipients()) { + $success = $mail->send(); + if ($success) { + $this->delete(); + } else { + $this['tries'] = $this['tries'] + 1; + $this['last_try'] = time(); + $this->store(); + } + } else { + $success = false; + $this->delete(); + } + return $success; + } +} diff --git a/lib/models/Message.class.php b/lib/models/Message.class.php deleted file mode 100644 index 46493d6..0000000 --- a/lib/models/Message.class.php +++ /dev/null @@ -1,343 +0,0 @@ - User::class, - 'foreign_key' => 'autor_id' - ]; - $config['has_one']['originator'] = [ - 'class_name' => MessageUser::class, - 'assoc_func' => 'findSentByMessageId', - 'on_store' => 'store', - 'on_delete' => 'delete' - ]; - $config['has_many']['receivers'] = [ - 'class_name' => MessageUser::class, - 'assoc_func' => 'findReceivedByMessageId', - 'on_store' => 'store', - 'on_delete' => 'delete' - ]; - $config['has_one']['attachment_folder'] = [ - 'class_name' => Folder::class, - 'assoc_foreign_key' => 'range_id', - 'on_store' => 'store', - 'on_delete' => 'delete' - ]; - - parent::configure($config); - } - - public static function markAllAs($user_id = null, $state_of_flag = 1) - { - PersonalNotifications::markAsReadByHTML('message_%', $user_id ?: $GLOBALS['user']->id); - - $query = "UPDATE message_user - SET readed = :flag - WHERE user_id = :user_id - AND snd_rec = 'rec' AND deleted = 0 - AND readed = :other_flag"; - $statement = DBManager::get()->prepare($query); - return $statement->execute([ - 'user_id' => $user_id ?: $GLOBALS['user']->id, - 'flag' => $state_of_flag ? 1 : 0, - 'other_flag' => $state_of_flag ? 0 : 1 - ]); - } - - public static function getUserTags($user_id = null) - { - $query = "SELECT DISTINCT tag - FROM message_tags - WHERE user_id = :user_id - ORDER BY tag ASC"; - return DBManager::get()->fetchFirst($query, [ - ':user_id' => $user_id ?: $GLOBALS['user']->id, - ], function ($tag) { - return ucfirst($tag); - }); - } - - public static function findNew($user_id, $receiver = true, $since = 0, $tag = null) - { - if ($tag) { - $messages_data = DBManager::get()->prepare(" - SELECT message.* - FROM message_user - INNER JOIN message ON (message_user.message_id = message.message_id) - INNER JOIN message_tags ON (message_tags.message_id = message_user.message_id - AND message_user.user_id = message_tags.user_id) - WHERE message_user.user_id = :me - AND snd_rec = :sender_receiver - AND message_tags.tag = :tag - AND message_user.mkdate > :since - ORDER BY message_user.mkdate ASC - "); - $messages_data->execute([ - 'me' => $user_id, - 'tag' => $tag, - 'sender_receiver' => $receiver ? "rec" : "snd", - 'since' => $since - ]); - } else { - $messages_data = DBManager::get()->prepare(" - SELECT message.* - FROM message_user - INNER JOIN message ON (message_user.message_id = message.message_id) - WHERE message_user.user_id = :me - AND snd_rec = :sender_receiver - AND message_user.mkdate > :since - ORDER BY message_user.mkdate ASC - "); - $messages_data->execute([ - 'me' => $user_id, - 'sender_receiver' => $receiver ? "rec" : "snd", - 'since' => $since - ]); - } - $messages_data->setFetchMode(PDO::FETCH_ASSOC); - $messages = []; - foreach ($messages_data as $data) { - $messages[] = Message::buildExisting($data); - } - return $messages; - } - - public function getSender() - { - return $this->author; - } - - public function getRecipients() - { - if ($this->relations['receivers'] === null) { - $sql = "SELECT user_id,vorname,nachname,username,title_front,title_rear,perms,motto - FROM message_user - INNER JOIN auth_user_md5 aum USING (user_id) - LEFT JOIN user_info ui USING (user_id) - WHERE message_id = ? AND snd_rec = 'rec' - ORDER BY Nachname, Vorname"; - $params = [$this->id]; - } else { - $sql = "SELECT user_id,vorname,nachname,username,title_front,title_rear,perms,motto - FROM auth_user_md5 AS aum - LEFT JOIN user_info ui USING (user_id) - WHERE aum.user_id IN (?) - ORDER BY Nachname, Vorname"; - $params = [$this->receivers->pluck('user_id')]; - } - $db = DBManager::get(); - return new SimpleCollection( - $db->fetchAll($sql, - $params, - function ($data) { - $user_id = $data['user_id']; - unset($data['user_id']); - $user = User::build($data); - $ret = $user->toArray('username vorname nachname'); - $ret['fullname'] = $user->getFullName(); - $ret['user_id'] = $user_id; - return $ret; - }) - ); - } - - public function getNumRecipients() - { - return MessageUser::countBySQL("message_id=? AND snd_rec='rec'", [$this->id]); - } - - public function markAsRead($user_id) - { - PersonalNotifications::markAsReadByHTML('message_'.$this->getId(), $user_id); - return $this->markAs($user_id, 1); - } - - public function markAsUnread($user_id) - { - return $this->markAs($user_id, 0); - } - - private function markAs($user_id, $state_of_flag) - { - $changed = 0; - $mu = []; - if ($user_id == $this->autor_id) { - $mu[] = $this->originator; - } - $receiver = MessageUser::findOneBySQL("message_id = ? AND user_id = ? AND snd_rec ='rec'", [$this->id, $user_id]); - if ($receiver) { - $mu[] = $receiver; - } - foreach ($mu as $message_user) { - $message_user->readed = $state_of_flag; - $changed += $message_user->store(); - } - return $changed; - } - - public function markAsAnswered($user_id) - { - $mu = MessageUser::findOneBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd')", [$this->id, $user_id]); - if ($mu) { - $mu->answered = 1; - return $mu->store(); - } - } - - public function isRead($user_id = null) - { - $user_id || $user_id = $GLOBALS['user']->id; - return (bool)MessageUser::countBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd') AND readed = 1", [$this->message_id, $user_id]); - } - - public function isAnswered($user_id = null) - { - $user_id || $user_id = $GLOBALS['user']->id; - return (bool)MessageUser::countBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd') AND answered = 1", [$this->message_id, $user_id]); - } - - public static function send($sender, $recipients, $subject, $message) - { - if ($sender === 'cli') { - $sender = '____%system%____'; - } - $messaging = new \messaging(); - $result = $messaging->insert_message($message, - $recipients, - $sender, - time(), - $message_id = md5(uniqid('message', true)), - false, // deleted - '', // force email - $subject); - return $result ? self::find($message_id) : null; - } - - public function permissionToRead($user_id = null) - { - $user_id || $user_id = $GLOBALS['user']->id; - return (bool) MessageUser::countBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd') AND deleted = 0", [$this->message_id, $user_id]); - } - - /** - * Returns all tags for the message for the given user. - * @param null $user_id : user-id of the user that tags should be related. null if it's the current user. - * @return array of string : tags - */ - public function getTags($user_id = null) - { - $query = "SELECT tag - FROM message_tags - WHERE message_id = :message_id AND user_id = :user_id - ORDER BY tag ASC"; - return DBManager::get()->fetchFirst($query, [ - 'message_id' => $this->id, - 'user_id' => $user_id ?: $GLOBALS['user']->id, - ], function ($tag) { - return ucfirst($tag); - }); - } - - public function addTag($tag, $user_id = null) - { - $user_id || $user_id = $GLOBALS['user']->id; - $statement = DBManager::get()->prepare(" - INSERT INTO message_tags - SET message_id = :message_id, - user_id = :user_id, - tag = :tag, - mkdate = UNIX_TIMESTAMP() - ON DUPLICATE KEY - UPDATE chdate = UNIX_TIMESTAMP() - "); - return $statement->execute([ - 'message_id' => $this->getId(), - 'user_id' => $user_id, - 'tag' => mb_strtolower($tag) - ]); - } - - public function removeTag($tag, $user_id = null) - { - $user_id || $user_id = $GLOBALS['user']->id; - $statement = DBManager::get()->prepare(" - DELETE FROM message_tags - WHERE message_id = :message_id - AND user_id = :user_id - AND tag = :tag - "); - return $statement->execute([ - 'message_id' => $this->getId(), - 'user_id' => $user_id, - 'tag' => mb_strtolower($tag) - ]); - } - - public function getNumAttachments() - { - return FileRef::countBySql("INNER JOIN folders ON(folders.id = folder_id) WHERE folders.range_id = ?", [$this->id]); - } - - /** - * Deletes the message if all references in message_user indicate 'deleted' - * @return bool - */ - public function removeIfOrphaned() - { - if (!MessageUser::countBySQL("message_id = ? AND snd_rec IN('rec','snd') AND deleted = 0", [$this->message_id])) { - return (bool)$this->delete(); - } - return false; - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL("autor_id = ?", [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('Nachrichten'), 'message', $field_data); - } - } - } - -} diff --git a/lib/models/Message.php b/lib/models/Message.php new file mode 100644 index 0000000..46493d6 --- /dev/null +++ b/lib/models/Message.php @@ -0,0 +1,343 @@ + User::class, + 'foreign_key' => 'autor_id' + ]; + $config['has_one']['originator'] = [ + 'class_name' => MessageUser::class, + 'assoc_func' => 'findSentByMessageId', + 'on_store' => 'store', + 'on_delete' => 'delete' + ]; + $config['has_many']['receivers'] = [ + 'class_name' => MessageUser::class, + 'assoc_func' => 'findReceivedByMessageId', + 'on_store' => 'store', + 'on_delete' => 'delete' + ]; + $config['has_one']['attachment_folder'] = [ + 'class_name' => Folder::class, + 'assoc_foreign_key' => 'range_id', + 'on_store' => 'store', + 'on_delete' => 'delete' + ]; + + parent::configure($config); + } + + public static function markAllAs($user_id = null, $state_of_flag = 1) + { + PersonalNotifications::markAsReadByHTML('message_%', $user_id ?: $GLOBALS['user']->id); + + $query = "UPDATE message_user + SET readed = :flag + WHERE user_id = :user_id + AND snd_rec = 'rec' AND deleted = 0 + AND readed = :other_flag"; + $statement = DBManager::get()->prepare($query); + return $statement->execute([ + 'user_id' => $user_id ?: $GLOBALS['user']->id, + 'flag' => $state_of_flag ? 1 : 0, + 'other_flag' => $state_of_flag ? 0 : 1 + ]); + } + + public static function getUserTags($user_id = null) + { + $query = "SELECT DISTINCT tag + FROM message_tags + WHERE user_id = :user_id + ORDER BY tag ASC"; + return DBManager::get()->fetchFirst($query, [ + ':user_id' => $user_id ?: $GLOBALS['user']->id, + ], function ($tag) { + return ucfirst($tag); + }); + } + + public static function findNew($user_id, $receiver = true, $since = 0, $tag = null) + { + if ($tag) { + $messages_data = DBManager::get()->prepare(" + SELECT message.* + FROM message_user + INNER JOIN message ON (message_user.message_id = message.message_id) + INNER JOIN message_tags ON (message_tags.message_id = message_user.message_id + AND message_user.user_id = message_tags.user_id) + WHERE message_user.user_id = :me + AND snd_rec = :sender_receiver + AND message_tags.tag = :tag + AND message_user.mkdate > :since + ORDER BY message_user.mkdate ASC + "); + $messages_data->execute([ + 'me' => $user_id, + 'tag' => $tag, + 'sender_receiver' => $receiver ? "rec" : "snd", + 'since' => $since + ]); + } else { + $messages_data = DBManager::get()->prepare(" + SELECT message.* + FROM message_user + INNER JOIN message ON (message_user.message_id = message.message_id) + WHERE message_user.user_id = :me + AND snd_rec = :sender_receiver + AND message_user.mkdate > :since + ORDER BY message_user.mkdate ASC + "); + $messages_data->execute([ + 'me' => $user_id, + 'sender_receiver' => $receiver ? "rec" : "snd", + 'since' => $since + ]); + } + $messages_data->setFetchMode(PDO::FETCH_ASSOC); + $messages = []; + foreach ($messages_data as $data) { + $messages[] = Message::buildExisting($data); + } + return $messages; + } + + public function getSender() + { + return $this->author; + } + + public function getRecipients() + { + if ($this->relations['receivers'] === null) { + $sql = "SELECT user_id,vorname,nachname,username,title_front,title_rear,perms,motto + FROM message_user + INNER JOIN auth_user_md5 aum USING (user_id) + LEFT JOIN user_info ui USING (user_id) + WHERE message_id = ? AND snd_rec = 'rec' + ORDER BY Nachname, Vorname"; + $params = [$this->id]; + } else { + $sql = "SELECT user_id,vorname,nachname,username,title_front,title_rear,perms,motto + FROM auth_user_md5 AS aum + LEFT JOIN user_info ui USING (user_id) + WHERE aum.user_id IN (?) + ORDER BY Nachname, Vorname"; + $params = [$this->receivers->pluck('user_id')]; + } + $db = DBManager::get(); + return new SimpleCollection( + $db->fetchAll($sql, + $params, + function ($data) { + $user_id = $data['user_id']; + unset($data['user_id']); + $user = User::build($data); + $ret = $user->toArray('username vorname nachname'); + $ret['fullname'] = $user->getFullName(); + $ret['user_id'] = $user_id; + return $ret; + }) + ); + } + + public function getNumRecipients() + { + return MessageUser::countBySQL("message_id=? AND snd_rec='rec'", [$this->id]); + } + + public function markAsRead($user_id) + { + PersonalNotifications::markAsReadByHTML('message_'.$this->getId(), $user_id); + return $this->markAs($user_id, 1); + } + + public function markAsUnread($user_id) + { + return $this->markAs($user_id, 0); + } + + private function markAs($user_id, $state_of_flag) + { + $changed = 0; + $mu = []; + if ($user_id == $this->autor_id) { + $mu[] = $this->originator; + } + $receiver = MessageUser::findOneBySQL("message_id = ? AND user_id = ? AND snd_rec ='rec'", [$this->id, $user_id]); + if ($receiver) { + $mu[] = $receiver; + } + foreach ($mu as $message_user) { + $message_user->readed = $state_of_flag; + $changed += $message_user->store(); + } + return $changed; + } + + public function markAsAnswered($user_id) + { + $mu = MessageUser::findOneBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd')", [$this->id, $user_id]); + if ($mu) { + $mu->answered = 1; + return $mu->store(); + } + } + + public function isRead($user_id = null) + { + $user_id || $user_id = $GLOBALS['user']->id; + return (bool)MessageUser::countBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd') AND readed = 1", [$this->message_id, $user_id]); + } + + public function isAnswered($user_id = null) + { + $user_id || $user_id = $GLOBALS['user']->id; + return (bool)MessageUser::countBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd') AND answered = 1", [$this->message_id, $user_id]); + } + + public static function send($sender, $recipients, $subject, $message) + { + if ($sender === 'cli') { + $sender = '____%system%____'; + } + $messaging = new \messaging(); + $result = $messaging->insert_message($message, + $recipients, + $sender, + time(), + $message_id = md5(uniqid('message', true)), + false, // deleted + '', // force email + $subject); + return $result ? self::find($message_id) : null; + } + + public function permissionToRead($user_id = null) + { + $user_id || $user_id = $GLOBALS['user']->id; + return (bool) MessageUser::countBySQL("message_id = ? AND user_id = ? AND snd_rec IN('rec','snd') AND deleted = 0", [$this->message_id, $user_id]); + } + + /** + * Returns all tags for the message for the given user. + * @param null $user_id : user-id of the user that tags should be related. null if it's the current user. + * @return array of string : tags + */ + public function getTags($user_id = null) + { + $query = "SELECT tag + FROM message_tags + WHERE message_id = :message_id AND user_id = :user_id + ORDER BY tag ASC"; + return DBManager::get()->fetchFirst($query, [ + 'message_id' => $this->id, + 'user_id' => $user_id ?: $GLOBALS['user']->id, + ], function ($tag) { + return ucfirst($tag); + }); + } + + public function addTag($tag, $user_id = null) + { + $user_id || $user_id = $GLOBALS['user']->id; + $statement = DBManager::get()->prepare(" + INSERT INTO message_tags + SET message_id = :message_id, + user_id = :user_id, + tag = :tag, + mkdate = UNIX_TIMESTAMP() + ON DUPLICATE KEY + UPDATE chdate = UNIX_TIMESTAMP() + "); + return $statement->execute([ + 'message_id' => $this->getId(), + 'user_id' => $user_id, + 'tag' => mb_strtolower($tag) + ]); + } + + public function removeTag($tag, $user_id = null) + { + $user_id || $user_id = $GLOBALS['user']->id; + $statement = DBManager::get()->prepare(" + DELETE FROM message_tags + WHERE message_id = :message_id + AND user_id = :user_id + AND tag = :tag + "); + return $statement->execute([ + 'message_id' => $this->getId(), + 'user_id' => $user_id, + 'tag' => mb_strtolower($tag) + ]); + } + + public function getNumAttachments() + { + return FileRef::countBySql("INNER JOIN folders ON(folders.id = folder_id) WHERE folders.range_id = ?", [$this->id]); + } + + /** + * Deletes the message if all references in message_user indicate 'deleted' + * @return bool + */ + public function removeIfOrphaned() + { + if (!MessageUser::countBySQL("message_id = ? AND snd_rec IN('rec','snd') AND deleted = 0", [$this->message_id])) { + return (bool)$this->delete(); + } + return false; + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL("autor_id = ?", [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('Nachrichten'), 'message', $field_data); + } + } + } + +} diff --git a/lib/models/MessageUser.class.php b/lib/models/MessageUser.class.php deleted file mode 100644 index 0ae6dd5..0000000 --- a/lib/models/MessageUser.class.php +++ /dev/null @@ -1,98 +0,0 @@ - User::class, - 'foreign_key' => 'user_id', - ]; - $config['belongs_to']['message'] = [ - 'class_name' => Message::class, - 'foreign_key' => 'message_id', - ]; - - $config['registered_callbacks']['after_store'][] = 'cleanUpTags'; - $config['registered_callbacks']['after_delete'][] = 'cleanUpTags'; - - parent::configure($config); - } - - public static function hasUnreadByUserId($user_id) - { - return self::countBySql("snd_rec = 'rec' AND readed = 0 AND user_id = ? AND deleted = 0", [$user_id]) > 0; - } - - public static function findSentByMessageId($message_id) - { - return self::findOneBySQL("message_id=? AND snd_rec='snd'", [$message_id]); - } - - public static function findReceivedByMessageId($message_id) - { - return self::findBySQL("message_id=? AND snd_rec='rec'", [$message_id]); - } - - public function cleanUpTags($callback) - { - $query = "DELETE FROM message_tags - WHERE message_id = :message_id AND user_id = :user_id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':message_id', $this['message_id']); - $statement->bindValue(':user_id', $this['user_id']); - if ($callback == 'after_delete') { - $statement->execute(); - } - if ($callback == 'after_store' && $this->isDirty("deleted") && $this['deleted']) { - $statement->execute(); - $this->message->removeIfOrphaned(); - } - return true; - } - - /** - * Export available data of a given user into a storage object - * (an instance of the StoredUserData class) for that user. - * - * @param StoredUserData $storage object to store data into - */ - public static function exportUserData(StoredUserData $storage) - { - $sorm = self::findBySQL("user_id = ?", [$storage->user_id]); - if ($sorm) { - $field_data = []; - foreach ($sorm as $row) { - $field_data[] = $row->toRawArray(); - } - if ($field_data) { - $storage->addTabularData(_('MessageUser'), 'message_user', $field_data); - } - } - } -} diff --git a/lib/models/MessageUser.php b/lib/models/MessageUser.php new file mode 100644 index 0000000..0ae6dd5 --- /dev/null +++ b/lib/models/MessageUser.php @@ -0,0 +1,98 @@ + User::class, + 'foreign_key' => 'user_id', + ]; + $config['belongs_to']['message'] = [ + 'class_name' => Message::class, + 'foreign_key' => 'message_id', + ]; + + $config['registered_callbacks']['after_store'][] = 'cleanUpTags'; + $config['registered_callbacks']['after_delete'][] = 'cleanUpTags'; + + parent::configure($config); + } + + public static function hasUnreadByUserId($user_id) + { + return self::countBySql("snd_rec = 'rec' AND readed = 0 AND user_id = ? AND deleted = 0", [$user_id]) > 0; + } + + public static function findSentByMessageId($message_id) + { + return self::findOneBySQL("message_id=? AND snd_rec='snd'", [$message_id]); + } + + public static function findReceivedByMessageId($message_id) + { + return self::findBySQL("message_id=? AND snd_rec='rec'", [$message_id]); + } + + public function cleanUpTags($callback) + { + $query = "DELETE FROM message_tags + WHERE message_id = :message_id AND user_id = :user_id"; + $statement = DBManager::get()->prepare($query); + $statement->bindValue(':message_id', $this['message_id']); + $statement->bindValue(':user_id', $this['user_id']); + if ($callback == 'after_delete') { + $statement->execute(); + } + if ($callback == 'after_store' && $this->isDirty("deleted") && $this['deleted']) { + $statement->execute(); + $this->message->removeIfOrphaned(); + } + return true; + } + + /** + * Export available data of a given user into a storage object + * (an instance of the StoredUserData class) for that user. + * + * @param StoredUserData $storage object to store data into + */ + public static function exportUserData(StoredUserData $storage) + { + $sorm = self::findBySQL("user_id = ?", [$storage->user_id]); + if ($sorm) { + $field_data = []; + foreach ($sorm as $row) { + $field_data[] = $row->toRawArray(); + } + if ($field_data) { + $storage->addTabularData(_('MessageUser'), 'message_user', $field_data); + } + } + } +} diff --git a/lib/models/MvvOverlappingConflict.class.php b/lib/models/MvvOverlappingConflict.class.php deleted file mode 100644 index 6ac8e29..0000000 --- a/lib/models/MvvOverlappingConflict.class.php +++ /dev/null @@ -1,115 +0,0 @@ - - * @copyright 2018 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.4 - * - * @property int $id alias column for conflict_id - * @property int $conflict_id database column - * @property int $selection_id database column - * @property string $base_abschnitt_id database column - * @property string $base_modulteil_id database column - * @property string $base_course_id database column - * @property string $base_metadate_id database column - * @property string $comp_abschnitt_id database column - * @property string $comp_modulteil_id database column - * @property string $comp_course_id database column - * @property string $comp_metadate_id database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property MvvOverlappingSelection $selection belongs_to MvvOverlappingSelection - * @property StgteilAbschnitt $base_abschnitt belongs_to StgteilAbschnitt - * @property Modulteil $base_modulteil belongs_to Modulteil - * @property SeminarCycleDate $base_cycle belongs_to SeminarCycleDate - * @property Course $base_course belongs_to Course - * @property StgteilAbschnitt $comp_abschnitt belongs_to StgteilAbschnitt - * @property Modulteil $comp_modulteil belongs_to Modulteil - * @property SeminarCycleDate $comp_cycle belongs_to SeminarCycleDate - * @property Course $comp_course belongs_to Course - */ - -class MvvOverlappingConflict extends SimpleORMap -{ - /** - * Configures the model. - * - * @param array $config Configuration - */ - protected static function configure($config = array()) { - - $config['db_table'] = 'mvv_ovl_conflicts'; - $config['belongs_to']['selection'] = [ - 'class_name' => MvvOverlappingSelection::class, - 'foreign_key' => 'selection_id', - 'assoc_foreign_key' => 'id' - ]; - $config['belongs_to']['base_abschnitt'] = [ - 'class_name' => StgteilAbschnitt::class, - 'foreign_key' => 'base_abschnitt_id' - ]; - $config['belongs_to']['base_modulteil'] = [ - 'class_name' => Modulteil::class, - 'foreign_key' => 'base_modulteil_id' - ]; - $config['belongs_to']['base_cycle'] = [ - 'class_name' => SeminarCycleDate::class, - 'foreign_key' => 'base_metadate_id' - ]; - $config['belongs_to']['base_course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'base_course_id' - ]; - $config['belongs_to']['comp_abschnitt'] = [ - 'class_name' => StgteilAbschnitt::class, - 'foreign_key' => 'comp_abschnitt_id' - ]; - $config['belongs_to']['comp_modulteil'] = [ - 'class_name' => Modulteil::class, - 'foreign_key' => 'comp_modulteil_id' - ]; - $config['belongs_to']['comp_cycle'] = [ - 'class_name' => SeminarCycleDate::class, - 'foreign_key' => 'comp_metadate_id' - ]; - $config['belongs_to']['comp_course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'comp_course_id' - ]; - - parent::configure($config); - } - - /** - * Returns true if this conflict belongs to a excluded (hidden) course. - * - * @return boolean True if this conflict is excluded. - */ - public function isExcluded() - { - return MvvOverlappingExclude::find([$this->selection->selection_id, - $this->comp_course_id]) ? true : false; - } - - /** - * Deletes all conflicts by given selection id. - * - * @param string $selection_id - * @return number - */ - public static function deleteBySelection($selection_id) - { - return self::deleteBySQL('INNER JOIN `mvv_ovl_selections` - ON `mvv_ovl_selections`.`id` = `mvv_ovl_conflicts`.`selection_id` - WHERE `mvv_ovl_selections`.`selection_id` = ?', - [$selection_id]); - } -} diff --git a/lib/models/MvvOverlappingConflict.php b/lib/models/MvvOverlappingConflict.php new file mode 100644 index 0000000..6ac8e29 --- /dev/null +++ b/lib/models/MvvOverlappingConflict.php @@ -0,0 +1,115 @@ + + * @copyright 2018 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.4 + * + * @property int $id alias column for conflict_id + * @property int $conflict_id database column + * @property int $selection_id database column + * @property string $base_abschnitt_id database column + * @property string $base_modulteil_id database column + * @property string $base_course_id database column + * @property string $base_metadate_id database column + * @property string $comp_abschnitt_id database column + * @property string $comp_modulteil_id database column + * @property string $comp_course_id database column + * @property string $comp_metadate_id database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property MvvOverlappingSelection $selection belongs_to MvvOverlappingSelection + * @property StgteilAbschnitt $base_abschnitt belongs_to StgteilAbschnitt + * @property Modulteil $base_modulteil belongs_to Modulteil + * @property SeminarCycleDate $base_cycle belongs_to SeminarCycleDate + * @property Course $base_course belongs_to Course + * @property StgteilAbschnitt $comp_abschnitt belongs_to StgteilAbschnitt + * @property Modulteil $comp_modulteil belongs_to Modulteil + * @property SeminarCycleDate $comp_cycle belongs_to SeminarCycleDate + * @property Course $comp_course belongs_to Course + */ + +class MvvOverlappingConflict extends SimpleORMap +{ + /** + * Configures the model. + * + * @param array $config Configuration + */ + protected static function configure($config = array()) { + + $config['db_table'] = 'mvv_ovl_conflicts'; + $config['belongs_to']['selection'] = [ + 'class_name' => MvvOverlappingSelection::class, + 'foreign_key' => 'selection_id', + 'assoc_foreign_key' => 'id' + ]; + $config['belongs_to']['base_abschnitt'] = [ + 'class_name' => StgteilAbschnitt::class, + 'foreign_key' => 'base_abschnitt_id' + ]; + $config['belongs_to']['base_modulteil'] = [ + 'class_name' => Modulteil::class, + 'foreign_key' => 'base_modulteil_id' + ]; + $config['belongs_to']['base_cycle'] = [ + 'class_name' => SeminarCycleDate::class, + 'foreign_key' => 'base_metadate_id' + ]; + $config['belongs_to']['base_course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'base_course_id' + ]; + $config['belongs_to']['comp_abschnitt'] = [ + 'class_name' => StgteilAbschnitt::class, + 'foreign_key' => 'comp_abschnitt_id' + ]; + $config['belongs_to']['comp_modulteil'] = [ + 'class_name' => Modulteil::class, + 'foreign_key' => 'comp_modulteil_id' + ]; + $config['belongs_to']['comp_cycle'] = [ + 'class_name' => SeminarCycleDate::class, + 'foreign_key' => 'comp_metadate_id' + ]; + $config['belongs_to']['comp_course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'comp_course_id' + ]; + + parent::configure($config); + } + + /** + * Returns true if this conflict belongs to a excluded (hidden) course. + * + * @return boolean True if this conflict is excluded. + */ + public function isExcluded() + { + return MvvOverlappingExclude::find([$this->selection->selection_id, + $this->comp_course_id]) ? true : false; + } + + /** + * Deletes all conflicts by given selection id. + * + * @param string $selection_id + * @return number + */ + public static function deleteBySelection($selection_id) + { + return self::deleteBySQL('INNER JOIN `mvv_ovl_selections` + ON `mvv_ovl_selections`.`id` = `mvv_ovl_conflicts`.`selection_id` + WHERE `mvv_ovl_selections`.`selection_id` = ?', + [$selection_id]); + } +} diff --git a/lib/models/MvvOverlappingSelection.class.php b/lib/models/MvvOverlappingSelection.class.php deleted file mode 100644 index 9d159e2..0000000 --- a/lib/models/MvvOverlappingSelection.class.php +++ /dev/null @@ -1,339 +0,0 @@ - - * @copyright 2018 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 4.4 - * - * @property int $id database column - * @property string $selection_id database column - * @property string $semester_id database column - * @property string $base_version_id database column - * @property string $comp_version_id database column - * @property string $fachsems database column - * @property string $semtypes database column - * @property string $user_id database column - * @property int $show_excluded database column - * @property int $mkdate database column - * @property SimpleORMapCollection|MvvOverlappingConflict[] $conflicts has_many MvvOverlappingConflict - * @property SimpleORMapCollection|MvvOverlappingExclude[] $excludes has_many MvvOverlappingExclude - * @property Semester $semester belongs_to Semester - * @property StgteilVersion $base_version belongs_to StgteilVersion - * @property StgteilVersion $comp_version belongs_to StgteilVersion - * @property User $user belongs_to User - */ - -class MvvOverlappingSelection extends SimpleORMap -{ - /** - * Configures the model. - * - * @param array $config Configuration - */ - protected static function configure($config = array()) - { - $config['db_table'] = 'mvv_ovl_selections'; - $config['belongs_to']['semester'] = [ - 'class_name' => Semester::class, - 'foreign_key' => 'semester_id' - ]; - $config['belongs_to']['base_version'] = [ - 'class_name' => StgteilVersion::class, - 'foreign_key' => 'base_version_id' - ]; - $config['belongs_to']['comp_version'] = [ - 'class_name' => StgteilVersion::class, - 'foreign_key' => 'comp_version_id' - ]; - $config['has_many']['conflicts'] = [ - 'class_name' => MvvOverlappingConflict::class, - 'foreign_key' => 'id', - 'assoc_foreign_key' => 'selection_id', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'user_id' - ]; - $config['has_many']['excludes'] = [ - 'class_name' => MvvOverlappingExclude::class, - 'foreign_key' => 'selection_id', - 'assoc_foreign_key' => 'selection_id', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - parent::configure($config); - } - - /** - * Creates a selection id and stores the selection. - * - * @throws UnexpectedValueException if there are forbidden NULL values - * @return number|boolean - */ - public function store() - { - if ($this->isNew() && $this->selection_id == '') { - $this->selection_id = self::createSelectionId( - $this->base_version, - $this->comp_version, - $this->fachsems, - $this->semtypes - ); - } - return parent::store(); - } - - /** - * Sets the given Fachsemester. Expects an array or a comma - * separated list of Fachsemester. - * - * @param array|string $semtypes - */ - public function setFachsemester($fachsems) - { - if (is_array($fachsems)) { - sort($fachsems, SORT_NUMERIC); - $fachsems = implode(',', $fachsems); - } - $this->fachsems = $fachsems; - } - - /** - * Sets the given course types (semtypes). Expects an array or a comma - * separated list of course types. - * - * @param array|string $semtypes - */ - public function setCoursetypes($semtypes) - { - if (is_array($semtypes)) { - sort($semtypes, SORT_NUMERIC); - $semtypes = implode(',', $semtypes); - } - $this->semtypes = $semtypes; - } - - /** - * Store this selection with its all conflicts. - * - * @throws UnexpectedValueException if there are forbidden NULL values - * @return number|boolean - */ - public function storeConflicts() - { - - $query = " - SELECT DISTINCT `cbase`.`metadate_id` AS `cbase_metadate_id`, - `cbase`.`seminar_id` AS `cbase_seminar_id`, - `sembase`.`abschnitt_id` AS `sembase_abschnitt_id`, - `sembase`.`modulteil_id` AS `sembase_modulteil_id`, - `ccomp`.`metadate_id` AS `ccomp_metadate_id`, - `ccomp`.`seminar_id` AS `ccomp_seminar_id`, - `semcomp`.`abschnitt_id` AS `semcomp_abschnitt_id`, - `semcomp`.`modulteil_id` AS `semcomp_modulteil_id` - FROM `seminar_cycle_dates` AS `cbase` - INNER JOIN ( - SELECT `mvv_lvgruppe_seminar`.`seminar_id`, - `mvv_stgteilabschnitt_modul`.`abschnitt_id`, - `mvv_modulteil`.`modulteil_id` - FROM `mvv_stgteilabschnitt` - INNER JOIN `mvv_stgteilabschnitt_modul` USING (`abschnitt_id`) - INNER JOIN `mvv_modul` USING (`modul_id`) - INNER JOIN `mvv_modulteil` USING (`modul_id`) - INNER JOIN `mvv_lvgruppe_modulteil` USING (`modulteil_id`) - INNER JOIN `mvv_lvgruppe_seminar` USING (`lvgruppe_id`) - INNER JOIN `seminare` USING (`seminar_id`) - INNER JOIN `mvv_modulteil_stgteilabschnitt` - ON (`mvv_stgteilabschnitt_modul`.`abschnitt_id` = - `mvv_modulteil_stgteilabschnitt`.`abschnitt_id` - AND `mvv_modulteil`.`modulteil_id` = - `mvv_modulteil_stgteilabschnitt`.`modulteil_id`) - LEFT JOIN `semester_data` AS `start_sem` - ON (`mvv_modul`.`start` = `start_sem`.`semester_id`) - LEFT JOIN `semester_data` AS `end_sem` - ON (`mvv_modul`.`end` = `end_sem`.`semester_id`) - LEFT JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) - WHERE `mvv_stgteilabschnitt`.`version_id` = :base_version - AND `mvv_modulteil_stgteilabschnitt`.`fachsemester` IN (:fachsem) - AND ((`start_sem`.`beginn` < :sem_end OR ISNULL(`start_sem`.`beginn`)) - AND (`end_sem`.`ende` > :sem_start OR ISNULL(`end_sem`.`ende`))) - AND `seminare`.`status` IN (:typ) - AND `seminare`.`start_time` <= :sem_end - AND (`semester_courses`.`semester_id` IS NULL OR `semester_courses`.`semester_id` = :semester_id) - ) AS `sembase` ON (`sembase`.`seminar_id` = `cbase`.`seminar_id`) - INNER JOIN `seminar_cycle_dates` AS `ccomp` - ON (`cbase`.`seminar_id` != `ccomp`.`seminar_id` - AND `cbase`.`weekday` = `ccomp`.`weekday` - AND `cbase`.`start_time` < `ccomp`.`end_time` - AND `cbase`.`end_time` > `ccomp`.`start_time` - AND `cbase`.`metadate_id` = ( - SELECT DISTINCT `metadate_id` - FROM `termine` - WHERE `termine`.`metadate_id` = `cbase`.`metadate_id` - LIMIT 1) - AND `ccomp`.`metadate_id` = ( - SELECT DISTINCT `metadate_id` - FROM `termine` - WHERE `termine`.`metadate_id` = `ccomp`.`metadate_id` - LIMIT 1) - ) - INNER JOIN ( - SELECT `mvv_lvgruppe_seminar`.`seminar_id`, - `mvv_stgteilabschnitt_modul`.`abschnitt_id`, - `mvv_modulteil`.`modulteil_id` - FROM `mvv_stgteilabschnitt` - INNER JOIN `mvv_stgteilabschnitt_modul` USING (`abschnitt_id`) - INNER JOIN `mvv_modul` USING (`modul_id`) - INNER JOIN `mvv_modulteil` USING (`modul_id`) - INNER JOIN `mvv_lvgruppe_modulteil` USING (`modulteil_id`) - INNER JOIN `mvv_lvgruppe_seminar` USING (`lvgruppe_id`) - INNER JOIN `seminare` USING (`seminar_id`) - INNER JOIN `mvv_modulteil_stgteilabschnitt` - ON (`mvv_stgteilabschnitt_modul`.`abschnitt_id` = - `mvv_modulteil_stgteilabschnitt`.`abschnitt_id` - AND `mvv_modulteil`.`modulteil_id` = - `mvv_modulteil_stgteilabschnitt`.`modulteil_id`) - LEFT JOIN `semester_data` AS `start_sem` - ON (`mvv_modul`.`start` = `start_sem`.`semester_id`) - LEFT JOIN `semester_data` AS `end_sem` - ON (`mvv_modul`.`end` = `end_sem`.`semester_id`) - LEFT JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) - WHERE `mvv_stgteilabschnitt`.`version_id` = :comp_version - AND `mvv_modulteil_stgteilabschnitt`.`fachsemester` IN (:fachsem) - AND ((`start_sem`.`beginn` < :sem_end OR ISNULL(`start_sem`.`beginn`)) - AND (`end_sem`.`ende` > :sem_start OR ISNULL(`end_sem`.`ende`))) - AND `seminare`.`status` IN (:typ) - AND `seminare`.`start_time` <= :sem_end - AND (`semester_courses`.`semester_id` IS NULL OR `semester_courses`.`semester_id` = :semester_id) - ) AS `semcomp` ON (`semcomp`.`seminar_id` = `ccomp`.`seminar_id`) - INNER JOIN `mvv_modulteil_stgteilabschnitt` AS `mms1` - ON (`mms1`.`abschnitt_id` = `semcomp`.`abschnitt_id` AND `mms1`.`modulteil_id` = `semcomp`.`modulteil_id`) - WHERE `mms1`.`fachsemester` IN ( - SELECT `fachsemester` - FROM `mvv_modulteil_stgteilabschnitt` AS `mms2` - WHERE `mms2`.`abschnitt_id` = `sembase`.`abschnitt_id` - AND `mms2`.`modulteil_id` = `sembase`.`modulteil_id`) - ORDER BY `cbase_seminar_id`"; - - // if no filter is set use all types and fachsems - $fachsems = $this->fachsems ? $this->fachsems : implode(',', range(1, 6)); - $semtypes = $this->semtypes ? $this->semtypes : implode(',', array_keys(SemType::getTypes())); - - $db = DBManager::get(); - $conflicts = $db->fetchAll($query, [ - ':base_version' => $this->base_version_id, - ':comp_version' => $this->comp_version_id, - ':fachsem' => explode(',', $fachsems), - ':typ' => explode(',', $semtypes), - ':sem_start' => $this->semester->beginn, - ':sem_end' => $this->semester->ende, - ':semester_id' => $this->semester->id - ]); - - $conlicts = []; - foreach ($conflicts as $conflict) { - $ovl_conflict = new MvvOverlappingConflict(); - $ovl_conflict->selection_id = $this->id; - $ovl_conflict->base_abschnitt_id = $conflict['sembase_abschnitt_id']; - $ovl_conflict->base_modulteil_id = $conflict['sembase_modulteil_id']; - $ovl_conflict->base_course_id = $conflict['cbase_seminar_id']; - $ovl_conflict->base_metadate_id = $conflict['cbase_metadate_id']; - $ovl_conflict->comp_abschnitt_id = $conflict['semcomp_abschnitt_id']; - $ovl_conflict->comp_modulteil_id = $conflict['semcomp_modulteil_id']; - $ovl_conflict->comp_course_id = $conflict['ccomp_seminar_id']; - $ovl_conflict->comp_metadate_id = $conflict['ccomp_metadate_id']; - $this->conflicts[] = $ovl_conflict; - } - return $this->store(); - } - - /** - * Returns all conflicts of all selections with the given selection id. - * - * @param string $selection_id The selection id. - * @param boolean $only_visible Returns only visible conflicts. - * @return SimpleORMapCollection All conflicts of appropriate selections. - */ - public static function getConflictsBySelection($selection_id, $only_visible = false) - { - $excluded_courses = []; - $visible_sql = ''; - if ($only_visible) { - $excluded_courses = SimpleORMapCollection::createFromArray( - MvvOverlappingExclude::findBySelection_id($selection_id))->pluck('course_id'); - if ($excluded_courses) { - $visible_sql = 'AND `mvv_ovl_conflicts`.`comp_course_id` NOT IN (:course_ids)'; - } - } - return SimpleORMapCollection::createFromArray( - MvvOverlappingConflict::findBySql('LEFT JOIN `mvv_ovl_selections` - ON (`mvv_ovl_conflicts`.`selection_id` = `mvv_ovl_selections`.`id`) - WHERE `mvv_ovl_selections`.`selection_id` = :selection_id ' . $visible_sql, [ - ':selection_id' => $selection_id, - ':course_ids' => $excluded_courses - ]) - ); - } - - /** - * Returns a md5 hash over all given parameters. - * - * @param string $base_version The id of the base version. - * @param string $comp_versions The id of the compared version. - * @param array|string $fachsems An array or a string with comma separated fachsem numbers. - * @param array|string $semtypes An array or a string with comma separated course types. - * @param string|null $user_id User id that created the selection (defaults to current user) - * @return string The md5 id. - */ - public static function createSelectionId($base_version, $comp_versions, $fachsems, $semtypes, string $user_id = null) - { - if (is_array($fachsems)) { - sort($fachsems, SORT_NUMERIC); - $fachsems = implode(',', $fachsems); - } - if (is_array($semtypes)) { - sort($semtypes, SORT_NUMERIC); - $semtypes = implode(',', $semtypes); - } - if (is_array($comp_versions)) { - $comp_version_ids = []; - foreach ($comp_versions as $comp_version) { - $comp_version_ids[] = $comp_version->id; - } - sort($comp_version_ids); - $comp_versions = implode(',', $comp_version_ids); - } else { - $comp_versions = $comp_versions->id; - } - return md5(implode('_', [ - $base_version->id, - $comp_versions, - trim($fachsems) ? $fachsems : 'x', - trim($semtypes) ? $semtypes : 'x', - $user_id ?? $GLOBALS['user']->id, - ])); - } - - /** - * Returns all excluded (hidden) conflicts of this selection. - * - * @return SimpleORMapCollection The excluded (hidden) conflicts. - */ - public function getExcludedConflicts() - { - return $this->conflicts->findBy( - 'comp_course_id', - $this->excludes->pluck('course_id') - ); - } -} diff --git a/lib/models/MvvOverlappingSelection.php b/lib/models/MvvOverlappingSelection.php new file mode 100644 index 0000000..9d159e2 --- /dev/null +++ b/lib/models/MvvOverlappingSelection.php @@ -0,0 +1,339 @@ + + * @copyright 2018 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 4.4 + * + * @property int $id database column + * @property string $selection_id database column + * @property string $semester_id database column + * @property string $base_version_id database column + * @property string $comp_version_id database column + * @property string $fachsems database column + * @property string $semtypes database column + * @property string $user_id database column + * @property int $show_excluded database column + * @property int $mkdate database column + * @property SimpleORMapCollection|MvvOverlappingConflict[] $conflicts has_many MvvOverlappingConflict + * @property SimpleORMapCollection|MvvOverlappingExclude[] $excludes has_many MvvOverlappingExclude + * @property Semester $semester belongs_to Semester + * @property StgteilVersion $base_version belongs_to StgteilVersion + * @property StgteilVersion $comp_version belongs_to StgteilVersion + * @property User $user belongs_to User + */ + +class MvvOverlappingSelection extends SimpleORMap +{ + /** + * Configures the model. + * + * @param array $config Configuration + */ + protected static function configure($config = array()) + { + $config['db_table'] = 'mvv_ovl_selections'; + $config['belongs_to']['semester'] = [ + 'class_name' => Semester::class, + 'foreign_key' => 'semester_id' + ]; + $config['belongs_to']['base_version'] = [ + 'class_name' => StgteilVersion::class, + 'foreign_key' => 'base_version_id' + ]; + $config['belongs_to']['comp_version'] = [ + 'class_name' => StgteilVersion::class, + 'foreign_key' => 'comp_version_id' + ]; + $config['has_many']['conflicts'] = [ + 'class_name' => MvvOverlappingConflict::class, + 'foreign_key' => 'id', + 'assoc_foreign_key' => 'selection_id', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id' + ]; + $config['has_many']['excludes'] = [ + 'class_name' => MvvOverlappingExclude::class, + 'foreign_key' => 'selection_id', + 'assoc_foreign_key' => 'selection_id', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + parent::configure($config); + } + + /** + * Creates a selection id and stores the selection. + * + * @throws UnexpectedValueException if there are forbidden NULL values + * @return number|boolean + */ + public function store() + { + if ($this->isNew() && $this->selection_id == '') { + $this->selection_id = self::createSelectionId( + $this->base_version, + $this->comp_version, + $this->fachsems, + $this->semtypes + ); + } + return parent::store(); + } + + /** + * Sets the given Fachsemester. Expects an array or a comma + * separated list of Fachsemester. + * + * @param array|string $semtypes + */ + public function setFachsemester($fachsems) + { + if (is_array($fachsems)) { + sort($fachsems, SORT_NUMERIC); + $fachsems = implode(',', $fachsems); + } + $this->fachsems = $fachsems; + } + + /** + * Sets the given course types (semtypes). Expects an array or a comma + * separated list of course types. + * + * @param array|string $semtypes + */ + public function setCoursetypes($semtypes) + { + if (is_array($semtypes)) { + sort($semtypes, SORT_NUMERIC); + $semtypes = implode(',', $semtypes); + } + $this->semtypes = $semtypes; + } + + /** + * Store this selection with its all conflicts. + * + * @throws UnexpectedValueException if there are forbidden NULL values + * @return number|boolean + */ + public function storeConflicts() + { + + $query = " + SELECT DISTINCT `cbase`.`metadate_id` AS `cbase_metadate_id`, + `cbase`.`seminar_id` AS `cbase_seminar_id`, + `sembase`.`abschnitt_id` AS `sembase_abschnitt_id`, + `sembase`.`modulteil_id` AS `sembase_modulteil_id`, + `ccomp`.`metadate_id` AS `ccomp_metadate_id`, + `ccomp`.`seminar_id` AS `ccomp_seminar_id`, + `semcomp`.`abschnitt_id` AS `semcomp_abschnitt_id`, + `semcomp`.`modulteil_id` AS `semcomp_modulteil_id` + FROM `seminar_cycle_dates` AS `cbase` + INNER JOIN ( + SELECT `mvv_lvgruppe_seminar`.`seminar_id`, + `mvv_stgteilabschnitt_modul`.`abschnitt_id`, + `mvv_modulteil`.`modulteil_id` + FROM `mvv_stgteilabschnitt` + INNER JOIN `mvv_stgteilabschnitt_modul` USING (`abschnitt_id`) + INNER JOIN `mvv_modul` USING (`modul_id`) + INNER JOIN `mvv_modulteil` USING (`modul_id`) + INNER JOIN `mvv_lvgruppe_modulteil` USING (`modulteil_id`) + INNER JOIN `mvv_lvgruppe_seminar` USING (`lvgruppe_id`) + INNER JOIN `seminare` USING (`seminar_id`) + INNER JOIN `mvv_modulteil_stgteilabschnitt` + ON (`mvv_stgteilabschnitt_modul`.`abschnitt_id` = + `mvv_modulteil_stgteilabschnitt`.`abschnitt_id` + AND `mvv_modulteil`.`modulteil_id` = + `mvv_modulteil_stgteilabschnitt`.`modulteil_id`) + LEFT JOIN `semester_data` AS `start_sem` + ON (`mvv_modul`.`start` = `start_sem`.`semester_id`) + LEFT JOIN `semester_data` AS `end_sem` + ON (`mvv_modul`.`end` = `end_sem`.`semester_id`) + LEFT JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) + WHERE `mvv_stgteilabschnitt`.`version_id` = :base_version + AND `mvv_modulteil_stgteilabschnitt`.`fachsemester` IN (:fachsem) + AND ((`start_sem`.`beginn` < :sem_end OR ISNULL(`start_sem`.`beginn`)) + AND (`end_sem`.`ende` > :sem_start OR ISNULL(`end_sem`.`ende`))) + AND `seminare`.`status` IN (:typ) + AND `seminare`.`start_time` <= :sem_end + AND (`semester_courses`.`semester_id` IS NULL OR `semester_courses`.`semester_id` = :semester_id) + ) AS `sembase` ON (`sembase`.`seminar_id` = `cbase`.`seminar_id`) + INNER JOIN `seminar_cycle_dates` AS `ccomp` + ON (`cbase`.`seminar_id` != `ccomp`.`seminar_id` + AND `cbase`.`weekday` = `ccomp`.`weekday` + AND `cbase`.`start_time` < `ccomp`.`end_time` + AND `cbase`.`end_time` > `ccomp`.`start_time` + AND `cbase`.`metadate_id` = ( + SELECT DISTINCT `metadate_id` + FROM `termine` + WHERE `termine`.`metadate_id` = `cbase`.`metadate_id` + LIMIT 1) + AND `ccomp`.`metadate_id` = ( + SELECT DISTINCT `metadate_id` + FROM `termine` + WHERE `termine`.`metadate_id` = `ccomp`.`metadate_id` + LIMIT 1) + ) + INNER JOIN ( + SELECT `mvv_lvgruppe_seminar`.`seminar_id`, + `mvv_stgteilabschnitt_modul`.`abschnitt_id`, + `mvv_modulteil`.`modulteil_id` + FROM `mvv_stgteilabschnitt` + INNER JOIN `mvv_stgteilabschnitt_modul` USING (`abschnitt_id`) + INNER JOIN `mvv_modul` USING (`modul_id`) + INNER JOIN `mvv_modulteil` USING (`modul_id`) + INNER JOIN `mvv_lvgruppe_modulteil` USING (`modulteil_id`) + INNER JOIN `mvv_lvgruppe_seminar` USING (`lvgruppe_id`) + INNER JOIN `seminare` USING (`seminar_id`) + INNER JOIN `mvv_modulteil_stgteilabschnitt` + ON (`mvv_stgteilabschnitt_modul`.`abschnitt_id` = + `mvv_modulteil_stgteilabschnitt`.`abschnitt_id` + AND `mvv_modulteil`.`modulteil_id` = + `mvv_modulteil_stgteilabschnitt`.`modulteil_id`) + LEFT JOIN `semester_data` AS `start_sem` + ON (`mvv_modul`.`start` = `start_sem`.`semester_id`) + LEFT JOIN `semester_data` AS `end_sem` + ON (`mvv_modul`.`end` = `end_sem`.`semester_id`) + LEFT JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) + WHERE `mvv_stgteilabschnitt`.`version_id` = :comp_version + AND `mvv_modulteil_stgteilabschnitt`.`fachsemester` IN (:fachsem) + AND ((`start_sem`.`beginn` < :sem_end OR ISNULL(`start_sem`.`beginn`)) + AND (`end_sem`.`ende` > :sem_start OR ISNULL(`end_sem`.`ende`))) + AND `seminare`.`status` IN (:typ) + AND `seminare`.`start_time` <= :sem_end + AND (`semester_courses`.`semester_id` IS NULL OR `semester_courses`.`semester_id` = :semester_id) + ) AS `semcomp` ON (`semcomp`.`seminar_id` = `ccomp`.`seminar_id`) + INNER JOIN `mvv_modulteil_stgteilabschnitt` AS `mms1` + ON (`mms1`.`abschnitt_id` = `semcomp`.`abschnitt_id` AND `mms1`.`modulteil_id` = `semcomp`.`modulteil_id`) + WHERE `mms1`.`fachsemester` IN ( + SELECT `fachsemester` + FROM `mvv_modulteil_stgteilabschnitt` AS `mms2` + WHERE `mms2`.`abschnitt_id` = `sembase`.`abschnitt_id` + AND `mms2`.`modulteil_id` = `sembase`.`modulteil_id`) + ORDER BY `cbase_seminar_id`"; + + // if no filter is set use all types and fachsems + $fachsems = $this->fachsems ? $this->fachsems : implode(',', range(1, 6)); + $semtypes = $this->semtypes ? $this->semtypes : implode(',', array_keys(SemType::getTypes())); + + $db = DBManager::get(); + $conflicts = $db->fetchAll($query, [ + ':base_version' => $this->base_version_id, + ':comp_version' => $this->comp_version_id, + ':fachsem' => explode(',', $fachsems), + ':typ' => explode(',', $semtypes), + ':sem_start' => $this->semester->beginn, + ':sem_end' => $this->semester->ende, + ':semester_id' => $this->semester->id + ]); + + $conlicts = []; + foreach ($conflicts as $conflict) { + $ovl_conflict = new MvvOverlappingConflict(); + $ovl_conflict->selection_id = $this->id; + $ovl_conflict->base_abschnitt_id = $conflict['sembase_abschnitt_id']; + $ovl_conflict->base_modulteil_id = $conflict['sembase_modulteil_id']; + $ovl_conflict->base_course_id = $conflict['cbase_seminar_id']; + $ovl_conflict->base_metadate_id = $conflict['cbase_metadate_id']; + $ovl_conflict->comp_abschnitt_id = $conflict['semcomp_abschnitt_id']; + $ovl_conflict->comp_modulteil_id = $conflict['semcomp_modulteil_id']; + $ovl_conflict->comp_course_id = $conflict['ccomp_seminar_id']; + $ovl_conflict->comp_metadate_id = $conflict['ccomp_metadate_id']; + $this->conflicts[] = $ovl_conflict; + } + return $this->store(); + } + + /** + * Returns all conflicts of all selections with the given selection id. + * + * @param string $selection_id The selection id. + * @param boolean $only_visible Returns only visible conflicts. + * @return SimpleORMapCollection All conflicts of appropriate selections. + */ + public static function getConflictsBySelection($selection_id, $only_visible = false) + { + $excluded_courses = []; + $visible_sql = ''; + if ($only_visible) { + $excluded_courses = SimpleORMapCollection::createFromArray( + MvvOverlappingExclude::findBySelection_id($selection_id))->pluck('course_id'); + if ($excluded_courses) { + $visible_sql = 'AND `mvv_ovl_conflicts`.`comp_course_id` NOT IN (:course_ids)'; + } + } + return SimpleORMapCollection::createFromArray( + MvvOverlappingConflict::findBySql('LEFT JOIN `mvv_ovl_selections` + ON (`mvv_ovl_conflicts`.`selection_id` = `mvv_ovl_selections`.`id`) + WHERE `mvv_ovl_selections`.`selection_id` = :selection_id ' . $visible_sql, [ + ':selection_id' => $selection_id, + ':course_ids' => $excluded_courses + ]) + ); + } + + /** + * Returns a md5 hash over all given parameters. + * + * @param string $base_version The id of the base version. + * @param string $comp_versions The id of the compared version. + * @param array|string $fachsems An array or a string with comma separated fachsem numbers. + * @param array|string $semtypes An array or a string with comma separated course types. + * @param string|null $user_id User id that created the selection (defaults to current user) + * @return string The md5 id. + */ + public static function createSelectionId($base_version, $comp_versions, $fachsems, $semtypes, string $user_id = null) + { + if (is_array($fachsems)) { + sort($fachsems, SORT_NUMERIC); + $fachsems = implode(',', $fachsems); + } + if (is_array($semtypes)) { + sort($semtypes, SORT_NUMERIC); + $semtypes = implode(',', $semtypes); + } + if (is_array($comp_versions)) { + $comp_version_ids = []; + foreach ($comp_versions as $comp_version) { + $comp_version_ids[] = $comp_version->id; + } + sort($comp_version_ids); + $comp_versions = implode(',', $comp_version_ids); + } else { + $comp_versions = $comp_versions->id; + } + return md5(implode('_', [ + $base_version->id, + $comp_versions, + trim($fachsems) ? $fachsems : 'x', + trim($semtypes) ? $semtypes : 'x', + $user_id ?? $GLOBALS['user']->id, + ])); + } + + /** + * Returns all excluded (hidden) conflicts of this selection. + * + * @return SimpleORMapCollection The excluded (hidden) conflicts. + */ + public function getExcludedConflicts() + { + return $this->conflicts->findBy( + 'comp_course_id', + $this->excludes->pluck('course_id') + ); + } +} diff --git a/lib/models/NewsRange.class.php b/lib/models/NewsRange.class.php deleted file mode 100644 index 7785214..0000000 --- a/lib/models/NewsRange.class.php +++ /dev/null @@ -1,71 +0,0 @@ - - * @copyright 2012 Stud.IP Core-Group - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * - * @property array $id alias for pk - * @property string $news_id database column - * @property string $range_id database column - * @property int|null $mkdate database column - * @property int|null $chdate database column - * @property User $user belongs_to User - * @property Course $course belongs_to Course - * @property Institute $institute belongs_to Institute - * @property mixed $type additional field - * @property mixed $name additional field - */ -class NewsRange extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'news_range'; - $config['belongs_to']['user'] = [ - 'class_name' => User::class, - 'foreign_key' => 'range_id', - ]; - $config['belongs_to']['course'] = [ - 'class_name' => Course::class, - 'foreign_key' => 'range_id', - ]; - $config['belongs_to']['institute'] = [ - 'class_name' => Institute::class, - 'foreign_key' => 'range_id', - ]; - $config['additional_fields']['type'] = true; - $config['additional_fields']['name'] = true; - parent::configure($config); - } - - function getType() - { - return get_object_type($this->range_id, ['sem','inst','user','fak']); - } - - function getName() - { - switch ($this->type) { - case 'global': - return _('Stud.IP-Startseite'); - break; - case 'sem': - return $this->course->name; - break; - case 'user': - return $this->user->getFullName(); - break; - case 'inst': - case 'fak': - return $this->institute->name; - break; - } - } -} diff --git a/lib/models/NewsRange.php b/lib/models/NewsRange.php new file mode 100644 index 0000000..7785214 --- /dev/null +++ b/lib/models/NewsRange.php @@ -0,0 +1,71 @@ + + * @copyright 2012 Stud.IP Core-Group + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + * @property array $id alias for pk + * @property string $news_id database column + * @property string $range_id database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property User $user belongs_to User + * @property Course $course belongs_to Course + * @property Institute $institute belongs_to Institute + * @property mixed $type additional field + * @property mixed $name additional field + */ +class NewsRange extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'news_range'; + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'range_id', + ]; + $config['belongs_to']['course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'range_id', + ]; + $config['belongs_to']['institute'] = [ + 'class_name' => Institute::class, + 'foreign_key' => 'range_id', + ]; + $config['additional_fields']['type'] = true; + $config['additional_fields']['name'] = true; + parent::configure($config); + } + + function getType() + { + return get_object_type($this->range_id, ['sem','inst','user','fak']); + } + + function getName() + { + switch ($this->type) { + case 'global': + return _('Stud.IP-Startseite'); + break; + case 'sem': + return $this->course->name; + break; + case 'user': + return $this->user->getFullName(); + break; + case 'inst': + case 'fak': + return $this->institute->name; + break; + } + } +} diff --git a/lib/models/NewsRoles.class.php b/lib/models/NewsRoles.class.php deleted file mode 100644 index 56da6ec..0000000 --- a/lib/models/NewsRoles.class.php +++ /dev/null @@ -1,142 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @package admin - * @since 5.1 - * - * @property array $id alias for pk - * @property string $news_id database column - * @property int $roleid database column - * @property StudipNews $news_ranges belongs_to StudipNews - */ - -class NewsRoles extends SimpleORMap -{ - protected static function configure($config = []) - { - $config['db_table'] = 'news_roles'; - - $config['belongs_to']['news_ranges'] = [ - 'class_name' => StudipNews::class, - 'foreign_key' => 'news_id', - ]; - - parent::configure($config); - } - - public static function checkUserAccess($news_id, $user_id = null) - { - $user_id = $user_id ?: (isset($GLOBALS['user']) ? $GLOBALS['user']->id : null); - $news_roles = self::getRoles($news_id); - - if (!$news_roles) { - return true; - } - - if (!$user_id) { - return false; - } - - $user_roles = RolePersistence::getAssignedRoles($user_id, true); - - foreach ($news_roles as $news_role) { - foreach ($user_roles as $user_role) { - if ($news_role->getRoleid() === $user_role->getRoleid()) { - return true; - } - } - } - - return false; - } - - public static function getRoles($news_id) - { - $news_roles = self::findBynews_id($news_id); - $news_role_ids = []; - foreach ($news_roles as $news_role) { - $news_role_ids[] = $news_role['roleid']; - } - - $only_system_roles = Config::get()->NEWS_ONLY_SYSTEM_ROLES; - $roles = RolePersistence::getAllRoles(); - $re = []; - foreach ($news_role_ids as $role_id) { - if (isset($roles[$role_id])) { - if ($only_system_roles && !$roles[$role_id]->getSystemtype()) { - continue; - } - $re[$role_id] = $roles[$role_id]; - } - } - return $re; - } - - public static function getAvailableRoles($news_id = null) - { - $news_role_ids = []; - if ($news_id) { - $news_roles = self::findBynews_id($news_id); - foreach ($news_roles as $news_role) { - $news_role_ids[] = $news_role['roleid']; - } - } - - $only_system_roles = Config::get()->NEWS_ONLY_SYSTEM_ROLES; - $roles = RolePersistence::getAllRoles(); - $rolesStats = RolePersistence::getStatistics(); - $re = []; - foreach ($roles as $key => $role) { - if (!in_array($key, $news_role_ids)) { - if ($only_system_roles && !$role->getSystemtype()) { - continue; - } - if ($rolesStats[$role->getRoleid()]['explicit'] + $rolesStats[$role->getRoleid()]['implicit'] == 0) { - continue; - } - $re[$key] = $role; - } - } - - return $re; - } - - public static function update($news_id, $new_roles) - { - self::deleteBynews_id($news_id); - - if ($new_roles) { - foreach ($new_roles as $new_role) { - $NewsRoles = new self(); - $NewsRoles->news_id = $news_id; - $NewsRoles->roleid = $new_role; - $NewsRoles->store(); - } - } - } - - public static function load($new_roles) - { - $roles = RolePersistence::getAllRoles(); - - return array_filter(array_map( - function ($role_id) use ($roles) { - if (!isset($roles[$role_id])) { - return false; - } - return [$role_id, $roles[$role_id]]; - }, - $new_roles - )); - } -} diff --git a/lib/models/NewsRoles.php b/lib/models/NewsRoles.php new file mode 100644 index 0000000..56da6ec --- /dev/null +++ b/lib/models/NewsRoles.php @@ -0,0 +1,142 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package admin + * @since 5.1 + * + * @property array $id alias for pk + * @property string $news_id database column + * @property int $roleid database column + * @property StudipNews $news_ranges belongs_to StudipNews + */ + +class NewsRoles extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'news_roles'; + + $config['belongs_to']['news_ranges'] = [ + 'class_name' => StudipNews::class, + 'foreign_key' => 'news_id', + ]; + + parent::configure($config); + } + + public static function checkUserAccess($news_id, $user_id = null) + { + $user_id = $user_id ?: (isset($GLOBALS['user']) ? $GLOBALS['user']->id : null); + $news_roles = self::getRoles($news_id); + + if (!$news_roles) { + return true; + } + + if (!$user_id) { + return false; + } + + $user_roles = RolePersistence::getAssignedRoles($user_id, true); + + foreach ($news_roles as $news_role) { + foreach ($user_roles as $user_role) { + if ($news_role->getRoleid() === $user_role->getRoleid()) { + return true; + } + } + } + + return false; + } + + public static function getRoles($news_id) + { + $news_roles = self::findBynews_id($news_id); + $news_role_ids = []; + foreach ($news_roles as $news_role) { + $news_role_ids[] = $news_role['roleid']; + } + + $only_system_roles = Config::get()->NEWS_ONLY_SYSTEM_ROLES; + $roles = RolePersistence::getAllRoles(); + $re = []; + foreach ($news_role_ids as $role_id) { + if (isset($roles[$role_id])) { + if ($only_system_roles && !$roles[$role_id]->getSystemtype()) { + continue; + } + $re[$role_id] = $roles[$role_id]; + } + } + return $re; + } + + public static function getAvailableRoles($news_id = null) + { + $news_role_ids = []; + if ($news_id) { + $news_roles = self::findBynews_id($news_id); + foreach ($news_roles as $news_role) { + $news_role_ids[] = $news_role['roleid']; + } + } + + $only_system_roles = Config::get()->NEWS_ONLY_SYSTEM_ROLES; + $roles = RolePersistence::getAllRoles(); + $rolesStats = RolePersistence::getStatistics(); + $re = []; + foreach ($roles as $key => $role) { + if (!in_array($key, $news_role_ids)) { + if ($only_system_roles && !$role->getSystemtype()) { + continue; + } + if ($rolesStats[$role->getRoleid()]['explicit'] + $rolesStats[$role->getRoleid()]['implicit'] == 0) { + continue; + } + $re[$key] = $role; + } + } + + return $re; + } + + public static function update($news_id, $new_roles) + { + self::deleteBynews_id($news_id); + + if ($new_roles) { + foreach ($new_roles as $new_role) { + $NewsRoles = new self(); + $NewsRoles->news_id = $news_id; + $NewsRoles->roleid = $new_role; + $NewsRoles->store(); + } + } + } + + public static function load($new_roles) + { + $roles = RolePersistence::getAllRoles(); + + return array_filter(array_map( + function ($role_id) use ($roles) { + if (!isset($roles[$role_id])) { + return false; + } + return [$role_id, $roles[$role_id]]; + }, + $new_roles + )); + } +} diff --git a/lib/models/OpenGraphURL.class.php b/lib/models/OpenGraphURL.class.php deleted file mode 100644 index 29e5718..0000000 --- a/lib/models/OpenGraphURL.class.php +++ /dev/null @@ -1,326 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - */ - -/** - * A model class to handle the database table "opengraphdata", fetch data from - * an Opengraph-URL and render a fitting box with the opengraph information to - * the user. - * - * @property int $id alias column for opengraph_id - * @property int $opengraph_id database column - * @property string $hash database column - * @property string $url database column - * @property int|null $is_opengraph database column - * @property string|null $title database column - * @property string|null $image database column - * @property string|null $description database column - * @property string|null $type database column - * @property JSONArrayObject $data database column - * @property int $last_update database column - * @property int $chdate database column - * @property int $mkdate database column - */ -class OpenGraphURL extends SimpleORMap -{ - const EXPIRES_DURATION = 86400; // = 24 * 60 * 60 - - /** - * Configures this model. - * - * @param Array $config Configuration array - */ - protected static function configure($config = []) - { - $config['db_table'] = 'opengraphdata'; - - $config['serialized_fields']['data'] = JSONArrayObject::class; - - parent::configure($config); - } - - /** - * Create an instance of this model given url. Differs from findOneByURL - * insofar that it will return a new object with the given url set - * instead of null. - * - * @param String $url URL to find - * @return OpenGraphURL Either existing instance or a new instance for - * the given url - */ - public static function fromURL($url) - { - $og = self::findOneByUrl($url); - if (!$og) { - $og = new self(); - $og->url = $url; - } - return $og; - } - - /** - * Specialized findOneByURL function that uses the hash to find the - * appropriate record instead. - * - * @param string $url URL to find record for - * @return mixed instance of OpenGraphURL if available, null otherwise - */ - public static function findOneByURL($url) - { - return self::findOneByHash(md5($url)); - } - - /** - * Constructor of the object. Provides a fallback if a url is passed - * instead of the usually expected numeric id in order to not break - * backward compatibility. - * But this constructor will fail miserably if a url is passed that - * is not in the database. This was chosen by design to encourage the - * correct use of an id. - * - * @param mixed $id Numeric id, existing url or null - */ - public function __construct($id = null) - { - // Try to find matching id when an url is passed instead of an id. - // This is to ensure that no legacy code will immediately break. - if ($id !== null && !ctype_digit($id)) { - $temp = self::findOneByUrl($id); - if ($temp) { - $id = $temp->id; - } - } - - parent::__construct($id); - } - - /** - * Sets value of a column. Overwritten so that the hash is also set when - * the url is set. - * - * @param string $field - * @param string $value - * @return string - * @see SimpleORMap::setValue - */ - public function setValue($field, $value) - { - $ret = parent::setValue($field, $value); - - if ($field === 'url') { - $this->content['hash'] = md5($value); - } - - return $ret; - } - - /** - * Stores the object and fetches the opengraph information when either - * the object is new or outdated. - * - * @return int Number of updated records - */ - public function store() - { - if ($this->isNew() || $this->last_update < time() - self::EXPIRES_DURATION) { - // Store last update timestamp BEFORE fetching so another thread - // will not fetch again - $this->last_update = time(); - parent::store(); - - $this->fetch(); - } - - return parent::store(); - } - - /** - * Fetches information from the url by getting the contents of the - * webpage, parse the webpage and extract the information from the - * opengraph meta-tags. - * If the site doesn't have any opengraph-metatags it is in fact no - * opengraph node and thus no data will be stored in the database. - * Only $url['is_opengraph'] === '0' indicates that the site is no - * opengraph node at all. - * - * @todo The combination of FileManager::fetchURLMetadata() and the following request - * leads to two requests for the open graph data. This should - * be fixed due to performance reasons. - */ - public function fetch() - { - if (!Config::get()->OPENGRAPH_ENABLE) { - return; - } - - $isOpenGraph = false; - - $response = FileManager::fetchURLMetadata($this['url']); - if ((int)$response['response_code'] === 200 && isset($response['Content-Type']) && mb_strpos($response['Content-Type'],'html') !== false) { - if (preg_match('/(?<=charset=)[^;]*/i', $response['Content-Type'], $match)) { - $currentEncoding = trim($match[0], '"'); - } else { - $currentEncoding = 'UTF-8'; - } - - $context = get_default_http_stream_context($this['url']); - stream_context_set_option($context, [ - 'http' => [ - 'method' => 'GET', - 'header' => sprintf("User-Agent: Stud.IP v%s OpenGraph Parser\r\n", $GLOBALS['SOFTWARE_VERSION']), - ], - ]); - - $content = @file_get_contents($this['url'], false, $context); - - if ($content === false) { - return; - } - - $content = mb_encode_numericentity($content, [0x80, 0xffff, 0, 0xffff], $currentEncoding); - $old_libxml_error = libxml_use_internal_errors(true); - $doc = new DOMDocument(); - $doc->loadHTML($content); - libxml_use_internal_errors($old_libxml_error); - - $metatags = $doc->getElementsByTagName('meta'); - $reservedTags = ['url', 'chdate', 'mkdate', 'last_update', 'is_opengraph', 'data']; - $ogTags = []; - $data = []; - foreach ($metatags as $tag) { - $key = false; - if ($tag->hasAttribute('property') - && mb_strpos($tag->getAttribute('property'), 'og:') === 0) - { - $key = mb_strtolower(mb_substr($tag->getAttribute('property'), 3)); - } - if (!$key && $tag->hasAttribute('name') - && mb_strpos($tag->getAttribute('name'), 'og:') === 0) - { - $key = mb_strtolower(mb_substr($tag->getAttribute('name'), 3)); - } - if ($key) { - $content = $tag->getAttribute('content'); - $data[] = ['og:'.$key => $content]; - $ogTags[$key] = $content; - $isOpenGraph = true; - } - } - foreach ($ogTags as $key => $tag) { - if ($this->isField($key) && !in_array($key, $reservedTags)) { - $this[$key] = $tag; - } - } - if (empty($this['title']) && $isOpenGraph) { - $titles = $doc->getElementsByTagName('title'); - if ($titles->length > 0) { - $this['title'] = $titles->item(0)->textContent; - } - } - if (empty($this['description']) && $isOpenGraph) { - foreach ($metatags as $tag) { - if (mb_stripos($tag->getAttribute('name'), "description") !== false - || mb_stripos($tag->getAttribute('property'), "description") !== false) - { - $this['description'] = $tag->getAttribute('content'); - } - } - } - $this['data'] = $data; - } - - $this['is_opengraph'] = (int) $isOpenGraph; - } - - /** - * Renders a small box with the information of the opengraph url. Used in - * blubber and in the forum. - * - * @return string html output of the box. - */ - public function render() - { - if (!Config::get()->OPENGRAPH_ENABLE || !$this->getValue('is_opengraph')) { - return ''; - } - $template = $GLOBALS['template_factory']->open('shared/opengraphinfo_wide.php'); - $template->og = $this; - return $template->render(); - } - - /** - * Returns an array with all audiofiles that are provided by the opengraph-node. - * Each array-entry is an array itself with the url as first parameter and the - * content-type (important for