├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .phpcs.xml ├── CHANGES.md ├── README.md ├── amd ├── build │ ├── form.min.js │ ├── form.min.js.map │ ├── scheduleforchooser.min.js │ ├── scheduleforchooser.min.js.map │ ├── toggle_text.min.js │ └── toggle_text.min.js.map └── src │ ├── form.js │ ├── scheduleforchooser.js │ └── toggle_text.js ├── backup └── moodle2 │ ├── backup_zoom_activity_task.class.php │ ├── backup_zoom_stepslib.php │ ├── restore_zoom_activity_task.class.php │ └── restore_zoom_stepslib.php ├── classes ├── analytics │ └── indicator │ │ ├── activity_base.php │ │ ├── cognitive_depth.php │ │ └── social_breadth.php ├── api_limit_exception.php ├── bad_request_exception.php ├── dates.php ├── event │ ├── course_module_instance_list_viewed.php │ ├── course_module_viewed.php │ └── join_meeting_button_clicked.php ├── external.php ├── invitation.php ├── not_found_exception.php ├── output │ └── mobile.php ├── privacy │ └── provider.php ├── retry_failed_exception.php ├── search │ └── activity.php ├── task │ ├── delete_meeting_recordings.php │ ├── get_meeting_recordings.php │ ├── get_meeting_reports.php │ ├── send_ical_notifications.php │ ├── update_meetings.php │ └── update_tracking_fields.php ├── webservice.php └── webservice_exception.php ├── cli └── get_meeting_report.php ├── composer.json ├── console └── get_meeting_report.php ├── db ├── access.php ├── caches.php ├── install.php ├── install.xml ├── messages.php ├── mobile.php ├── services.php ├── tasks.php ├── uninstall.php └── upgrade.php ├── exportical.php ├── index.php ├── lang └── en │ └── zoom.php ├── lib.php ├── loadmeeting.php ├── loadrecording.php ├── locallib.php ├── mod_form.php ├── participants.php ├── phpcs.xml ├── pix ├── i │ ├── calendar.png │ └── calendar.svg ├── icon.gif ├── icon.png ├── icon.svg └── monologo.svg ├── recordings.php ├── recreate.php ├── report.php ├── settings.php ├── showrecording.php ├── styles.css ├── templates ├── breakoutrooms_room_data.mustache ├── breakoutrooms_room_datatoclone.mustache ├── breakoutrooms_room_groups.mustache ├── breakoutrooms_room_participants.mustache ├── breakoutrooms_rooms.mustache ├── mobile_view_page_ionic3.mustache └── mobile_view_page_latest.mustache ├── tests ├── advanced_passcode_test.php ├── error_handling_test.php ├── generator │ └── lib.php ├── get_meeting_reports_test.php ├── mod_zoom_grade_test.php ├── mod_zoom_invitation_test.php ├── mod_zoom_webservice_test.php └── privacy │ └── mod_zoom_provider_test.php ├── upgrade.txt ├── version.php └── view.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Moodle Plugin CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: 'ubuntu-latest' 8 | 9 | services: 10 | postgres: 11 | image: postgres:13 12 | env: 13 | POSTGRES_USER: 'postgres' 14 | POSTGRES_HOST_AUTH_METHOD: 'trust' 15 | ports: 16 | - 5432:5432 17 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 18 | 19 | mariadb: 20 | image: mariadb:10 21 | env: 22 | MYSQL_USER: 'root' 23 | MYSQL_ALLOW_EMPTY_PASSWORD: "true" 24 | MYSQL_CHARACTER_SET_SERVER: "utf8mb4" 25 | MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci" 26 | ports: 27 | - 3306:3306 28 | options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | include: 34 | - php: '8.4' 35 | moodle-branch: 'main' 36 | database: 'mariadb' 37 | - php: '8.4' 38 | moodle-branch: 'MOODLE_500_STABLE' 39 | database: 'mariadb' 40 | - php: '8.3' 41 | moodle-branch: 'MOODLE_405_STABLE' 42 | database: 'pgsql' 43 | - php: '8.2' 44 | moodle-branch: 'MOODLE_404_STABLE' 45 | database: 'mariadb' 46 | - php: '8.1' 47 | moodle-branch: 'MOODLE_403_STABLE' 48 | database: 'pgsql' 49 | - php: '8.0' 50 | moodle-branch: 'MOODLE_401_STABLE' 51 | database: 'pgsql' 52 | - php: '7.2' 53 | moodle-branch: 'MOODLE_39_STABLE' 54 | database: 'mariadb' 55 | 56 | steps: 57 | - name: Check out repository code 58 | uses: actions/checkout@v4 59 | with: 60 | path: plugin 61 | 62 | - name: Setup PHP ${{ matrix.php }} 63 | uses: shivammathur/setup-php@v2 64 | with: 65 | php-version: ${{ matrix.php }} 66 | extensions: ${{ matrix.extensions }} 67 | ini-values: max_input_vars=5000 68 | # If you are not using code coverage, keep "none". Otherwise, use "pcov" (Moodle 3.10 and up) or "xdebug". 69 | # If you try to use code coverage with "none", it will fallback to phpdbg (which has known problems). 70 | coverage: none 71 | 72 | - name: Initialise moodle-plugin-ci 73 | if: ${{ matrix.php >= '7.4' }} 74 | run: | 75 | composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4 76 | 77 | - name: Initialise moodle-plugin-ci v3 78 | if: ${{ matrix.php < '7.4' }} 79 | run: | 80 | composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3 81 | 82 | - name: Set up environment 83 | run: | 84 | echo $(cd ci/bin; pwd) >> $GITHUB_PATH 85 | echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH 86 | sudo locale-gen en_AU.UTF-8 87 | echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV 88 | 89 | - name: Install moodle-plugin-ci 90 | id: install_ci 91 | run: | 92 | moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 93 | env: 94 | DB: ${{ matrix.database }} 95 | MOODLE_BRANCH: ${{ matrix.moodle-branch }} 96 | MUSTACHE_IGNORE_NAMES: 'mobile_view_page_latest.mustache,mobile_view_page_ionic3.mustache' 97 | # Uncomment this to run Behat tests using the Moodle App. 98 | # MOODLE_APP: 'true' 99 | 100 | - name: PHP Lint 101 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 102 | run: moodle-plugin-ci phplint 103 | 104 | - name: PHP Mess Detector 105 | continue-on-error: true # This step will show errors but will not fail 106 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 107 | run: moodle-plugin-ci phpmd 108 | 109 | - name: Moodle Code Checker 110 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 111 | run: moodle-plugin-ci codechecker --max-warnings 0 112 | 113 | - name: Moodle PHPDoc Checker 114 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' && matrix.php >= '7.4' }} 115 | run: moodle-plugin-ci phpdoc --max-warnings 0 116 | 117 | - name: Moodle PHPDoc Checker v3 118 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' && matrix.php < '7.4' }} 119 | run: moodle-plugin-ci phpdoc 120 | 121 | - name: Validating 122 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 123 | run: moodle-plugin-ci validate 124 | 125 | - name: Check upgrade savepoints 126 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 127 | run: moodle-plugin-ci savepoints 128 | 129 | - name: Mustache Lint 130 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 131 | run: moodle-plugin-ci mustache 132 | 133 | - name: Grunt 134 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 135 | run: moodle-plugin-ci grunt --max-lint-warnings 0 || [ "$MOODLE_BRANCH" = 'MOODLE_39_STABLE' ] || [ "$MOODLE_BRANCH" = 'MOODLE_311_STABLE' ] 136 | env: 137 | MOODLE_BRANCH: ${{ matrix.moodle-branch }} 138 | 139 | - name: PHPUnit tests 140 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 141 | run: moodle-plugin-ci phpunit --fail-on-warning 142 | 143 | - name: Behat features 144 | id: behat 145 | if: ${{ !cancelled() && steps.install_ci.outcome == 'success' }} 146 | run: moodle-plugin-ci behat --profile chrome 147 | 148 | - name: Upload Behat Faildump 149 | if: ${{ failure() && steps.behat.outcome == 'failure' }} 150 | uses: actions/upload-artifact@v4 151 | with: 152 | name: Behat Faildump (${{ join(matrix.*, ', ') }}) 153 | path: ${{ github.workspace }}/moodledata/behat_dump 154 | retention-days: 7 155 | if-no-files-found: ignore 156 | 157 | - name: Make sure cancelled jobs are marked as failures. 158 | if: ${{ cancelled() }} 159 | run: exit 1 160 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file specifies intentionally untracked files that all Moodle git 2 | # repositories should ignore. It is recommended not to modify this file in your 3 | # local clone. Instead, use .git/info/exclude and add new records there as 4 | # needed. 5 | # 6 | # Example: if you deploy a contributed plugin mod/foobar into your site, put 7 | # the following line into .git/info/exclude file in your Moodle clone: 8 | # /mod/foobar/ 9 | # 10 | # See gitignore(5) man page for more details 11 | # 12 | /.sass-cache 13 | /lib/editor/tinymce/extra/tools/temp/ 14 | *~ 15 | *.swp 16 | /tags 17 | /TAGS 18 | /cscope.* 19 | /.patches/ 20 | /.idea/ 21 | /nbproject/ 22 | CVS 23 | .DS_Store 24 | /.settings/ 25 | /.project 26 | /.buildpath 27 | /.cache 28 | phpunit.xml 29 | # Composer support - only composer.json is to be in git, the rest is installed in each checkout. 30 | composer.phar 31 | composer.lock 32 | /behat.yml 33 | -------------------------------------------------------------------------------- /.phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | [Zoom](https://zoom.us) is a web- and app-based video conferencing service. This 4 | plugin offers tight integration with Moodle, supporting meeting creation, 5 | synchronization, grading and backup/restore. 6 | 7 | ## Prerequisites 8 | 9 | This plugin is designed for Educational or Business Zoom accounts. 10 | 11 | To connect to the Zoom APIs, this plugin requires an account-level app to be 12 | created. 13 | 14 | ### Server-to-Server OAuth 15 | To [create an account-level Server-to-Server OAuth app](https://developers.zoom.us/docs/internal-apps/create/), the `Server-to-server OAuth app` 16 | permission is required. You should create a separate Server-to-Server OAuth app for each Moodle install. 17 | 18 | The Server-to-Server OAuth app will generate a client ID, client secret and account ID. 19 | 20 | #### Granular scopes 21 | At a minimum, the following scopes are required: 22 | 23 | - meeting:read:meeting:admin (Get meeting) 24 | - meeting:read:invitation:admin (Get meeting invitation) 25 | - meeting:delete:meeting:admin (Delete meeting) 26 | - meeting:update:meeting:admin (Update meeting) 27 | - meeting:write:meeting:admin (Create meeting) 28 | - user:read:list_schedulers:admin (List schedulers) 29 | - user:read:settings:admin (Get user settings) 30 | - user:read:user:admin (Get user) 31 | 32 | Optional functionality can be enabled by granting additional scopes: 33 | 34 | - Meeting registrations 35 | - meeting:read:list_registrants:admin (Get registrants) 36 | - Reports for meetings / webinars (Licensed accounts and higher) 37 | - report:read:list_meeting_participants:admin 38 | - report:read:list_webinar_participants:admin 39 | - report:read:list_users:admin 40 | - report:read:user:admin 41 | - Faster reports for meetings / webinars (Business accounts and higher) 42 | - dashboard:read:list_meeting_participants:admin 43 | - dashboard:read:list_meetings:admin 44 | - dashboard:read:list_webinar_participants:admin 45 | - dashboard:read:list_webinars:admin 46 | - Allow recordings to be viewed (zoom | viewrecordings) 47 | - cloud_recording:read:list_recording_files:admin 48 | - cloud_recording:read:list_user_recordings:admin 49 | - cloud_recording:read:recording_settings:admin 50 | - Tracking fields (zoom | defaulttrackingfields) 51 | - tracking_field:read:list_tracking_fields:admin 52 | - Recycle licenses (zoom | utmost), (zoom | recycleonjoin), (zoom | protectedgroups) 53 | - group:read:list_groups:admin 54 | - user:read:list_users:admin 55 | - user:update:user:admin 56 | - Webinars (zoom | showwebinars), (zoom | webinardefault) 57 | - webinar:read:list_registrants:admin 58 | - webinar:read:webinar:admin 59 | - webinar:delete:webinar:admin 60 | - webinar:update:webinar:admin 61 | - webinar:write:webinar:admin 62 | 63 | #### Classic scopes 64 | At a minimum, the following scopes are required: 65 | 66 | - meeting:read:admin (Read meeting details) 67 | - meeting:write:admin (Create/Update meetings) 68 | - user:read:admin (Read user details) 69 | 70 | Optional functionality can be enabled by granting additional scopes: 71 | 72 | - Reports for meetings / webinars 73 | - dashboard_meetings:read:admin (Business accounts and higher) 74 | - dashboard_webinars:read:admin (Business accounts and higher) 75 | - report:read:admin (Pro accounts and higher) 76 | - Allow recordings to be viewed (zoom | viewrecordings) 77 | - recording:read:admin 78 | - Tracking fields (zoom | defaulttrackingfields) 79 | - tracking_fields:read:admin 80 | - Recycle licenses (zoom | utmost), (zoom | recycleonjoin), (zoom | protectedgroups) 81 | - group:read:admin 82 | - user:write:admin 83 | - Webinars (zoom | showwebinars), (zoom | webinardefault) 84 | - webinar:read:admin 85 | - webinar:write:admin 86 | 87 | ## Installation 88 | 89 | 1. [Install plugin](https://docs.moodle.org/en/Installing_plugins#Installing_a_plugin) to the /mod/zoom folder in Moodle. 90 | 2. After installing the plugin, the following settings need to be configured to use the plugin: 91 | 92 | - Zoom account ID (mod_zoom | accountid) 93 | - Zoom client ID (mod_zoom | clientid) 94 | - Zoom client secret (mod_zoom | clientsecret) 95 | 96 | If you get "Access token is expired" errors, make sure the date/time on your 97 | server is properly synchronized with the time servers. 98 | -------------------------------------------------------------------------------- /amd/build/form.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Populates or de-populates password field based on whether the 3 | * password is required or not. 4 | * 5 | * @copyright 2018 UC Regents 6 | * @author Kubilay Agi 7 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 8 | */ 9 | define("mod_zoom/form",["jquery","core/form-autocomplete","core/str","core/notification"],(function($,autocomplete,str,notification){var SELECTORS_REPEAT_SELECT='select[name="recurrence_type"]',SELECTORS_REPEAT_INTERVAL=".repeat_interval",SELECTORS_REPEAT_INTERVAL_DAILY="#interval_daily",SELECTORS_REPEAT_INTERVAL_WEEKLY="#interval_weekly",SELECTORS_REPEAT_INTERVAL_MONTHLY="#interval_monthly",SELECTORS_REPEAT_INTERVAL_OPTIONS='select[name="repeat_interval"] option',SELECTORS_START_TIME='select[name*="start_time"]',SELECTORS_DURATION='*[name*="duration"]',SELECTORS_RECURRING='input[name="recurring"][type!="hidden"]',SELECTORS_OPTION_JBH='input[name="option_jbh"][type!="hidden"]',SELECTORS_OPTION_WAITING_ROOM='input[name="option_waiting_room"][type!="hidden"]',REPEAT_OPTIONS_REPEAT_OPTION_NONE=0,REPEAT_OPTIONS_REPEAT_OPTION_DAILY=1,REPEAT_OPTIONS_REPEAT_OPTION_WEEKLY=2,REPEAT_OPTIONS_REPEAT_OPTION_MONTHLY=3,REPEAT_MAX_OPTIONS_REPEAT_OPTION_WEEKLY=12,REPEAT_MAX_OPTIONS_REPEAT_OPTION_MONTHLY=3,toggleStartTimeDuration=function(){var disabled=!1,repeatVal=parseInt($(SELECTORS_REPEAT_SELECT).val(),10);$(SELECTORS_RECURRING).prop("checked")&&repeatVal===REPEAT_OPTIONS_REPEAT_OPTION_NONE&&(disabled=!0),$(SELECTORS_START_TIME).prop("disabled",disabled),$(SELECTORS_DURATION).prop("disabled",disabled)},toggleRepeatIntervalText=function(){$(SELECTORS_REPEAT_INTERVAL).hide();var repeatSelectVal=parseInt($(SELECTORS_REPEAT_SELECT).val(),10);repeatSelectVal===REPEAT_OPTIONS_REPEAT_OPTION_DAILY?$(SELECTORS_REPEAT_INTERVAL_DAILY).show():repeatSelectVal===REPEAT_OPTIONS_REPEAT_OPTION_WEEKLY?$(SELECTORS_REPEAT_INTERVAL_WEEKLY).show():repeatSelectVal===REPEAT_OPTIONS_REPEAT_OPTION_MONTHLY&&$(SELECTORS_REPEAT_INTERVAL_MONTHLY).show()},limitRepeatValues=function(){var selectedValue=parseInt($(SELECTORS_REPEAT_SELECT).val(),10);$(SELECTORS_REPEAT_INTERVAL_OPTIONS).each((function(){selectedValue===REPEAT_OPTIONS_REPEAT_OPTION_WEEKLY?this.value>REPEAT_MAX_OPTIONS_REPEAT_OPTION_WEEKLY&&$(this).hide():selectedValue===REPEAT_OPTIONS_REPEAT_OPTION_MONTHLY?this.value>REPEAT_MAX_OPTIONS_REPEAT_OPTION_MONTHLY&&$(this).hide():$(this).show()}))},TabsComponent=function(tabsColumn,tabsContentColumn,initialTabsCount,emptyAlert){this.tabsColumn=tabsColumn,this.tabsContentColumn=tabsContentColumn,this.emptyAlert=emptyAlert,this.countTabs=initialTabsCount,this.buildTab=function(item){var tab=item.tab.element,tabLink=$(".nav-link",tab);return tab.attr("id","tab-"+this.countTabs),$(".tab-name",tabLink).text(item.tab.name),tabLink.attr("href","#link"+this.countTabs),$("li a",this.tabsColumn).removeClass("active"),tabLink.addClass("active"),tab},this.buildTabContent=function(item){var tabContent=item.tabContent.element;return tabContent.attr("id","link"+this.countTabs),$(".tab-pane",this.tabsContentColumn).removeClass("active"),tabContent.addClass("active"),tabContent},this.addTab=function(item){var tab=this.buildTab(item),tabContent=this.buildTabContent(item);return this.emptyAlert.addClass("hidden"),$("ul",this.tabsColumn).append(tab),$(".tab-content",this.tabsContentColumn).append(tabContent),{element:tab,content:tabContent}},this.deleteTab=function(item){var tab=item,tabContent=$($("a",tab).attr("href"));(tab.remove(),tabContent.remove(),$("li",this.tabsColumn).length)?$("li a.active",this.tabsColumn).length||$("li:first-child a",this.tabsColumn).trigger("click"):this.emptyAlert.removeClass("hidden")}},BreakoutroomsEditor=function(){this.roomsListColumn=$("#mod-zoom-meeting-rooms-list"),this.roomsList=$("ul",this.roomsListColumn),this.addBtn=$("#add-room",this.roomsListColumn),this.emptyAlert=$(".empty-alert",this.roomsListColumn),this.deleteBtn=$(".delete-room",this.roomsListColumn),this.roomsDataColumn=$("#mod-zoom-meeting-rooms-data"),this.roomItemToClone=$("#rooms-list-item").html(),this.roomItemDataToClone=$("#rooms-list-item-data").html(),this.initialRoomsCount=parseInt(this.roomsListColumn.attr("data-initial-rooms-count")),this.tabsComponent=new TabsComponent(this.roomsListColumn,this.roomsDataColumn,this.initialRoomsCount,this.emptyAlert),this.init=function(){str.get_strings([{key:"room",component:"zoom"}]).then((function(){return null})).fail(notification.exception),this.addRoomEvent(),this.deleteRoomEvent(),$("li",this.roomsListColumn).length?(this.changeRoomNameEvent(),this.buildAutocompleteComponents()):this.emptyAlert.removeClass("hidden")},this.addRoomEvent=function(){var thisObject=this;thisObject.addBtn.click((function(){thisObject.tabsComponent.countTabs++;var newRoomName=M.util.get_string("room","zoom")+" "+thisObject.tabsComponent.countTabs,newRoomElement=$(thisObject.roomItemToClone),newRoomDataElement=$(thisObject.roomItemDataToClone),newRoomIndex=thisObject.tabsComponent.countTabs,roomNameInputId="room-name-"+newRoomIndex;$("input[type=text]",newRoomDataElement).prev().attr("for",roomNameInputId),$("input[type=text]",newRoomDataElement).attr("id",roomNameInputId),$("input[type=text]",newRoomDataElement).attr("name",roomNameInputId),$("input[type=text]",newRoomDataElement).val(newRoomName),$("input[type=text]",newRoomDataElement).next().attr("name","rooms["+newRoomIndex+"]"),$("input[type=text]",newRoomDataElement).next().val(newRoomName);var roomParticipantsSelectId="participants-"+newRoomIndex;$(".room-participants",newRoomDataElement).attr("id",roomParticipantsSelectId),$(".room-participants",newRoomDataElement).attr("name","roomsparticipants["+newRoomIndex+"][]");var roomGroupsSelectId="groups-"+newRoomIndex;$(".room-groups",newRoomDataElement).attr("id",roomGroupsSelectId),$(".room-groups",newRoomDataElement).attr("name","roomsgroups["+newRoomIndex+"][]");var newRoom={tab:{name:newRoomName,element:newRoomElement},tabContent:{element:newRoomDataElement}},addedTab=thisObject.tabsComponent.addTab(newRoom);$("li:last .delete-room",thisObject.roomsList).click((function(){var thisItem=$(this).closest("li");thisObject.tabsComponent.deleteTab(thisItem)})),$("input[type=text]",addedTab.content).on("change keyup paste",(function(){var newHiddenValue=this.value;$(this).next().val(newHiddenValue),$(".tab-name",addedTab.element).text(this.value)})),thisObject.buildAutocompleteComponent(roomParticipantsSelectId,"addparticipant"),thisObject.buildAutocompleteComponent(roomGroupsSelectId,"addparticipantgroup")}))},this.deleteRoomEvent=function(){var thisObject=this;thisObject.deleteBtn.click((function(){var thisItem=$(this).closest("li");thisObject.tabsComponent.deleteTab(thisItem)}))},this.changeRoomNameEvent=function(){var thisObject=this;$("li",this.roomsListColumn).each((function(){var tabIndex=$(this).attr("id").split("-")[1];$('input[name="room-name-'+tabIndex+'"]',thisObject.roomsDataColumn).on("change keyup paste",(function(){var newHiddenValue=this.value;$(this).next().val(newHiddenValue),$("#tab-"+tabIndex+" .tab-name").text(this.value)}))}))},this.buildAutocompleteComponents=function(){var thisObject=this;$(".room-participants",thisObject.roomsDataColumn).each((function(){var thisItemId=$(this).attr("id");thisObject.buildAutocompleteComponent(thisItemId,"addparticipant")})),$(".room-groups",thisObject.roomsDataColumn).each((function(){var thisItemId=$(this).attr("id");thisObject.buildAutocompleteComponent(thisItemId,"addparticipantgroup")}))},this.buildAutocompleteComponent=function(id,placeholder){var stringkeys=[{key:placeholder,component:"zoom"},{key:"selectionarea",component:"zoom"}];str.get_strings(stringkeys).then((function(langstrings){var placeholderString=langstrings[0],noSelectionString=langstrings[1];return autocomplete.enhance("#"+id,!1,"",placeholderString,!1,!0,noSelectionString,!0),null})).fail(notification.exception)}};return{init:function(){var optionJoinBeforeHost=$(SELECTORS_OPTION_JBH),optionWaitingRoom=$(SELECTORS_OPTION_WAITING_ROOM);optionJoinBeforeHost.change((function(){!0===optionJoinBeforeHost.is(":checked")&&optionWaitingRoom.prop("checked",!1)})),optionWaitingRoom.change((function(){!0===optionWaitingRoom.is(":checked")&&optionJoinBeforeHost.prop("checked",!1)})),toggleStartTimeDuration(),toggleRepeatIntervalText(),limitRepeatValues(),$(SELECTORS_REPEAT_SELECT).change((function(){toggleStartTimeDuration(),toggleRepeatIntervalText(),limitRepeatValues()})),$(SELECTORS_RECURRING).change((function(){toggleStartTimeDuration()})),(new BreakoutroomsEditor).init()}}})); 10 | 11 | //# sourceMappingURL=form.min.js.map -------------------------------------------------------------------------------- /amd/build/scheduleforchooser.min.js: -------------------------------------------------------------------------------- 1 | define("mod_zoom/scheduleforchooser",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0; 2 | /** 3 | * Schedule for selection handler. 4 | * 5 | * @module mod_zoom/scheduleforchooser 6 | * @copyright 2022 Antonio Duran Terres 7 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 8 | */ 9 | const Selectors_fields={selector:'[data-scheduleforchooser-field="selector"]',updateButton:'[data-scheduleforchooser-field="updateButton"]'};_exports.init=()=>{document.querySelector(Selectors_fields.selector).addEventListener("change",(e=>{const form=e.target.closest("form"),updateButton=form.querySelector(Selectors_fields.updateButton),fieldset=updateButton.closest("fieldset"),url=new URL(form.action);url.hash=fieldset.id,form.action=url.toString(),updateButton.click()}))}})); 10 | 11 | //# sourceMappingURL=scheduleforchooser.min.js.map -------------------------------------------------------------------------------- /amd/build/scheduleforchooser.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"scheduleforchooser.min.js","sources":["../src/scheduleforchooser.js"],"sourcesContent":["// This file is part of the Zoom plugin for Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Schedule for selection handler.\n *\n * @module mod_zoom/scheduleforchooser\n * @copyright 2022 Antonio Duran Terres \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n fields: {\n selector: '[data-scheduleforchooser-field=\"selector\"]',\n updateButton: '[data-scheduleforchooser-field=\"updateButton\"]',\n },\n};\n\n/**\n * Initialise the schedule_for chooser.\n */\nexport const init = () => {\n document.querySelector(Selectors.fields.selector).addEventListener('change', e => {\n const form = e.target.closest('form');\n const updateButton = form.querySelector(Selectors.fields.updateButton);\n const fieldset = updateButton.closest('fieldset');\n\n const url = new URL(form.action);\n url.hash = fieldset.id;\n\n form.action = url.toString();\n updateButton.click();\n });\n};\n"],"names":["Selectors","selector","updateButton","document","querySelector","addEventListener","e","form","target","closest","fieldset","url","URL","action","hash","id","toString","click"],"mappings":";;;;;;;;MAuBMA,iBACM,CACJC,SAAU,6CACVC,aAAc,gEAOF,KAChBC,SAASC,cAAcJ,iBAAiBC,UAAUI,iBAAiB,UAAUC,UACnEC,KAAOD,EAAEE,OAAOC,QAAQ,QACxBP,aAAeK,KAAKH,cAAcJ,iBAAiBE,cACnDQ,SAAWR,aAAaO,QAAQ,YAEhCE,IAAM,IAAIC,IAAIL,KAAKM,QACzBF,IAAIG,KAAOJ,SAASK,GAEpBR,KAAKM,OAASF,IAAIK,WAClBd,aAAae"} -------------------------------------------------------------------------------- /amd/build/toggle_text.min.js: -------------------------------------------------------------------------------- 1 | define("mod_zoom/toggle_text",["exports","core/str"],(function(_exports,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=()=>{const button=document.querySelector("#show-more-button");if(null!==button){const body=document.querySelector("#show-more-body");button.addEventListener("click",(async()=>{""===body.style.display?(body.style.display="none",button.innerHTML=await(0,_str.get_string)("meeting_invite_show","mod_zoom")):(body.style.display="",button.innerHTML=await(0,_str.get_string)("meeting_invite_hide","mod_zoom"))}))}}})); 2 | 3 | //# sourceMappingURL=toggle_text.min.js.map -------------------------------------------------------------------------------- /amd/build/toggle_text.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"toggle_text.min.js","sources":["../src/toggle_text.js"],"sourcesContent":["// This file is part of the Zoom plugin for Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Toggles text to be shown when a user hits 'Show More' and\n * hides text when user hits 'Show Less'\n *\n * @copyright 2020 UC Regents\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString} from \"core/str\";\n\nexport const init = () => {\n const button = document.querySelector(\"#show-more-button\");\n if (button !== null) {\n const body = document.querySelector(\"#show-more-body\");\n button.addEventListener(\"click\", async() => {\n if (body.style.display === \"\") {\n body.style.display = \"none\";\n button.innerHTML = await getString(\"meeting_invite_show\", \"mod_zoom\");\n } else {\n body.style.display = \"\";\n button.innerHTML = await getString(\"meeting_invite_hide\", \"mod_zoom\");\n }\n });\n }\n};\n"],"names":["button","document","querySelector","body","addEventListener","async","style","display","innerHTML"],"mappings":"yKAyBoB,WACZA,OAASC,SAASC,cAAc,wBACvB,OAAXF,OAAiB,OACbG,KAAOF,SAASC,cAAc,mBACpCF,OAAOI,iBAAiB,SAASC,UACJ,KAAvBF,KAAKG,MAAMC,SACbJ,KAAKG,MAAMC,QAAU,OACrBP,OAAOQ,gBAAkB,mBAAU,sBAAuB,cAE1DL,KAAKG,MAAMC,QAAU,GACrBP,OAAOQ,gBAAkB,mBAAU,sBAAuB"} -------------------------------------------------------------------------------- /amd/src/scheduleforchooser.js: -------------------------------------------------------------------------------- 1 | // This file is part of the Zoom plugin for Moodle - http://moodle.org/ 2 | // 3 | // Moodle is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // Moodle is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with Moodle. If not, see . 15 | 16 | /** 17 | * Schedule for selection handler. 18 | * 19 | * @module mod_zoom/scheduleforchooser 20 | * @copyright 2022 Antonio Duran Terres 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | const Selectors = { 25 | fields: { 26 | selector: '[data-scheduleforchooser-field="selector"]', 27 | updateButton: '[data-scheduleforchooser-field="updateButton"]', 28 | }, 29 | }; 30 | 31 | /** 32 | * Initialise the schedule_for chooser. 33 | */ 34 | export const init = () => { 35 | document.querySelector(Selectors.fields.selector).addEventListener('change', e => { 36 | const form = e.target.closest('form'); 37 | const updateButton = form.querySelector(Selectors.fields.updateButton); 38 | const fieldset = updateButton.closest('fieldset'); 39 | 40 | const url = new URL(form.action); 41 | url.hash = fieldset.id; 42 | 43 | form.action = url.toString(); 44 | updateButton.click(); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /amd/src/toggle_text.js: -------------------------------------------------------------------------------- 1 | // This file is part of the Zoom plugin for Moodle - http://moodle.org/ 2 | // 3 | // Moodle is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // Moodle is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with Moodle. If not, see . 15 | 16 | /** 17 | * Toggles text to be shown when a user hits 'Show More' and 18 | * hides text when user hits 'Show Less' 19 | * 20 | * @copyright 2020 UC Regents 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | import {get_string as getString} from "core/str"; 25 | 26 | export const init = () => { 27 | const button = document.querySelector("#show-more-button"); 28 | if (button !== null) { 29 | const body = document.querySelector("#show-more-body"); 30 | button.addEventListener("click", async() => { 31 | if (body.style.display === "") { 32 | body.style.display = "none"; 33 | button.innerHTML = await getString("meeting_invite_show", "mod_zoom"); 34 | } else { 35 | body.style.display = ""; 36 | button.innerHTML = await getString("meeting_invite_hide", "mod_zoom"); 37 | } 38 | }); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /backup/moodle2/backup_zoom_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines backup_zoom_activity_task class 19 | * 20 | * @package mod_zoom 21 | * @category backup 22 | * @copyright 2015 UC Regents 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | require_once($CFG->dirroot . '/mod/zoom/backup/moodle2/backup_zoom_stepslib.php'); 29 | 30 | use mod_zoom\backup_activity_structure_step; 31 | 32 | /** 33 | * Provides the steps to perform one complete backup of the zoom instance 34 | */ 35 | class backup_zoom_activity_task extends backup_activity_task { 36 | /** 37 | * No specific settings for this activity 38 | */ 39 | protected function define_my_settings() { 40 | } 41 | 42 | /** 43 | * Defines a backup step to store the instance data in the zoom.xml file 44 | */ 45 | protected function define_my_steps() { 46 | $this->add_step(new backup_activity_structure_step('zoom_structure', 'zoom.xml')); 47 | } 48 | 49 | /** 50 | * Encodes URLs to the index.php and view.php scripts 51 | * 52 | * @param string $content some HTML text that eventually contains URLs to the activity instance scripts 53 | * @return string the content with the URLs encoded 54 | */ 55 | public static function encode_content_links($content) { 56 | global $CFG; 57 | 58 | $base = preg_quote($CFG->wwwroot, '/'); 59 | 60 | // Link to the list of zooms. 61 | $search = '/(' . $base . '\/mod\/zoom\/index.php\?id\=)([0-9]+)/'; 62 | $content = preg_replace($search, '$@ZOOMINDEX*$2@$', $content); 63 | 64 | // Link to zoom view by moduleid. 65 | $search = '/(' . $base . '\/mod\/zoom\/view.php\?id\=)([0-9]+)/'; 66 | $content = preg_replace($search, '$@ZOOMVIEWBYID*$2@$', $content); 67 | 68 | return $content; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /backup/moodle2/backup_zoom_stepslib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines backup_activity_structure_step class. 19 | * 20 | * @package mod_zoom 21 | * @category backup 22 | * @copyright 2015 UC Regents 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_zoom; 27 | 28 | use backup; 29 | use backup_nested_element; 30 | 31 | /** 32 | * Define the complete zoom structure for backup, with file and id annotations. 33 | */ 34 | class backup_activity_structure_step extends \backup_activity_structure_step { 35 | /** 36 | * Defines the backup structure of the module. 37 | * 38 | * @return backup_nested_element 39 | */ 40 | protected function define_structure() { 41 | // Define the root element describing the zoom instance. 42 | $zoom = new backup_nested_element('zoom', ['id'], [ 43 | 'intro', 'introformat', 'grade', 'grading_method', 'meeting_id', 'join_url', 'created_at', 'host_id', 'name', 44 | 'start_time', 'timemodified', 'recurring', 'recurrence_type', 'repeat_interval', 'weekly_days', 'monthly_day', 45 | 'monthly_week', 'monthly_week_day', 'monthly_repeat_option', 'end_times', 'end_date_time', 'end_date_option', 46 | 'webinar', 'duration', 'timezone', 'password', 'option_jbh', 'option_start_type', 'option_host_video', 47 | 'option_participants_video', 'option_audio', 'option_mute_upon_entry', 'option_waiting_room', 48 | 'option_authenticated_users', 'option_encryption_type', 'exists_on_zoom', 'alternative_hosts', 49 | 'recordings_visible_default', 'show_schedule', 'show_security', 'show_media', 'option_auto_recording', 50 | 'registration', 51 | ]); 52 | 53 | $trackingfields = new backup_nested_element('trackingfields'); 54 | 55 | $trackingfield = new backup_nested_element('trackingfield', ['id'], ['meeting_id', 'tracking_field', 'value']); 56 | 57 | // If we had more elements, we would build the tree here. 58 | $zoom->add_child($trackingfields); 59 | $trackingfields->add_child($trackingfield); 60 | 61 | // Define data sources. 62 | $zoom->set_source_table('zoom', ['id' => backup::VAR_ACTIVITYID]); 63 | $trackingfield->set_source_table('zoom_meeting_tracking_fields', ['meeting_id' => backup::VAR_ACTIVITYID]); 64 | 65 | // If we were referring to other tables, we would annotate the relation 66 | // with the element's annotate_ids() method. 67 | 68 | // Define file annotations. 69 | // Intro does not need itemid. 70 | $zoom->annotate_files('mod_zoom', 'intro', null); 71 | 72 | // Return the root element (zoom), wrapped into standard activity structure. 73 | return $this->prepare_activity_structure($zoom); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /backup/moodle2/restore_zoom_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Provides the restore activity task class 19 | * 20 | * @package mod_zoom 21 | * @category backup 22 | * @copyright 2015 UC Regents 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | require_once($CFG->dirroot . '/mod/zoom/backup/moodle2/restore_zoom_stepslib.php'); 29 | 30 | use mod_zoom\restore_activity_structure_step; 31 | 32 | /** 33 | * Restore task for the zoom activity module 34 | * 35 | * Provides all the settings and steps to perform complete restore of the activity. 36 | */ 37 | class restore_zoom_activity_task extends restore_activity_task { 38 | /** 39 | * Define (add) particular settings this activity can have 40 | */ 41 | protected function define_my_settings() { 42 | // No particular settings for this activity. 43 | } 44 | 45 | /** 46 | * Define (add) particular steps this activity can have 47 | */ 48 | protected function define_my_steps() { 49 | // We have just one structure step here. 50 | $this->add_step(new restore_activity_structure_step('zoom_structure', 'zoom.xml')); 51 | } 52 | 53 | /** 54 | * Define the contents in the activity that must be 55 | * processed by the link decoder 56 | */ 57 | public static function define_decode_contents() { 58 | $contents = []; 59 | 60 | $contents[] = new restore_decode_content('zoom', ['intro'], 'zoom'); 61 | 62 | return $contents; 63 | } 64 | 65 | /** 66 | * Define the decoding rules for links belonging 67 | * to the activity to be executed by the link decoder 68 | */ 69 | public static function define_decode_rules() { 70 | $rules = []; 71 | 72 | $rules[] = new restore_decode_rule('ZOOMVIEWBYID', '/mod/zoom/view.php?id=$1', 'course_module'); 73 | $rules[] = new restore_decode_rule('ZOOMINDEX', '/mod/zoom/index.php?id=$1', 'course_module'); 74 | 75 | return $rules; 76 | } 77 | 78 | /** 79 | * Define the restore log rules that will be applied by the 80 | * restore_logs_processor when restoring zoom logs. It must return one array 81 | * of restore_log_rule objects 82 | */ 83 | public static function define_restore_log_rules() { 84 | $rules = []; 85 | 86 | $rules[] = new restore_log_rule('zoom', 'add', 'view.php?id={course_module}', '{zoom}'); 87 | $rules[] = new restore_log_rule('zoom', 'update', 'view.php?id={course_module}', '{zoom}'); 88 | $rules[] = new restore_log_rule('zoom', 'view', 'view.php?id={course_module}', '{zoom}'); 89 | 90 | return $rules; 91 | } 92 | 93 | /** 94 | * Define the restore log rules that will be applied by the 95 | * restore_logs_processor when restoring course logs. It must return one 96 | * array of restore_log_rule objects 97 | * 98 | * Note this rules are applied when restoring course logs 99 | * by the restore final task, but are defined here at 100 | * activity level. All them are rules not linked to any module instance (cmid = 0) 101 | */ 102 | public static function define_restore_log_rules_for_course() { 103 | $rules = []; 104 | 105 | $rules[] = new restore_log_rule('zoom', 'view all', 'index.php?id={course}', null); 106 | 107 | return $rules; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /backup/moodle2/restore_zoom_stepslib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Define all the restore steps that will be used by the restore_zoom_activity_task 19 | * 20 | * @package mod_zoom 21 | * @category backup 22 | * @copyright 2015 UC Regents 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_zoom; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 31 | 32 | use moodle_exception; 33 | use restore_path_element; 34 | 35 | /** 36 | * Structure step to restore one zoom activity 37 | */ 38 | class restore_activity_structure_step extends \restore_activity_structure_step { 39 | /** 40 | * Defines structure of path elements to be processed during the restore 41 | * 42 | * @return array of restore_path_element 43 | */ 44 | protected function define_structure() { 45 | $paths = []; 46 | $paths[] = new restore_path_element('zoom', '/activity/zoom'); 47 | $paths[] = new restore_path_element('zoom_tracking_field', '/activity/zoom/trackingfields/trackingfield'); 48 | 49 | // Return the paths wrapped into standard activity structure. 50 | return $this->prepare_activity_structure($paths); 51 | } 52 | 53 | /** 54 | * Process the given restore path element data 55 | * 56 | * @param array $data parsed element data 57 | */ 58 | protected function process_zoom($data) { 59 | global $DB; 60 | 61 | $data = (object) $data; 62 | 63 | // Update start_time before attempting to create a new meeting. 64 | $data->start_time = $this->apply_date_offset($data->start_time); 65 | 66 | // Either create a new meeting or set meeting as expired. 67 | try { 68 | // FIXME: Do we provide course context? That won't have the right activity names etc. 69 | $cmid = null; 70 | $updateddata = zoom_webservice()->create_meeting($data, $cmid); 71 | $data = populate_zoom_from_response($data, $updateddata); 72 | $data->exists_on_zoom = ZOOM_MEETING_EXISTS; 73 | } catch (moodle_exception $e) { 74 | $data->join_url = ''; 75 | $data->meeting_id = 0; 76 | $data->exists_on_zoom = ZOOM_MEETING_EXPIRED; 77 | } 78 | 79 | $data->course = $this->get_courseid(); 80 | 81 | if (empty($data->timemodified)) { 82 | $data->timemodified = time(); 83 | } 84 | 85 | if ($data->grade < 0) { 86 | // Scale found, get mapping. 87 | $data->grade = -($this->get_mappingid('scale', abs($data->grade))); 88 | } 89 | 90 | // Create the zoom instance. 91 | $newitemid = $DB->insert_record('zoom', $data); 92 | $this->apply_activity_instance($newitemid); 93 | 94 | // Create the calendar events for the new meeting. 95 | $data->id = $newitemid; 96 | zoom_calendar_item_update($data); 97 | } 98 | 99 | /** 100 | * Process the zoom tracking fields. 101 | * 102 | * @param array $data 103 | */ 104 | protected function process_zoom_tracking_field($data) { 105 | global $DB; 106 | 107 | $data = (object) $data; 108 | $oldid = $data->id; 109 | 110 | $data->meeting_id = $this->get_new_parentid('zoom'); 111 | 112 | $defaulttrackingfields = zoom_clean_tracking_fields(); 113 | 114 | if (isset($defaulttrackingfields[$data->tracking_field])) { 115 | $newitemid = $DB->insert_record('zoom_meeting_tracking_fields', $data); 116 | $this->set_mapping('zoom_tracking_field', $oldid, $newitemid); 117 | } 118 | } 119 | 120 | /** 121 | * Post-execution actions 122 | */ 123 | protected function after_execute() { 124 | // Add zoom related files, no need to match by itemname (just internally handled context). 125 | $this->add_related_files('mod_zoom', 'intro', null); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /classes/analytics/indicator/activity_base.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Activity base class. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 Catalyst IT 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\analytics\indicator; 26 | 27 | use core_analytics\local\indicator\community_of_inquiry_activity; 28 | 29 | /** 30 | * Activity base class. 31 | */ 32 | abstract class activity_base extends community_of_inquiry_activity { 33 | /** 34 | * Grading not implemented. 35 | * 36 | * @return bool 37 | */ 38 | public function feedback_check_grades() { 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /classes/analytics/indicator/cognitive_depth.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Cognitive depth indicator - zoom. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 Catalyst IT 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\analytics\indicator; 26 | 27 | use cm_info; 28 | use lang_string; 29 | 30 | /** 31 | * Cognitive depth indicator - zoom. 32 | */ 33 | class cognitive_depth extends activity_base { 34 | /** 35 | * Returns the name. 36 | * 37 | * @return object 38 | */ 39 | public static function get_name(): lang_string { 40 | return new lang_string('indicator:cognitivedepth', 'mod_zoom'); 41 | } 42 | 43 | /** 44 | * Returns the indicator type. 45 | * 46 | * @return integer 47 | */ 48 | public function get_indicator_type() { 49 | return self::INDICATOR_COGNITIVE; 50 | } 51 | 52 | /** 53 | * Returns the cognitive depth level. 54 | * 55 | * @param cm_info $cm 56 | * 57 | * @return integer 58 | */ 59 | public function get_cognitive_depth_level(cm_info $cm) { 60 | return self::COGNITIVE_LEVEL_1; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /classes/analytics/indicator/social_breadth.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Social breadth indicator. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 Catalyst IT 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\analytics\indicator; 26 | 27 | use cm_info; 28 | use lang_string; 29 | 30 | /** 31 | * Social breadth indicator. 32 | */ 33 | class social_breadth extends activity_base { 34 | /** 35 | * Returns the name. 36 | * 37 | * If there is a corresponding '_help' string this will be shown as well. 38 | * 39 | * @return object 40 | */ 41 | public static function get_name(): lang_string { 42 | return new lang_string('indicator:socialbreadth', 'mod_zoom'); 43 | } 44 | 45 | /** 46 | * Returns the indicator type. 47 | * 48 | * @return integer 49 | */ 50 | public function get_indicator_type() { 51 | return self::INDICATOR_SOCIAL; 52 | } 53 | 54 | /** 55 | * Returns the social breadth level. 56 | * 57 | * @param cm_info $cm 58 | * 59 | * @return integer 60 | */ 61 | public function get_social_breadth_level(cm_info $cm) { 62 | return self::SOCIAL_LEVEL_2; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /classes/api_limit_exception.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception class for Zoom API errors. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2023 Jonathan Champ 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | use stdClass; 28 | 29 | /** 30 | * Exceeded daily API limit. 31 | */ 32 | class api_limit_exception extends webservice_exception { 33 | /** 34 | * Unix timestamp of next time to API can be called. 35 | * @var int 36 | */ 37 | public $retryafter = null; 38 | 39 | /** 40 | * Constructor 41 | * @param string $response Web service response 42 | * @param int $errorcode Web service response error code 43 | * @param int $retryafter Unix timestamp of next time to API can be called. 44 | */ 45 | public function __construct($response, $errorcode, $retryafter) { 46 | $this->retryafter = $retryafter; 47 | 48 | $a = new stdClass(); 49 | $a->response = $response; 50 | parent::__construct( 51 | $response, 52 | $errorcode, 53 | 'zoomerr_apilimit', 54 | 'mod_zoom', 55 | '', 56 | userdate($retryafter, get_string('strftimedaydatetime', 'core_langconfig')) 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /classes/bad_request_exception.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception class for Zoom API errors. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2023 Jonathan Champ 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | /** 28 | * Bad request received by Zoom. 29 | */ 30 | class bad_request_exception extends webservice_exception { 31 | /** 32 | * Constructor 33 | * @param string $response Web service response message 34 | * @param int $errorcode Web service response error code 35 | */ 36 | public function __construct($response, $errorcode) { 37 | parent::__construct($response, $errorcode, 'errorwebservice_badrequest', 'mod_zoom', '', $response); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /classes/dates.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Contains the class for fetching the important dates in mod_zoom for a given module instance and a user. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2021 Shamim Rezaie 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | declare(strict_types=1); 26 | 27 | namespace mod_zoom; 28 | 29 | use core\activity_dates; 30 | 31 | /** 32 | * Class for fetching the important dates in mod_zoom for a given module instance and a user. 33 | * 34 | * @copyright 2021 Shamim Rezaie 35 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 | */ 37 | class dates extends activity_dates { 38 | /** 39 | * Returns a list of important dates in mod_zoom 40 | * 41 | * @return array 42 | */ 43 | protected function get_dates(): array { 44 | $starttime = $this->cm->customdata['start_time'] ?? null; 45 | $duration = $this->cm->customdata['duration'] ?? null; 46 | $recurring = $this->cm->customdata['recurring'] ?? null; 47 | $recurrencetype = $this->cm->customdata['recurrence_type'] ?? null; 48 | 49 | // For meeting with no fixed time, no time info needed on course page. 50 | if ($recurring && $recurrencetype == \ZOOM_RECURRINGTYPE_NOTIME) { 51 | return []; 52 | } 53 | 54 | $dates = []; 55 | 56 | if ($starttime) { 57 | $now = time(); 58 | if ($duration && $starttime + $duration < $now) { 59 | // Meeting has ended. 60 | $dataid = 'end_date_time'; 61 | $labelid = 'activitydate:ended'; 62 | $meetimgtimestamp = $starttime + $duration; 63 | } else { 64 | // Meeting hasn't started / in progress. 65 | $dataid = 'start_time'; 66 | $labelid = $starttime > $now ? 'activitydate:starts' : 'activitydate:started'; 67 | $meetimgtimestamp = $starttime; 68 | } 69 | 70 | $dates[] = [ 71 | 'dataid' => $dataid, 72 | 'label' => get_string($labelid, 'mod_zoom'), 73 | 'timestamp' => $meetimgtimestamp, 74 | ]; 75 | } 76 | 77 | return $dates; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /classes/event/course_module_instance_list_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The mod_zoom instance list viewed event. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\event; 26 | 27 | /** 28 | * The mod_zoom instance list viewed event class. 29 | */ 30 | class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { 31 | } 32 | -------------------------------------------------------------------------------- /classes/event/course_module_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines the view event. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\event; 26 | 27 | /** 28 | * The mod_zoom instance list viewed event class 29 | * 30 | * If the view mode needs to be stored as well, you may need to 31 | * override methods get_url() and get_legacy_log_data(), too. 32 | */ 33 | class course_module_viewed extends \core\event\course_module_viewed { 34 | /** 35 | * Initialize the event 36 | */ 37 | protected function init() { 38 | $this->data['objecttable'] = 'zoom'; 39 | parent::init(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /classes/event/join_meeting_button_clicked.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Contains the event class for when a user clicks a 'Join Meeting' button. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\event; 26 | 27 | use coding_exception; 28 | use moodle_url; 29 | 30 | /** 31 | * Records when a join meeting button is clicked. 32 | */ 33 | class join_meeting_button_clicked extends \core\event\base { 34 | /** 35 | * Initializes the event. 36 | */ 37 | protected function init() { 38 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 39 | $this->data['crud'] = 'r'; 40 | $this->data['objecttable'] = 'zoom'; 41 | } 42 | 43 | /** 44 | * Validates arguments. 45 | */ 46 | protected function validate_data() { 47 | $fieldstovalidate = ['cmid' => "integer", 'meetingid' => "integer", 'userishost' => "boolean"]; 48 | foreach ($fieldstovalidate as $field => $shouldbe) { 49 | if (is_null($this->other[$field])) { 50 | throw new coding_exception("The $field value must be set in other."); 51 | } else if (gettype($this->other[$field]) != $shouldbe) { 52 | throw new coding_exception("The $field value must be an $shouldbe."); 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * Returns the name of the event. 59 | * 60 | * @return string 61 | */ 62 | public static function get_name() { 63 | return get_string('clickjoin', 'mod_zoom'); 64 | } 65 | 66 | /** 67 | * Returns a short description for the event. 68 | * 69 | * @return string 70 | */ 71 | public function get_description() { 72 | return "User '$this->userid' " . ($this->other['userishost'] ? 'started' : 'joined') . " meeting with meeting_id '" . 73 | $this->other['meetingid'] . "' in course '$this->courseid'"; 74 | } 75 | 76 | /** 77 | * Returns URL to meeting view page. 78 | * 79 | * @return moodle_url 80 | */ 81 | public function get_url() { 82 | return new moodle_url('/mod/zoom/view.php', ['id' => $this->other['cmid']]); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /classes/external.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Zoom external API 19 | * 20 | * @package mod_zoom 21 | * @category external 22 | * @author Nick Stefanski 23 | * @copyright 2017 Auguste Escoffier School of Culinary Arts {@link https://www.escoffier.edu} 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | * @since Moodle 3.1 26 | */ 27 | 28 | namespace mod_zoom; 29 | 30 | defined('MOODLE_INTERNAL') || die; 31 | 32 | require_once("$CFG->libdir/externallib.php"); 33 | 34 | use context_module; 35 | use external_api; 36 | use external_function_parameters; 37 | use external_single_structure; 38 | use external_value; 39 | use external_warnings; 40 | use invalid_response_exception; 41 | 42 | /** 43 | * Zoom external functions 44 | */ 45 | class external extends external_api { 46 | /** 47 | * Returns description of method parameters 48 | * 49 | * @return external_function_parameters 50 | * @since Moodle 3.1 51 | */ 52 | public static function get_state_parameters() { 53 | return new external_function_parameters( 54 | [ 55 | 'zoomid' => new external_value(PARAM_INT, 'zoom course module id'), 56 | ] 57 | ); 58 | } 59 | 60 | /** 61 | * Determine if a zoom meeting is available, meeting status, and the start time, duration, and other meeting options. 62 | * This function grabs most of the options to display for users in /mod/zoom/view.php 63 | * Host functions are not currently supported 64 | * 65 | * @param int $zoomid the zoom course module id 66 | * @return array of warnings and status result 67 | * @since Moodle 3.1 68 | * @throws moodle_exception 69 | */ 70 | public static function get_state($zoomid) { 71 | global $DB, $CFG; 72 | require_once($CFG->dirroot . "/mod/zoom/locallib.php"); 73 | 74 | $params = self::validate_parameters( 75 | self::get_state_parameters(), 76 | [ 77 | 'zoomid' => $zoomid, 78 | ] 79 | ); 80 | $warnings = []; 81 | 82 | // Request and permission validation. 83 | $cm = $DB->get_record('course_modules', ['id' => $params['zoomid']], '*', MUST_EXIST); 84 | $zoom = $DB->get_record('zoom', ['id' => $cm->instance], '*', MUST_EXIST); 85 | 86 | $context = context_module::instance($cm->id); 87 | self::validate_context($context); 88 | 89 | require_capability('mod/zoom:view', $context); 90 | 91 | // Call the zoom/locallib API. 92 | [$inprogress, $available, $finished] = zoom_get_state($zoom); 93 | 94 | $result = []; 95 | $result['available'] = $available; 96 | 97 | if ($zoom->recurring) { 98 | $result['start_time'] = 0; 99 | $result['duration'] = 0; 100 | } else { 101 | $result['start_time'] = $zoom->start_time; 102 | $result['duration'] = $zoom->duration; 103 | } 104 | 105 | $result['haspassword'] = (isset($zoom->password) && $zoom->password !== ''); 106 | $result['joinbeforehost'] = $zoom->option_jbh; 107 | $result['startvideohost'] = $zoom->option_host_video; 108 | $result['startvideopart'] = $zoom->option_participants_video; 109 | $result['audioopt'] = $zoom->option_audio; 110 | 111 | if (!$zoom->recurring) { 112 | if ($zoom->exists_on_zoom == ZOOM_MEETING_EXPIRED) { 113 | $status = get_string('meeting_nonexistent_on_zoom', 'mod_zoom'); 114 | } else if ($finished) { 115 | $status = get_string('meeting_finished', 'mod_zoom'); 116 | } else if ($inprogress) { 117 | $status = get_string('meeting_started', 'mod_zoom'); 118 | } else { 119 | $status = get_string('meeting_not_started', 'mod_zoom'); 120 | } 121 | } else { 122 | $status = get_string('recurringmeetinglong', 'mod_zoom'); 123 | } 124 | 125 | $result['status'] = $status; 126 | 127 | $result['warnings'] = $warnings; 128 | return $result; 129 | } 130 | 131 | /** 132 | * Returns description of method result value 133 | * 134 | * @return external_description 135 | * @since Moodle 3.1 136 | */ 137 | public static function get_state_returns() { 138 | return new external_single_structure( 139 | [ 140 | 'available' => new external_value(PARAM_BOOL, 'if true, run grade_item_update and redirect to meeting url'), 141 | 142 | 'start_time' => new external_value(PARAM_INT, 'meeting start time as unix timestamp (0 if recurring)'), 143 | 'duration' => new external_value(PARAM_INT, 'meeting duration in seconds (0 if recurring)'), 144 | 145 | 'haspassword' => new external_value(PARAM_BOOL, ''), 146 | 'joinbeforehost' => new external_value(PARAM_BOOL, ''), 147 | 'startvideohost' => new external_value(PARAM_BOOL, ''), 148 | 'startvideopart' => new external_value(PARAM_BOOL, ''), 149 | 'audioopt' => new external_value(PARAM_TEXT, ''), 150 | 151 | 'status' => new external_value(PARAM_TEXT, 'meeting status: not_started, started, finished, expired, recurring'), 152 | 153 | 'warnings' => new external_warnings(), 154 | ] 155 | ); 156 | } 157 | 158 | /** 159 | * Returns description of method parameters 160 | * 161 | * @return external_function_parameters 162 | * @since Moodle 3.1 163 | */ 164 | public static function grade_item_update_parameters() { 165 | return new external_function_parameters( 166 | [ 167 | 'zoomid' => new external_value(PARAM_INT, 'zoom course module id'), 168 | ] 169 | ); 170 | } 171 | 172 | /** 173 | * Creates or updates grade item for the given zoom instance and returns join url. 174 | * This function grabs most of the options to display for users in /mod/zoom/view.php 175 | * 176 | * @param int $zoomid the zoom course module id 177 | * @return array of warnings and status result 178 | * @since Moodle 3.1 179 | * @throws moodle_exception 180 | */ 181 | public static function grade_item_update($zoomid) { 182 | global $CFG; 183 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 184 | 185 | $params = self::validate_parameters( 186 | self::get_state_parameters(), 187 | [ 188 | 'zoomid' => $zoomid, 189 | ] 190 | ); 191 | $warnings = []; 192 | 193 | $context = context_module::instance($params['zoomid']); 194 | self::validate_context($context); 195 | 196 | // Call load meeting function, do not use start url on mobile. 197 | $meetinginfo = zoom_load_meeting($params['zoomid'], $context, $usestarturl = false); 198 | 199 | // Pass url to join zoom meeting in order to redirect user. 200 | $result = []; 201 | if ($meetinginfo['nexturl']) { 202 | $result['status'] = true; 203 | $result['joinurl'] = $meetinginfo['nexturl']->__toString(); 204 | } else { 205 | $warningmsg = clean_param($meetinginfo['error'], PARAM_TEXT); 206 | throw new invalid_response_exception($warningmsg); 207 | } 208 | 209 | $result['warnings'] = $warnings; 210 | return $result; 211 | } 212 | 213 | /** 214 | * Returns description of method result value 215 | * 216 | * @return external_description 217 | * @since Moodle 3.1 218 | */ 219 | public static function grade_item_update_returns() { 220 | return new external_single_structure( 221 | [ 222 | 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 223 | 'joinurl' => new external_value(PARAM_RAW, 'Zoom meeting join url'), 224 | 'warnings' => new external_warnings(), 225 | ] 226 | ); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /classes/not_found_exception.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception class for Zoom API errors. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2023 Jonathan Champ 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | /** 28 | * Entry not found on Zoom. 29 | */ 30 | class not_found_exception extends webservice_exception { 31 | /** 32 | * Constructor 33 | * @param string $response Web service response message 34 | * @param int $errorcode Web service response error code 35 | */ 36 | public function __construct($response, $errorcode) { 37 | parent::__construct($response, $errorcode, 'errorwebservice_notfound', 'mod_zoom', '', $response); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /classes/output/mobile.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Mobile support for zoom. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2018 Nick Stefanski 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\output; 26 | 27 | use context_module; 28 | use mod_zoom\external; 29 | 30 | /** 31 | * Mobile output class for zoom 32 | */ 33 | class mobile { 34 | /** 35 | * Returns the zoom course view for the mobile app, 36 | * including meeting details and launch button (if applicable). 37 | * @param array $args Arguments from tool_mobile_get_content WS 38 | * 39 | * @return array HTML, javascript and otherdata 40 | */ 41 | public static function mobile_course_view($args) { 42 | global $OUTPUT, $DB; 43 | 44 | $args = (object) $args; 45 | $versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3'; 46 | $cm = get_coursemodule_from_id('zoom', $args->cmid); 47 | 48 | // Capabilities check. 49 | require_login($args->courseid, false, $cm, true, true); 50 | 51 | $context = context_module::instance($cm->id); 52 | 53 | require_capability('mod/zoom:view', $context); 54 | // Right now we're just implementing basic viewing, otherwise we may 55 | // need to check other capabilities. 56 | $zoom = $DB->get_record('zoom', ['id' => $cm->instance]); 57 | 58 | // WS to get zoom state. 59 | try { 60 | $zoomstate = external::get_state($cm->id); 61 | } catch (\Exception $e) { 62 | $zoomstate = []; 63 | } 64 | 65 | // Format date and time. 66 | $starttime = userdate($zoom->start_time); 67 | $duration = format_time($zoom->duration); 68 | 69 | // Get audio option string. 70 | $optionaudio = get_string('audio_' . $zoom->option_audio, 'mod_zoom'); 71 | 72 | $data = [ 73 | 'zoom' => $zoom, 74 | 'available' => $zoomstate['available'], 75 | 'status' => $zoomstate['status'], 76 | 'start_time' => $starttime, 77 | 'duration' => $duration, 78 | 'option_audio' => $optionaudio, 79 | 'cmid' => $cm->id, 80 | 'courseid' => $args->courseid, 81 | 'canusemoduleinfo' => $args->appversioncode >= 44000, 82 | ]; 83 | 84 | return [ 85 | 'templates' => [ 86 | [ 87 | 'id' => 'main', 88 | 'html' => $OUTPUT->render_from_template("mod_zoom/mobile_view_page_$versionname", $data), 89 | ], 90 | ], 91 | 'javascript' => "this.loadMeeting = function(result) { window.open(result.joinurl, '_system'); };", 92 | // This JS will redirect to a joinurl passed by the mod_zoom_grade_item_update WS. 93 | 'otherdata' => '', 94 | 'files' => '', 95 | ]; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /classes/retry_failed_exception.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception class for Zoom API errors. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2023 Jonathan Champ 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | use stdClass; 28 | 29 | /** 30 | * Couldn't succeed within the allowed number of retries. 31 | */ 32 | class retry_failed_exception extends webservice_exception { 33 | /** 34 | * Constructor 35 | * @param string $response Web service response 36 | * @param int $errorcode Web service response error code 37 | */ 38 | public function __construct($response, $errorcode) { 39 | $a = new stdClass(); 40 | $a->response = $response; 41 | $a->maxretries = webservice::MAX_RETRIES; 42 | parent::__construct($response, $errorcode, 'zoomerr_maxretries', 'mod_zoom', '', $a); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /classes/search/activity.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Search area for mod_zoom activities. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2019 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\search; 26 | 27 | use core_search\base_activity; 28 | 29 | /** 30 | * Search area for mod_zoom activities. 31 | */ 32 | class activity extends base_activity { 33 | /** 34 | * Returns true if this area uses file indexing. 35 | * 36 | * @return bool 37 | */ 38 | public function uses_file_indexing() { 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /classes/task/delete_meeting_recordings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The task for deleting recordings in Moodle if removed from Zoom. 19 | * 20 | * @package mod_zoom 21 | * @author Jwalit Shah 22 | * @copyright 2021 Jwalit Shah 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_zoom\task; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 31 | 32 | use core\task\scheduled_task; 33 | use moodle_exception; 34 | 35 | /** 36 | * Scheduled task to delete meeting recordings from Moodle. 37 | */ 38 | class delete_meeting_recordings extends scheduled_task { 39 | /** 40 | * Returns name of task. 41 | * 42 | * @return string 43 | */ 44 | public function get_name() { 45 | return get_string('deletemeetingrecordings', 'mod_zoom'); 46 | } 47 | 48 | /** 49 | * Delete any recordings that have been removed from zoom. 50 | * 51 | * @return void 52 | */ 53 | public function execute() { 54 | global $DB; 55 | 56 | try { 57 | $service = zoom_webservice(); 58 | } catch (moodle_exception $exception) { 59 | mtrace('Skipping task - ', $exception->getMessage()); 60 | return; 61 | } 62 | 63 | // Required scopes for deleting meeting recordings. 64 | $requiredscopes = [ 65 | 'classic' => [ 66 | 'recording:read:admin', 67 | ], 68 | 'granular' => [ 69 | 'cloud_recording:read:list_recording_files:admin', 70 | ], 71 | ]; 72 | 73 | // Checking for missing scopes. 74 | $missingscopes = $service->check_scopes($requiredscopes); 75 | if (!empty($missingscopes)) { 76 | foreach ($missingscopes as $missingscope) { 77 | mtrace('Missing scope: ' . $missingscope); 78 | } 79 | return; 80 | } 81 | 82 | // See if we cannot make anymore API calls. 83 | $retryafter = get_config('zoom', 'retry-after'); 84 | if (!empty($retryafter) && time() < $retryafter) { 85 | mtrace('Out of API calls, retry after ' . userdate($retryafter, get_string('strftimedaydatetime', 'core_langconfig'))); 86 | return; 87 | } 88 | 89 | mtrace('Checking if any meeting recordings in Moodle have been removed from Zoom...'); 90 | 91 | // Get all recordings stored in Moodle, grouped by meetinguuid. 92 | $zoomrecordings = zoom_get_meeting_recordings_grouped(); 93 | foreach ($zoomrecordings as $meetinguuid => $recordings) { 94 | try { 95 | // Now check which recordings still exist on Zoom. 96 | $recordinglist = $service->get_recording_url_list($meetinguuid); 97 | foreach ($recordinglist as $recordinginfo) { 98 | $zoomrecordingid = trim($recordinginfo->recordingid); 99 | if (isset($recordings[$zoomrecordingid])) { 100 | mtrace('Recording id: ' . $zoomrecordingid . ' exist(s)...skipping'); 101 | unset($recordings[$zoomrecordingid]); 102 | } 103 | } 104 | 105 | // If recordings are in Moodle but not in Zoom, we need to remove them from Moodle as well. 106 | foreach ($recordings as $zoomrecordingid => $recording) { 107 | mtrace('Deleting recording with id: ' . $zoomrecordingid . ' because the recording is no longer in Zoom.'); 108 | $DB->delete_records('zoom_meeting_recordings', ['zoomrecordingid' => $zoomrecordingid]); 109 | } 110 | } catch (moodle_exception $e) { 111 | mtrace('Exception occurred: ' . $e->getMessage()); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /classes/task/get_meeting_recordings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The task for getting recordings from Zoom to Moodle. 19 | * 20 | * @package mod_zoom 21 | * @author Jwalit Shah 22 | * @copyright 2021 Jwalit Shah 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_zoom\task; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 31 | 32 | use core\task\scheduled_task; 33 | use moodle_exception; 34 | use stdClass; 35 | 36 | /** 37 | * Scheduled task to get the meeting recordings. 38 | */ 39 | class get_meeting_recordings extends scheduled_task { 40 | /** 41 | * Returns name of task. 42 | * 43 | * @return string 44 | */ 45 | public function get_name() { 46 | return get_string('getmeetingrecordings', 'mod_zoom'); 47 | } 48 | 49 | /** 50 | * Get any new recordings that have been added on zoom. 51 | * 52 | * @return void 53 | */ 54 | public function execute() { 55 | global $DB; 56 | 57 | try { 58 | $service = zoom_webservice(); 59 | } catch (moodle_exception $exception) { 60 | mtrace('Skipping task - ', $exception->getMessage()); 61 | return; 62 | } 63 | 64 | $config = get_config('zoom'); 65 | if (empty($config->viewrecordings)) { 66 | mtrace('Skipping task - ', get_string('zoomerr_viewrecordings_off', 'zoom')); 67 | return; 68 | } 69 | 70 | // Required scopes for meeting recordings. 71 | $requiredscopes = [ 72 | 'classic' => [ 73 | 'recording:read:admin', 74 | ], 75 | 'granular' => [ 76 | 'cloud_recording:read:list_user_recordings:admin', 77 | 'cloud_recording:read:recording_settings:admin', 78 | ], 79 | ]; 80 | 81 | // Checking for missing scopes. 82 | $missingscopes = $service->check_scopes($requiredscopes); 83 | if (!empty($missingscopes)) { 84 | foreach ($missingscopes as $missingscope) { 85 | mtrace('Missing scope: ' . $missingscope); 86 | } 87 | return; 88 | } 89 | 90 | // See if we cannot make anymore API calls. 91 | $retryafter = get_config('zoom', 'retry-after'); 92 | if (!empty($retryafter) && time() < $retryafter) { 93 | mtrace('Out of API calls, retry after ' . userdate($retryafter, get_string('strftimedaydatetime', 'core_langconfig'))); 94 | return; 95 | } 96 | 97 | mtrace('Finding meeting recordings for this account...'); 98 | 99 | $localmeetings = zoom_get_all_meeting_records(); 100 | 101 | $now = time(); 102 | $from = gmdate('Y-m-d', strtotime('-1 day', $now)); 103 | $to = gmdate('Y-m-d', strtotime('+1 day', $now)); 104 | 105 | $hostmeetings = []; 106 | 107 | foreach ($localmeetings as $zoom) { 108 | // Only get recordings for this meeting if its recurring or already finished. 109 | if ($zoom->recurring || $now > (intval($zoom->start_time) + intval($zoom->duration))) { 110 | $hostmeetings[$zoom->host_id][$zoom->meeting_id] = $zoom; 111 | } 112 | } 113 | 114 | if (empty($hostmeetings)) { 115 | mtrace('No meetings need to be processed.'); 116 | return; 117 | } 118 | 119 | $meetingpasscodes = []; 120 | $localrecordings = zoom_get_meeting_recordings_grouped(); 121 | 122 | foreach ($hostmeetings as $hostid => $meetings) { 123 | // Fetch all recordings for this user. 124 | $zoomrecordings = $service->get_user_recordings($hostid, $from, $to); 125 | 126 | foreach ($zoomrecordings as $recordingid => $recording) { 127 | if (isset($localrecordings[$recording->meetinguuid][$recordingid])) { 128 | mtrace('Recording id: ' . $recordingid . ' exists...skipping'); 129 | $localrecording = $localrecordings[$recording->meetinguuid][$recordingid]; 130 | 131 | if ($localrecording->recordingtype !== $recording->recordingtype) { 132 | $updatemeeting = (object) [ 133 | 'id' => $localrecording->id, 134 | 'recordingtype' => $recording->recordingtype, 135 | ]; 136 | $DB->update_record('zoom_meeting_recordings', $updatemeeting); 137 | } 138 | continue; 139 | } 140 | 141 | if (empty($meetings[$recording->meetingid])) { 142 | // Skip meetings that are not in Moodle. 143 | mtrace('Meeting id: ' . $recording->meetingid . ' does not exist...skipping'); 144 | continue; 145 | } 146 | 147 | // As of 2023-09-24, 'password' is not present in the user recordings API response. 148 | if (empty($meetingpasscodes[$recording->meetinguuid])) { 149 | try { 150 | $settings = $service->get_recording_settings($recording->meetinguuid); 151 | $meetingpasscodes[$recording->meetinguuid] = $settings->password; 152 | } catch (moodle_exception $error) { 153 | continue; 154 | } 155 | } 156 | 157 | $zoom = $meetings[$recording->meetingid]; 158 | $recordingtype = $recording->recordingtype; 159 | 160 | $record = new stdClass(); 161 | $record->zoomid = $zoom->id; 162 | $record->meetinguuid = $recording->meetinguuid; 163 | $record->zoomrecordingid = $recordingid; 164 | $record->name = $zoom->name; 165 | $record->externalurl = $recording->url; 166 | $record->passcode = $meetingpasscodes[$recording->meetinguuid]; 167 | $record->recordingtype = $recordingtype; 168 | $record->recordingstart = $recording->recordingstart; 169 | $record->showrecording = $zoom->recordings_visible_default; 170 | $record->timecreated = $now; 171 | $record->timemodified = $now; 172 | 173 | $record->id = $DB->insert_record('zoom_meeting_recordings', $record); 174 | mtrace('Recording id: ' . $recordingid . ' (' . $recordingtype . ') added to the database'); 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /classes/task/update_meetings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Task: update_meetings 19 | * 20 | * @package mod_zoom 21 | * @copyright 2018 UC Regents 22 | * @author Rohan Khajuria 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_zoom\task; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | require_once($CFG->libdir . '/modinfolib.php'); 31 | require_once($CFG->dirroot . '/mod/zoom/lib.php'); 32 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 33 | 34 | use core\task\scheduled_task; 35 | use mod_zoom\not_found_exception; 36 | use moodle_exception; 37 | use moodle_url; 38 | 39 | /** 40 | * Scheduled task to sychronize meeting data. 41 | */ 42 | class update_meetings extends scheduled_task { 43 | /** 44 | * Returns name of task. 45 | * 46 | * @return string 47 | */ 48 | public function get_name() { 49 | return get_string('updatemeetings', 'mod_zoom'); 50 | } 51 | 52 | /** 53 | * Updates meetings that are not expired. 54 | * 55 | * @return boolean 56 | */ 57 | public function execute() { 58 | global $DB; 59 | 60 | try { 61 | $service = zoom_webservice(); 62 | } catch (moodle_exception $exception) { 63 | mtrace('Skipping task - ', $exception->getMessage()); 64 | return; 65 | } 66 | 67 | // Required scopes for reading meeting information. 68 | $requiredscopes = [ 69 | 'classic' => [ 70 | 'meeting:read:admin', 71 | ], 72 | 'granular' => [ 73 | 'meeting:read:meeting:admin', 74 | ], 75 | ]; 76 | 77 | // Checking for missing scopes. 78 | $missingmeetingscopes = $service->check_scopes($requiredscopes); 79 | foreach ($missingmeetingscopes as $missingscope) { 80 | mtrace('Missing scope: ' . $missingscope); 81 | } 82 | 83 | // Required scopes for reading webinar information. 84 | $requiredscopes = [ 85 | 'classic' => [ 86 | 'webinar:read:admin', 87 | ], 88 | 'granular' => [ 89 | 'webinar:read:webinar:admin', 90 | ], 91 | ]; 92 | 93 | // Checking for missing scopes. 94 | $missingwebinarscopes = $service->check_scopes($requiredscopes); 95 | foreach ($missingwebinarscopes as $missingscope) { 96 | mtrace('Missing scope: ' . $missingscope); 97 | } 98 | 99 | // Exit if we have neither meeting scopes nor webinar scopes. 100 | if (!empty($missingmeetingscopes) && !empty($missingwebinarscopes)) { 101 | return; 102 | } 103 | 104 | // Show trace message. 105 | mtrace('Starting to process existing Zoom meeting activities ...'); 106 | 107 | // Check all meetings, in case they were deleted/changed on Zoom. 108 | $zoomstoupdate = $DB->get_records('zoom', ['exists_on_zoom' => ZOOM_MEETING_EXISTS]); 109 | $courseidstoupdate = []; 110 | $calendarfields = ['intro', 'introformat', 'start_time', 'duration', 'recurring']; 111 | 112 | foreach ($zoomstoupdate as $zoom) { 113 | // Show trace message. 114 | mtrace('Processing next Zoom meeting activity ...'); 115 | mtrace(' Zoom meeting ID: ' . $zoom->meeting_id); 116 | mtrace(' Zoom meeting title: ' . $zoom->name); 117 | $zoomactivityurl = new moodle_url('/mod/zoom/view.php', ['n' => $zoom->id]); 118 | mtrace(' Zoom meeting activity URL: ' . $zoomactivityurl->out()); 119 | mtrace(' Moodle course ID: ' . $zoom->course); 120 | 121 | $gotinfo = false; 122 | try { 123 | $response = $service->get_meeting_webinar_info($zoom->meeting_id, $zoom->webinar); 124 | $gotinfo = true; 125 | } catch (not_found_exception $error) { 126 | $zoom->exists_on_zoom = ZOOM_MEETING_EXPIRED; 127 | $DB->update_record('zoom', $zoom); 128 | 129 | // Show trace message. 130 | mtrace(' => Marked Zoom meeting activity for Zoom meeting ID ' . $zoom->meeting_id . 131 | ' as not existing anymore on Zoom'); 132 | } catch (moodle_exception $error) { 133 | // Show trace message. 134 | mtrace(' !! Error updating Zoom meeting activity for Zoom meeting ID ' . $zoom->meeting_id . ': ' . $error); 135 | } 136 | 137 | if ($gotinfo) { 138 | $changed = false; 139 | $newzoom = populate_zoom_from_response($zoom, $response); 140 | 141 | // Iterate over all Zoom meeting fields. 142 | foreach ((array) $zoom as $field => $value) { 143 | // The start_url has a parameter that always changes, so it doesn't really count as a change. 144 | // Similarly, the timemodified parameter does not count as change if nothing else has changed. 145 | if ($field === 'start_url' || $field === 'timemodified') { 146 | continue; 147 | } 148 | 149 | // For doing a better comparison and for easing mtrace() output, convert booleans from the Zoom response 150 | // to strings like they are stored in the Moodle database for the existing activity. 151 | $newfieldvalue = $newzoom->$field; 152 | if (is_bool($newfieldvalue)) { 153 | $newfieldvalue = $newfieldvalue ? '1' : '0'; 154 | } 155 | 156 | // If the field value has changed. 157 | if ($newfieldvalue != $value) { 158 | // Show trace message. 159 | mtrace(' => Field "' . $field . '" has changed from "' . $value . '" to "' . $newfieldvalue . '"'); 160 | 161 | // Remember this meeting as changed. 162 | $changed = true; 163 | } 164 | } 165 | 166 | if ($changed) { 167 | $newzoom->timemodified = time(); 168 | $DB->update_record('zoom', $newzoom); 169 | 170 | // Show trace message. 171 | mtrace(' => Updated Zoom meeting activity for Zoom meeting ID ' . $zoom->meeting_id); 172 | 173 | // If the topic/title was changed, mark this course for cache clearing. 174 | if ($zoom->name != $newzoom->name) { 175 | $courseidstoupdate[] = $newzoom->course; 176 | } 177 | } else { 178 | // Show trace message. 179 | mtrace(' => Skipped Zoom meeting activity for Zoom meeting ID ' . $zoom->meeting_id . ' as unchanged'); 180 | } 181 | 182 | // Update the calendar events. 183 | if (!$zoom->recurring && $changed) { 184 | // Check if calendar needs updating. 185 | foreach ($calendarfields as $field) { 186 | if ($zoom->$field != $newzoom->$field) { 187 | zoom_calendar_item_update($newzoom); 188 | 189 | // Show trace message. 190 | mtrace(' => Updated calendar item for Zoom meeting ID ' . $zoom->meeting_id); 191 | 192 | break; 193 | } 194 | } 195 | } else if ($zoom->recurring) { 196 | // Show trace message. 197 | mtrace(' => Updated calendar items for recurring Zoom meeting ID ' . $zoom->meeting_id); 198 | zoom_calendar_item_update($newzoom); 199 | } 200 | 201 | // Update tracking fields for meeting. 202 | mtrace(' => Updated tracking fields for Zoom meeting ID ' . $zoom->meeting_id); 203 | zoom_sync_meeting_tracking_fields($zoom->id, $response->tracking_fields ?? []); 204 | } 205 | } 206 | 207 | // Show trace message. 208 | mtrace('Finished to process existing Zoom meetings'); 209 | 210 | // Show trace message. 211 | mtrace('Starting to rebuild course caches ...'); 212 | 213 | // Clear caches for meetings whose topic/title changed (and rebuild as needed). 214 | foreach ($courseidstoupdate as $courseid) { 215 | rebuild_course_cache($courseid, true); 216 | } 217 | 218 | // Show trace message. 219 | mtrace('Finished to rebuild course caches'); 220 | 221 | return true; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /classes/task/update_tracking_fields.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Scheduled task for updating Zoom tracking fields 19 | * 20 | * @package mod_zoom 21 | * @copyright 2021 Michelle Melton 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom\task; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | require_once($CFG->dirroot . '/mod/zoom/lib.php'); 30 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 31 | 32 | use core\task\scheduled_task; 33 | use moodle_exception; 34 | 35 | /** 36 | * Scheduled task to sychronize tracking field data. 37 | */ 38 | class update_tracking_fields extends scheduled_task { 39 | /** 40 | * Returns name of task. 41 | * 42 | * @return string 43 | */ 44 | public function get_name() { 45 | return get_string('updatetrackingfields', 'mod_zoom'); 46 | } 47 | 48 | /** 49 | * Updates tracking fields. 50 | * 51 | * @return boolean 52 | */ 53 | public function execute() { 54 | try { 55 | $service = zoom_webservice(); 56 | } catch (moodle_exception $exception) { 57 | mtrace('Skipping task - ', $exception->getMessage()); 58 | return; 59 | } 60 | 61 | // Required scopes for tracking fields. 62 | $requiredscopes = [ 63 | 'classic' => [ 64 | 'tracking_fields:read:admin', 65 | ], 66 | 'granular' => [ 67 | 'tracking_field:read:list_tracking_fields:admin', 68 | ], 69 | ]; 70 | 71 | // Checking for missing scopes. 72 | $missingscopes = $service->check_scopes($requiredscopes); 73 | if (!empty($missingscopes)) { 74 | foreach ($missingscopes as $missingscope) { 75 | mtrace('Missing scope: ' . $missingscope); 76 | } 77 | return; 78 | } 79 | 80 | // Show trace message. 81 | mtrace('Starting to process existing Zoom tracking fields ...'); 82 | 83 | if (!mod_zoom_update_tracking_fields()) { 84 | mtrace('Error: Failed to update tracking fields.'); 85 | } 86 | 87 | // Show trace message. 88 | mtrace('Finished processing existing Zoom tracking fields'); 89 | 90 | return true; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /classes/webservice_exception.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception class for Zoom API errors. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2023 Jonathan Champ 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | use moodle_exception; 28 | 29 | /** 30 | * Webservice exception class. 31 | */ 32 | class webservice_exception extends moodle_exception { 33 | /** 34 | * Web service response. 35 | * @var string 36 | */ 37 | public $response = null; 38 | 39 | /** 40 | * Web service error code. 41 | * @var int 42 | */ 43 | public $zoomerrorcode = null; 44 | 45 | /** 46 | * Constructor 47 | * 48 | * @param string $response Webservice response body. 49 | * @param int $zoomerrorcode Webservice response error code. 50 | * @param string $errorcode The name of the string from error.php to print 51 | * @param string $module name of module 52 | * @param string $link The url where the user will be directed. Else, the user will be directed to the site index page. 53 | * @param mixed $a Extra words and phrases that might be required in the error string 54 | * @param string $debuginfo optional debugging information 55 | */ 56 | public function __construct($response, $zoomerrorcode, $errorcode, $module = '', $link = '', $a = null, $debuginfo = null) { 57 | $this->response = $response; 58 | $this->zoomerrorcode = $zoomerrorcode; 59 | 60 | parent::__construct($errorcode, $module, $link, $a, $debuginfo); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cli/get_meeting_report.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * CLI script to manually get the meeting report. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | define('CLI_SCRIPT', true); 26 | 27 | require(__DIR__ . '/../../../config.php'); 28 | require_once($CFG->libdir . '/clilib.php'); 29 | 30 | // Now get cli options. 31 | [$options, $unrecognized] = cli_get_params( 32 | [ 33 | 'help' => false, 34 | 'start' => false, 35 | 'end' => false, 36 | 'hostuuid' => false, 37 | 'courseid' => false, 38 | ], 39 | [ 40 | 'h' => 'help', 41 | ] 42 | ); 43 | 44 | if ($unrecognized) { 45 | $unrecognized = implode("\n ", $unrecognized); 46 | cli_error(get_string('cliunknowoption', 'admin', $unrecognized)); 47 | } 48 | 49 | if ($options['help'] || empty($options['start'] || empty($options['end']))) { 50 | $help = "CLI script to manually get the meeting report for a given start and end date. 51 | 52 | Options: 53 | -h, --help Print out this help 54 | --start Required. In YYYY-MM-DD format 55 | --end Required. In YYYY-MM-DD format 56 | --hostuuid Optional. Specific host we want to get meetings for. 57 | --courseid Optional. If given, will find all hosts for course and get meeting reports. 58 | 59 | Example: 60 | \$sudo -u www-data /usr/bin/php mod/zoom/cli/get_meeting_report.php --start=2020-03-31 --end=2020-04-01 61 | "; 62 | cli_error($help); 63 | } 64 | 65 | $hostuuids = null; 66 | if (!empty($options['hostuuid'])) { 67 | $hostuuids = [$options['hostuuid']]; 68 | } else if (!empty($options['courseid'])) { 69 | // Find all hosts for course. 70 | $hostuuids = $DB->get_fieldset_select('zoom', 'DISTINCT host_id', 'course=:courseid', ['courseid' => $options['courseid']]); 71 | if (empty($hostuuids)) { 72 | cli_writeln(get_string('nozoomsfound', 'mod_zoom')); 73 | cli_error('No hosts found for course'); 74 | } 75 | } 76 | 77 | // Turn on debugging so we can see the detailed progress. 78 | set_debugging(DEBUG_DEVELOPER, true); 79 | 80 | $meetingtask = new mod_zoom\task\get_meeting_reports(); 81 | $meetingtask->execute($options['start'], $options['end'], $hostuuids); 82 | 83 | cli_writeln('DONE!'); 84 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ucla/moodle-mod_zoom", 3 | "type": "moodle-mod", 4 | "require": { 5 | "composer/installers": "~1.0" 6 | }, 7 | "extra": { 8 | "installer-name": "zoom" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /console/get_meeting_report.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Console page to output the results of the CLI to get the Zoom meeting reports. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__ . '/../../../config.php'); 26 | require_once($CFG->libdir . '/moodlelib.php'); 27 | 28 | // Force debugging errors. 29 | error_reporting(E_ALL); 30 | ini_set('display_errors', '1'); 31 | 32 | $courseid = required_param('courseid', PARAM_INT); 33 | $startdate = optional_param('start', date('Y-m-d', strtotime('-3 days')), PARAM_ALPHANUMEXT); 34 | $enddate = optional_param('end', date('Y-m-d'), PARAM_ALPHANUMEXT); 35 | 36 | $course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST); 37 | 38 | require_course_login($course); 39 | 40 | $context = context_course::instance($course->id); 41 | require_capability('mod/zoom:view', $context); 42 | require_capability('mod/zoom:refreshsessions', $context); 43 | 44 | // Set up the moodle page. 45 | $PAGE->set_url('/mod/zoom/console/'); 46 | 47 | echo html_writer::tag('h1', get_string('getmeetingreports', 'mod_zoom')); 48 | $output = null; 49 | exec("php $CFG->dirroot/mod/zoom/cli/get_meeting_report.php --start=$startdate --end=$enddate --courseid=$courseid", $output); 50 | echo '
';
51 | echo implode("\n", $output);
52 | echo '
'; 53 | -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Capability definitions for the zoom module 19 | * 20 | * The capabilities are loaded into the database table when the module is 21 | * installed or updated. Whenever the capability definitions are updated, 22 | * the module version number should be bumped up. 23 | * 24 | * The system has four possible values for a capability: 25 | * CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT, and inherit (not set). 26 | * 27 | * It is important that capability names are unique. The naming convention 28 | * for capabilities that are specific to modules and blocks is as follows: 29 | * [mod/block]/: 30 | * 31 | * component_name should be the same as the directory name of the mod or block. 32 | * 33 | * Core moodle capabilities are defined thus: 34 | * moodle/: 35 | * 36 | * Examples: mod/forum:viewpost 37 | * block/recent_activity:view 38 | * moodle/site:deleteuser 39 | * 40 | * The variable name for the capability definitions array is $capabilities 41 | * 42 | * @package mod_zoom 43 | * @copyright 2015 UC Regents 44 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 45 | */ 46 | 47 | defined('MOODLE_INTERNAL') || die(); 48 | 49 | // Modify capabilities as needed and remove this comment. 50 | $capabilities = [ 51 | 'mod/zoom:addinstance' => [ 52 | 'riskbitmask' => RISK_XSS, 53 | 'captype' => 'write', 54 | 'contextlevel' => CONTEXT_COURSE, 55 | 'archetypes' => [ 56 | 'editingteacher' => CAP_ALLOW, 57 | 'manager' => CAP_ALLOW, 58 | ], 59 | 'clonepermissionsfrom' => 'moodle/course:manageactivities', 60 | ], 61 | 62 | 'mod/zoom:view' => [ 63 | 'captype' => 'read', 64 | 'contextlevel' => CONTEXT_MODULE, 65 | 'legacy' => [ 66 | 'guest' => CAP_ALLOW, 67 | 'student' => CAP_ALLOW, 68 | 'teacher' => CAP_ALLOW, 69 | 'editingteacher' => CAP_ALLOW, 70 | 'manager' => CAP_ALLOW, 71 | ], 72 | ], 73 | 74 | 'mod/zoom:refreshsessions' => [ 75 | 'riskbitmask' => RISK_XSS, 76 | 'captype' => 'write', 77 | 'contextlevel' => CONTEXT_COURSE, 78 | 'archetypes' => [ 79 | 'manager' => CAP_ALLOW, 80 | ], 81 | ], 82 | 83 | 'mod/zoom:eligiblealternativehost' => [ 84 | 'riskbitmask' => RISK_PERSONAL, 85 | 'captype' => 'read', 86 | 'contextlevel' => CONTEXT_COURSE, 87 | 'archetypes' => [ 88 | 'teacher' => CAP_ALLOW, 89 | 'editingteacher' => CAP_ALLOW, 90 | ], 91 | ], 92 | 93 | 'mod/zoom:viewjoinurl' => [ 94 | 'riskbitmask' => RISK_PERSONAL, 95 | 'captype' => 'read', 96 | 'contextlevel' => CONTEXT_COURSE, 97 | 'archetypes' => [ 98 | 'teacher' => CAP_ALLOW, 99 | 'editingteacher' => CAP_ALLOW, 100 | 'manager' => CAP_ALLOW, 101 | ], 102 | ], 103 | 104 | 'mod/zoom:viewdialin' => [ 105 | 'riskbitmask' => RISK_PERSONAL, 106 | 'captype' => 'read', 107 | 'contextlevel' => CONTEXT_COURSE, 108 | 'archetypes' => [ 109 | 'student' => CAP_ALLOW, 110 | 'teacher' => CAP_ALLOW, 111 | 'editingteacher' => CAP_ALLOW, 112 | 'manager' => CAP_ALLOW, 113 | ], 114 | ], 115 | ]; 116 | -------------------------------------------------------------------------------- /db/caches.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Zoom cache definitions. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die; 26 | 27 | $definitions = [ 28 | 'zoomid' => [ 29 | 'mode' => cache_store::MODE_SESSION, 30 | ], 31 | 'zoommeetingsecurity' => [ 32 | 'mode' => cache_store::MODE_APPLICATION, 33 | ], 34 | 'oauth' => [ 35 | 'mode' => cache_store::MODE_APPLICATION, 36 | 'simplekeys' => true, 37 | 'simpledata' => true, 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /db/install.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Provides code to be executed during the module installation. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | /** 26 | * Post installation procedure 27 | * 28 | * @see upgrade_plugins_modules() 29 | */ 30 | function xmldb_zoom_install() { 31 | } 32 | 33 | /** 34 | * Post installation recovery procedure 35 | * 36 | * @see upgrade_plugins_modules() 37 | */ 38 | function xmldb_zoom_install_recovery() { 39 | } 40 | -------------------------------------------------------------------------------- /db/messages.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines message providers for mod_zoom. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2023 Mo Farouk 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $messageproviders = [ 28 | 'teacher_notification' => [], 29 | // The ical notifications task messages. 30 | 'ical_notifications' => [ 31 | 'defaults' => [ 32 | 'popup' => MESSAGE_DISALLOWED, 33 | 'email' => MESSAGE_PERMITTED + (defined('MESSAGE_DEFAULT_ENABLED') ? 34 | MESSAGE_DEFAULT_ENABLED : MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF), 35 | 'airnotifier' => MESSAGE_DISALLOWED, 36 | ], 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /db/mobile.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Zoom module capability definition 19 | * 20 | * @package mod_zoom 21 | * @copyright 2018 Nick Stefanski 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die; 26 | 27 | $addons = [ 28 | "mod_zoom" => [ 29 | "handlers" => [ 30 | 'zoommeetingdetails' => [ 31 | 'displaydata' => [ 32 | 'title' => 'pluginname', 33 | 'icon' => $CFG->wwwroot . '/mod/zoom/pix/icon.gif', 34 | 'class' => '', 35 | ], 36 | 37 | 'delegate' => 'CoreCourseModuleDelegate', 38 | 'method' => 'mobile_course_view', // Main function in \mod_zoom\output\mobile. 39 | 'offlinefunctions' => [ 40 | 'mobile_course_view' => [], 41 | ], 42 | ], 43 | ], 44 | 'lang' => [ 45 | ['pluginname', 'zoom'], 46 | ['join_meeting', 'zoom'], 47 | ['unavailable', 'zoom'], 48 | ['meeting_time', 'zoom'], 49 | ['duration', 'zoom'], 50 | ['passwordprotected', 'zoom'], 51 | ['password', 'zoom'], 52 | ['joinlink', 'zoom'], 53 | ['joinbeforehost', 'zoom'], 54 | ['starthostjoins', 'zoom'], 55 | ['startpartjoins', 'zoom'], 56 | ['option_audio', 'zoom'], 57 | ['status', 'zoom'], 58 | ['recurringmeetinglong', 'zoom'], 59 | ], 60 | ], 61 | ]; 62 | -------------------------------------------------------------------------------- /db/services.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Zoom external functions and service definitions. 19 | * 20 | * @package mod_zoom 21 | * @category external 22 | * @author Nick Stefanski 23 | * @copyright 2017 Auguste Escoffier School of Culinary Arts {@link https://www.escoffier.edu} 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | * @since Moodle 3.1 26 | */ 27 | 28 | defined('MOODLE_INTERNAL') || die; 29 | 30 | $functions = [ 31 | 'mod_zoom_get_state' => [ 32 | 'classname' => 'mod_zoom\external', 33 | 'methodname' => 'get_state', 34 | 'classpath' => 'mod/zoom/classes/external.php', 35 | 'description' => 'Determine if a zoom meeting is available, meeting ' 36 | . 'status, and the start time, duration, and other meeting options.', 37 | 'type' => 'read', 38 | 'capabilities' => 'mod/zoom:view', 39 | 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], 40 | ], 41 | 'mod_zoom_grade_item_update' => [ 42 | 'classname' => 'mod_zoom\external', 43 | 'methodname' => 'grade_item_update', 44 | 'classpath' => 'mod/zoom/classes/external.php', 45 | 'description' => 'Creates or updates grade item for the given zoom instance and returns join url.', 46 | 'type' => 'write', 47 | 'capabilities' => 'mod/zoom:view', 48 | 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], 49 | ], 50 | ]; 51 | -------------------------------------------------------------------------------- /db/tasks.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Definition of Zoom scheduled tasks. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die; 26 | 27 | $tasks = [ 28 | [ 29 | 'classname' => 'mod_zoom\task\update_meetings', 30 | 'blocking' => 0, 31 | 'minute' => '30', 32 | 'hour' => '4', 33 | 'day' => '*', 34 | 'month' => '*', 35 | 'dayofweek' => '*', 36 | ], 37 | [ 38 | 'classname' => 'mod_zoom\task\get_meeting_reports', 39 | 'blocking' => 0, 40 | 'minute' => '0', 41 | 'hour' => '*/6', 42 | 'day' => '*', 43 | 'dayofweek' => '*', 44 | 'month' => '*', 45 | ], 46 | [ 47 | 'classname' => 'mod_zoom\task\update_tracking_fields', 48 | 'blocking' => 0, 49 | 'minute' => '0', 50 | 'hour' => '*/6', 51 | 'day' => '*', 52 | 'dayofweek' => '*', 53 | 'month' => '*', 54 | ], 55 | [ 56 | 'classname' => 'mod_zoom\task\get_meeting_recordings', 57 | 'blocking' => 0, 58 | 'minute' => '0', 59 | 'hour' => '*/3', 60 | 'day' => '*', 61 | 'dayofweek' => '*', 62 | 'month' => '*', 63 | ], 64 | [ 65 | 'classname' => 'mod_zoom\task\delete_meeting_recordings', 66 | 'blocking' => 0, 67 | 'minute' => '0', 68 | 'hour' => '0', 69 | 'day' => '*', 70 | 'dayofweek' => '*', 71 | 'month' => '*', 72 | ], 73 | [ 74 | 'classname' => 'mod_zoom\task\send_ical_notifications', 75 | 'blocking' => 0, 76 | 'minute' => '*/5', 77 | 'hour' => '*', 78 | 'day' => '*', 79 | 'dayofweek' => '*', 80 | 'month' => '*', 81 | ], 82 | ]; 83 | -------------------------------------------------------------------------------- /db/uninstall.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Provides code to be executed during the module uninstallation 19 | * 20 | * @see uninstall_plugin() 21 | * 22 | * @package mod_zoom 23 | * @copyright 2015 UC Regents 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | /** 28 | * Custom uninstallation procedure 29 | */ 30 | function xmldb_zoom_uninstall() { 31 | return true; 32 | } 33 | -------------------------------------------------------------------------------- /exportical.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Export ical file for a zoom meeting. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__ . '/../../config.php'); 26 | require_once($CFG->libdir . '/moodlelib.php'); 27 | require_once(__DIR__ . '/locallib.php'); 28 | require_once($CFG->libdir . '/bennu/bennu.inc.php'); 29 | 30 | // Course_module ID. 31 | $id = required_param('id', PARAM_INT); 32 | if ($id) { 33 | $cm = get_coursemodule_from_id('zoom', $id, 0, false, MUST_EXIST); 34 | $course = get_course($cm->course); 35 | $zoom = $DB->get_record('zoom', ['id' => $cm->instance], '*', MUST_EXIST); 36 | } else { 37 | throw new moodle_exception('zoomerr_id_missing', 'mod_zoom'); 38 | } 39 | 40 | require_login($course, true, $cm); 41 | 42 | $context = context_module::instance($cm->id); 43 | $PAGE->set_context($context); 44 | 45 | require_capability('mod/zoom:view', $context); 46 | 47 | // Get config. 48 | $config = get_config('zoom'); 49 | 50 | // Check if the admin did not disable the feature. 51 | if ($config->showdownloadical == ZOOM_DOWNLOADICAL_DISABLE) { 52 | $disabledredirecturl = new moodle_url('/mod/zoom/view.php', ['id' => $id]); 53 | throw new moodle_exception('err_downloadicaldisabled', 'mod_zoom', $disabledredirecturl); 54 | } 55 | 56 | // Check if we are dealing with a recurring meeting with no fixed time. 57 | if ($zoom->recurring && $zoom->recurrence_type == ZOOM_RECURRINGTYPE_NOTIME) { 58 | $errorredirecturl = new moodle_url('/mod/zoom/view.php', ['id' => $id]); 59 | throw new moodle_exception('err_downloadicalrecurringnofixed', 'mod_zoom', $errorredirecturl); 60 | } 61 | 62 | // Start ical file. 63 | $ical = new iCalendar(); 64 | $ical->add_property('method', 'PUBLISH'); 65 | $ical->add_property('prodid', '-//Moodle Pty Ltd//NONSGML Moodle Version ' . $CFG->version . '//EN'); 66 | 67 | // Get the meeting invite note to add to the description property. 68 | $meetinginvite = zoom_webservice()->get_meeting_invitation($zoom)->get_display_string($cm->id); 69 | 70 | // Compute and add description property to event. 71 | $convertedtext = html_to_text($zoom->intro); 72 | $descriptiontext = get_string('calendardescriptionurl', 'mod_zoom', $CFG->wwwroot . '/mod/zoom/view.php?id=' . $cm->id); 73 | if (!empty($convertedtext)) { 74 | $descriptiontext .= get_string('calendardescriptionintro', 'mod_zoom', $convertedtext); 75 | } 76 | 77 | if (!empty($meetinginvite)) { 78 | $descriptiontext .= "\n\n" . $meetinginvite; 79 | } 80 | 81 | // Get all occurrences of the meeting from the DB. 82 | $params = ['modulename' => 'zoom', 'instance' => $zoom->id]; 83 | $events = $DB->get_records('event', $params, 'timestart ASC'); 84 | 85 | // If we haven't got at least a single occurrence. 86 | if (empty($events)) { 87 | // We could handle this case in a nicer way ans return an empty iCal file without events, 88 | // but as this case should not happen in real life anyway, return a fatal error to make clear that something is wrong. 89 | $errorredirecturl = new moodle_url('/mod/zoom/view.php', ['id' => $id]); 90 | throw new moodle_exception('err_downloadicalrecurringempty', 'mod_zoom', $errorredirecturl); 91 | } 92 | 93 | // Iterate over all events. 94 | // We will add each event as an individual iCal event. 95 | foreach ($events as $event) { 96 | $icalevent = zoom_helper_icalendar_event($event, $descriptiontext); 97 | // Add the event to the iCal file. 98 | $ical->add_component($icalevent); 99 | } 100 | 101 | // Start output of iCal file. 102 | $serialized = $ical->serialize(); 103 | $filename = 'icalexport.ics'; 104 | 105 | // Create headers. 106 | header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); 107 | header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0'); 108 | header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . 'GMT'); 109 | header('Pragma: no-cache'); 110 | header('Accept-Ranges: none'); // Comment out if PDFs do not work... 111 | header('Content-disposition: attachment; filename=' . $filename); 112 | header('Content-length: ' . strlen($serialized)); 113 | header('Content-type: text/calendar; charset=utf-8'); 114 | 115 | echo $serialized; 116 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * List all zoom meetings. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__ . '/../../config.php'); 26 | require_once(__DIR__ . '/lib.php'); 27 | require_once(__DIR__ . '/locallib.php'); 28 | require_once($CFG->libdir . '/moodlelib.php'); 29 | 30 | $id = required_param('id', PARAM_INT); // Course. 31 | 32 | $course = $DB->get_record('course', ['id' => $id], '*', MUST_EXIST); 33 | 34 | require_course_login($course); 35 | 36 | $context = context_course::instance($course->id); 37 | require_capability('mod/zoom:view', $context); 38 | $iszoommanager = has_capability('mod/zoom:addinstance', $context); 39 | 40 | $params = [ 41 | 'context' => $context, 42 | ]; 43 | $event = \mod_zoom\event\course_module_instance_list_viewed::create($params); 44 | $event->add_record_snapshot('course', $course); 45 | $event->trigger(); 46 | 47 | $strname = get_string('modulenameplural', 'mod_zoom'); 48 | $strnew = get_string('newmeetings', 'mod_zoom'); 49 | $strold = get_string('oldmeetings', 'mod_zoom'); 50 | 51 | $strtitle = get_string('title', 'mod_zoom'); 52 | $strwebinar = get_string('webinar', 'mod_zoom'); 53 | $strtime = get_string('meeting_time', 'mod_zoom'); 54 | $strduration = get_string('duration', 'mod_zoom'); 55 | $stractions = get_string('actions', 'mod_zoom'); 56 | $strsessions = get_string('sessions', 'mod_zoom'); 57 | 58 | $strmeetingstarted = get_string('meeting_started', 'mod_zoom'); 59 | $strjoin = get_string('join', 'mod_zoom'); 60 | 61 | $PAGE->set_url('/mod/zoom/index.php', ['id' => $id]); 62 | $PAGE->navbar->add($strname); 63 | $PAGE->set_title("$course->shortname: $strname"); 64 | $PAGE->set_heading($course->fullname); 65 | $PAGE->set_pagelayout('incourse'); 66 | 67 | echo $OUTPUT->header(); 68 | 69 | if ($CFG->branch < '400') { 70 | echo $OUTPUT->heading($strname); 71 | } 72 | 73 | if (! $zooms = get_all_instances_in_course('zoom', $course)) { 74 | notice(get_string('nozooms', 'mod_zoom'), new moodle_url('/course/view.php', ['id' => $course->id])); 75 | } 76 | 77 | $usesections = course_format_uses_sections($course->format); 78 | 79 | $zoomuserid = zoom_get_user_id(false); 80 | 81 | $newtable = new html_table(); 82 | $newtable->attributes['class'] = 'generaltable mod_index'; 83 | $newhead = [$strtitle, $strtime, $strduration, $stractions]; 84 | $newalign = ['left', 'left', 'left', 'left']; 85 | 86 | $oldtable = new html_table(); 87 | $oldhead = [$strtitle, $strtime]; 88 | $oldalign = ['left', 'left']; 89 | 90 | // Show section column if there are sections. 91 | if ($usesections) { 92 | $strsectionname = get_string('sectionname', 'format_' . $course->format); 93 | array_unshift($newhead, $strsectionname); 94 | array_unshift($newalign, 'center'); 95 | array_unshift($oldhead, $strsectionname); 96 | array_unshift($oldalign, 'center'); 97 | } 98 | 99 | // Show sessions column only if user can edit Zoom meetings. 100 | if ($iszoommanager) { 101 | $newhead[] = $strsessions; 102 | $newalign[] = 'left'; 103 | $oldhead[] = $strsessions; 104 | $oldalign[] = 'left'; 105 | } 106 | 107 | $newtable->head = $newhead; 108 | $newtable->align = $newalign; 109 | $oldtable->head = $oldhead; 110 | $oldtable->align = $oldalign; 111 | 112 | $now = time(); 113 | $modinfo = get_fast_modinfo($course); 114 | $cms = $modinfo->instances['zoom']; 115 | foreach ($zooms as $z) { 116 | $row = []; 117 | [$inprogress, $available, $finished] = zoom_get_state($z); 118 | 119 | $cm = $cms[$z->id]; 120 | if ($usesections && isset($cm->sectionnum)) { 121 | $row[0] = get_section_name($course, $cm->sectionnum); 122 | } 123 | 124 | $url = new moodle_url('view.php', ['id' => $cm->id]); 125 | $row[1] = html_writer::link($url, $cm->get_formatted_name()); 126 | if ($z->webinar) { 127 | $row[1] .= " ($strwebinar)"; 128 | } 129 | 130 | // Get start time column information. 131 | if ($z->recurring && $z->recurrence_type == ZOOM_RECURRINGTYPE_NOTIME) { 132 | $displaytime = get_string('recurringmeeting', 'mod_zoom'); 133 | $displaytime .= html_writer::empty_tag('br'); 134 | $displaytime .= get_string('recurringmeetingexplanation', 'mod_zoom'); 135 | } else if ($z->recurring && $z->recurrence_type != ZOOM_RECURRINGTYPE_NOTIME) { 136 | $displaytime = get_string('recurringmeeting', 'mod_zoom'); 137 | $displaytime .= html_writer::empty_tag('br'); 138 | if (($nextoccurrence = zoom_get_next_occurrence($z)) > 0) { 139 | $displaytime .= get_string('nextoccurrence', 'mod_zoom') . ': ' . userdate($nextoccurrence); 140 | } else { 141 | $displaytime .= get_string('nooccurrenceleft', 'mod_zoom'); 142 | } 143 | } else { 144 | $displaytime = userdate($z->start_time); 145 | } 146 | 147 | $report = new moodle_url('report.php', ['id' => $cm->id]); 148 | $sessions = html_writer::link($report, $strsessions); 149 | 150 | if ($finished) { 151 | $row[2] = $displaytime; 152 | if ($iszoommanager) { 153 | $row[3] = $sessions; 154 | } 155 | 156 | $oldtable->data[] = $row; 157 | } else { 158 | if ($inprogress) { 159 | $label = html_writer::tag('span', $strmeetingstarted, ['class' => 'badge bg-dark badge-dark bg-text-dark']); 160 | $row[2] = html_writer::tag('div', $label); 161 | } else { 162 | $row[2] = $displaytime; 163 | } 164 | 165 | $row[3] = ($z->recurring && $z->recurrence_type == ZOOM_RECURRINGTYPE_NOTIME) ? '--' : format_time($z->duration); 166 | 167 | if ($available) { 168 | $buttonhtml = html_writer::tag('button', $strjoin, ['type' => 'submit', 'class' => 'btn btn-primary']); 169 | $aurl = new moodle_url('/mod/zoom/loadmeeting.php', ['id' => $cm->id]); 170 | $buttonhtml .= html_writer::input_hidden_params($aurl); 171 | $row[4] = html_writer::tag('form', $buttonhtml, ['action' => $aurl->out_omit_querystring(), 'target' => '_blank']); 172 | } else { 173 | $row[4] = '--'; 174 | } 175 | 176 | if ($iszoommanager) { 177 | $row[] = $sessions; 178 | } 179 | 180 | $newtable->data[] = $row; 181 | } 182 | } 183 | 184 | echo $OUTPUT->heading($strnew, 4); 185 | echo html_writer::table($newtable); 186 | echo $OUTPUT->heading($strold, 4, null, 'mod-zoom-old-meetings-header'); 187 | // Show refresh meeting sessions link only if user can run the 'refresh session reports' console command. 188 | if (has_capability('mod/zoom:refreshsessions', $context)) { 189 | $linkarguments = [ 190 | 'courseid' => $id, 191 | 'start' => date('Y-m-d', strtotime('-3 days')), 192 | 'end' => date('Y-m-d'), 193 | ]; 194 | $url = new moodle_url($CFG->wwwroot . '/mod/zoom/console/get_meeting_report.php', $linkarguments); 195 | echo html_writer::link($url, get_string('refreshreports', 'mod_zoom'), ['target' => '_blank', 'class' => 'pl-4']); 196 | } 197 | 198 | echo html_writer::table($oldtable); 199 | 200 | echo $OUTPUT->footer(); 201 | -------------------------------------------------------------------------------- /loadmeeting.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Load zoom meeting. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__ . '/../../config.php'); 26 | require_once(__DIR__ . '/locallib.php'); 27 | 28 | require_login(); 29 | 30 | // Course_module ID. 31 | $id = required_param('id', PARAM_INT); 32 | if ($id) { 33 | $context = context_module::instance($id); 34 | $PAGE->set_context($context); 35 | 36 | // Call load meeting function (note: this is where additional access checks happen). 37 | $meetinginfo = zoom_load_meeting($id, $context); 38 | 39 | // Redirect if available, otherwise deny access. 40 | if ($meetinginfo['nexturl']) { 41 | redirect($meetinginfo['nexturl']); 42 | } else { 43 | // Get redirect URL. 44 | $unavailabilityurl = new moodle_url('/mod/zoom/view.php', ['id' => $id]); 45 | 46 | // Redirect the user back to the activity overview page. 47 | redirect($unavailabilityurl, $meetinginfo['error'], null, \core\output\notification::NOTIFY_ERROR); 48 | } 49 | } else { 50 | throw new moodle_exception('zoomerr_id_missing', 'mod_zoom'); 51 | } 52 | -------------------------------------------------------------------------------- /loadrecording.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Load zoom meeting recording and add a record of the view. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 Nick Stefanski 22 | * @author 2021 Jwalit Shah 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require(__DIR__ . '/../../config.php'); 27 | require_once($CFG->libdir . '/moodlelib.php'); 28 | require_once(__DIR__ . '/locallib.php'); 29 | 30 | $recordingid = required_param('recordingid', PARAM_INT); 31 | 32 | if (!get_config('zoom', 'viewrecordings')) { 33 | throw new moodle_exception('recordingnotvisible', 'mod_zoom', get_string('recordingnotvisible', 'zoom')); 34 | } 35 | 36 | [$course, $cm, $zoom] = zoom_get_instance_setup(); 37 | require_login($course, true, $cm); 38 | 39 | $context = context_module::instance($cm->id); 40 | $PAGE->set_context($context); 41 | 42 | require_capability('mod/zoom:view', $context); 43 | 44 | // Only show recording that is visble and valid. 45 | $params = [ 46 | 'id' => $recordingid, 47 | 'showrecording' => 1, 48 | 'zoomid' => $zoom->id, 49 | ]; 50 | $rec = $DB->get_record('zoom_meeting_recordings', $params); 51 | if (empty($rec)) { 52 | throw new moodle_exception('recordingnotfound', 'mod_zoom', '', get_string('recordingnotfound', 'zoom')); 53 | } 54 | 55 | $params = ['recordingsid' => $rec->id, 'userid' => $USER->id]; 56 | $now = time(); 57 | 58 | // Keep track of whether someone has viewed the recording or not. 59 | $view = $DB->get_record('zoom_meeting_recordings_view', $params); 60 | if (!empty($view)) { 61 | if (empty($view->viewed)) { 62 | $view->viewed = 1; 63 | $view->timemodified = $now; 64 | $DB->update_record('zoom_meeting_recordings_view', $view); 65 | } 66 | } else { 67 | $view = new stdClass(); 68 | $view->recordingsid = $rec->id; 69 | $view->userid = $USER->id; 70 | $view->viewed = 1; 71 | $view->timemodified = $now; 72 | $view->id = $DB->insert_record('zoom_meeting_recordings_view', $view); 73 | } 74 | 75 | $nexturl = new moodle_url($rec->externalurl); 76 | 77 | redirect($nexturl); 78 | -------------------------------------------------------------------------------- /participants.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * List all zoom meetings. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__ . '/../../config.php'); 26 | require_once(__DIR__ . '/lib.php'); 27 | require_once(__DIR__ . '/locallib.php'); 28 | require_once($CFG->libdir . '/accesslib.php'); 29 | require_once($CFG->libdir . '/moodlelib.php'); 30 | 31 | require_login(); 32 | // Additional access checks in zoom_get_instance_setup(). 33 | [$course, $cm, $zoom] = zoom_get_instance_setup(); 34 | 35 | global $DB; 36 | 37 | // Check capability. 38 | $context = context_module::instance($cm->id); 39 | require_capability('mod/zoom:addinstance', $context); 40 | 41 | $uuid = required_param('uuid', PARAM_RAW); 42 | $export = optional_param('export', null, PARAM_ALPHA); 43 | 44 | $PAGE->set_url('/mod/zoom/participants.php', ['id' => $cm->id, 'uuid' => $uuid, 'export' => $export]); 45 | 46 | $strname = $zoom->name; 47 | $strtitle = get_string('participants', 'mod_zoom'); 48 | $PAGE->navbar->add($strtitle); 49 | $PAGE->set_title("$course->shortname: $strname"); 50 | $PAGE->set_heading($course->fullname); 51 | $PAGE->set_pagelayout('incourse'); 52 | 53 | $maskparticipantdata = get_config('zoom', 'maskparticipantdata'); 54 | // If participant data is masked then display a message stating as such and be done with it. 55 | if ($maskparticipantdata) { 56 | zoom_fatal_error( 57 | 'participantdatanotavailable_help', 58 | 'mod_zoom', 59 | new moodle_url('/mod/zoom/report.php', ['id' => $cm->id]) 60 | ); 61 | } 62 | 63 | $sessions = zoom_get_sessions_for_display($zoom->id); 64 | $participants = $sessions[$uuid]['participants']; 65 | 66 | // Display the headers/etc if we're not exporting, or if there is no data. 67 | if (empty($export) || empty($participants)) { 68 | echo $OUTPUT->header(); 69 | echo $OUTPUT->heading($strname); 70 | echo $OUTPUT->heading($strtitle, 4); 71 | 72 | // Stop if there is no data. 73 | if (empty($participants)) { 74 | notice(get_string('noparticipants', 'mod_zoom'), new moodle_url('/mod/zoom/report.php', ['id' => $cm->id])); 75 | echo $OUTPUT->footer(); 76 | exit(); 77 | } 78 | } 79 | 80 | // Loop through each user to generate id->idnumber mapping. 81 | $coursecontext = context_course::instance($course->id); 82 | $enrolled = get_enrolled_users($coursecontext); 83 | $moodleidtouids = []; 84 | foreach ($enrolled as $user) { 85 | $moodleidtouids[$user->id] = $user->idnumber; 86 | } 87 | 88 | $table = new html_table(); 89 | // If we are exporting, then put email as a separate column. 90 | if (!empty($export)) { 91 | $table->head = [ 92 | get_string('idnumber'), 93 | get_string('name'), 94 | get_string('email'), 95 | get_string('jointime', 'mod_zoom'), 96 | get_string('leavetime', 'mod_zoom'), 97 | get_string('duration', 'mod_zoom'), 98 | ]; 99 | } else { 100 | $table->head = [ 101 | get_string('idnumber'), 102 | get_string('name'), 103 | get_string('jointime', 'mod_zoom'), 104 | get_string('leavetime', 'mod_zoom'), 105 | get_string('duration', 'mod_zoom'), 106 | ]; 107 | } 108 | 109 | foreach ($participants as $p) { 110 | $row = []; 111 | 112 | // Gets moodleuser so we can try to match information to Moodle database. 113 | $moodleuser = new stdClass(); 114 | if (!empty($p->userid)) { 115 | $moodleuser = $DB->get_record('user', ['id' => $p->userid], 'idnumber, email'); 116 | } 117 | 118 | // ID number. 119 | if (array_key_exists($p->userid, $moodleidtouids)) { 120 | $row[] = $moodleidtouids[$p->userid]; 121 | } else if (isset($moodleuser->idnumber)) { 122 | $row[] = $moodleuser->idnumber; 123 | } else { 124 | $row[] = ''; 125 | } 126 | 127 | // Name/email. 128 | $name = $p->name; 129 | $email = ''; 130 | if (!empty($moodleuser->email)) { 131 | $email = $moodleuser->email; 132 | } else if (!empty($p->user_email)) { 133 | $email = $p->user_email; 134 | } 135 | 136 | // Put email in separate column if we are exporting to Excel. 137 | if (!empty($export)) { 138 | $row[] = $name; 139 | $row[] = $email; 140 | } else if (!empty($email)) { 141 | $row[] = html_writer::link("mailto:$email", $name); 142 | } else { 143 | $row[] = $name; 144 | } 145 | 146 | // Join/leave times. 147 | $row[] = userdate($p->join_time, get_string('strftimedatetimeshort', 'langconfig')); 148 | $row[] = userdate($p->leave_time, get_string('strftimedatetimeshort', 'langconfig')); 149 | 150 | // Duration. 151 | $row[] = format_time($p->duration); 152 | 153 | $table->data[] = $row; 154 | } 155 | 156 | if ($export != 'xls') { 157 | echo html_writer::table($table); 158 | 159 | $exporturl = new moodle_url('/mod/zoom/participants.php', [ 160 | 'id' => $cm->id, 161 | 'uuid' => $uuid, 162 | 'export' => 'xls', 163 | ]); 164 | $xlsstring = get_string('application/vnd.ms-excel', 'mimetypes'); 165 | $xlsicon = html_writer::img( 166 | $OUTPUT->image_url('f/spreadsheet'), 167 | $xlsstring, 168 | ['title' => $xlsstring, 'class' => 'mimetypeicon'] 169 | ); 170 | echo get_string('export', 'mod_zoom') . ': ' . html_writer::link($exporturl, $xlsicon); 171 | 172 | echo $OUTPUT->footer(); 173 | } else { 174 | require_once($CFG->libdir . '/excellib.class.php'); 175 | 176 | $workbook = new MoodleExcelWorkbook("zoom_participants_{$zoom->meeting_id}"); 177 | $worksheet = $workbook->add_worksheet($strtitle); 178 | $boldformat = $workbook->add_format(); 179 | $boldformat->set_bold(true); 180 | $row = $col = 0; 181 | 182 | foreach ($table->head as $colname) { 183 | $worksheet->write_string($row, $col++, $colname, $boldformat); 184 | } 185 | 186 | $row++; 187 | $col = 0; 188 | 189 | foreach ($table->data as $entry) { 190 | foreach ($entry as $value) { 191 | $worksheet->write_string($row, $col++, $value); 192 | } 193 | 194 | $row++; 195 | $col = 0; 196 | } 197 | 198 | $workbook->close(); 199 | exit(); 200 | } 201 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /pix/i/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncstate-delta/moodle-mod_zoom/53bcd4d5212d72c1ff0d947f0a071c915401c28c/pix/i/calendar.png -------------------------------------------------------------------------------- /pix/i/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pix/icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncstate-delta/moodle-mod_zoom/53bcd4d5212d72c1ff0d947f0a071c915401c28c/pix/icon.gif -------------------------------------------------------------------------------- /pix/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncstate-delta/moodle-mod_zoom/53bcd4d5212d72c1ff0d947f0a071c915401c28c/pix/icon.png -------------------------------------------------------------------------------- /pix/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pix/monologo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /recordings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Adding, updating, and deleting zoom meeting recordings. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 UC Regents 22 | * @author 2021 Jwalit Shah 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require(__DIR__ . '/../../config.php'); 27 | require_once(__DIR__ . '/lib.php'); 28 | require_once(__DIR__ . '/locallib.php'); 29 | 30 | [$course, $cm, $zoom] = zoom_get_instance_setup(); 31 | 32 | require_login($course, true, $cm); 33 | 34 | if (!get_config('zoom', 'viewrecordings')) { 35 | throw new moodle_exception('recordingnotvisible', 'mod_zoom', get_string('recordingnotvisible', 'zoom')); 36 | } 37 | 38 | $context = context_module::instance($cm->id); 39 | // Set up the page. 40 | $params = ['id' => $cm->id]; 41 | $url = new moodle_url('/mod/zoom/recordings.php', $params); 42 | $PAGE->set_url($url); 43 | 44 | $strname = $zoom->name; 45 | $PAGE->set_title("$course->shortname: $strname"); 46 | $PAGE->set_heading($course->fullname); 47 | $PAGE->set_pagelayout('incourse'); 48 | 49 | echo $OUTPUT->header(); 50 | echo $OUTPUT->heading($strname); 51 | 52 | $iszoommanager = has_capability('mod/zoom:addinstance', $context); 53 | 54 | // Set up html table. 55 | $table = new html_table(); 56 | $table->attributes['class'] = 'generaltable mod_view'; 57 | if ($iszoommanager) { 58 | $table->align = ['left', 'left', 'left', 'left']; 59 | $table->head = [ 60 | get_string('recordingdate', 'mod_zoom'), 61 | get_string('recordinglink', 'mod_zoom'), 62 | get_string('recordingpasscode', 'mod_zoom'), 63 | get_string('recordingshowtoggle', 'mod_zoom'), 64 | ]; 65 | } else { 66 | $table->align = ['left', 'left', 'left']; 67 | $table->head = [ 68 | get_string('recordingdate', 'mod_zoom'), 69 | get_string('recordinglink', 'mod_zoom'), 70 | get_string('recordingpasscode', 'mod_zoom'), 71 | ]; 72 | } 73 | 74 | // Find all entries for this meeting in the database. 75 | $recordings = zoom_get_meeting_recordings_grouped($zoom->id); 76 | if (empty($recordings)) { 77 | $cell = new html_table_cell(); 78 | $cell->colspan = count($table->head); 79 | $cell->text = get_string('norecordings', 'mod_zoom'); 80 | $cell->style = 'text-align: center'; 81 | $row = new html_table_row([$cell]); 82 | $table->data = [$row]; 83 | } else { 84 | foreach ($recordings as $grouping) { 85 | // Output the related recordings into the same row. 86 | $recordingdate = ''; 87 | $recordinghtml = ''; 88 | $recordingpasscode = ''; 89 | $recordingshowhtml = ''; 90 | foreach ($grouping as $recording) { 91 | // If zoom admin -> show all recordings. 92 | // Or if visible to students. 93 | if ($iszoommanager || intval($recording->showrecording) === 1) { 94 | if (empty($recordingdate)) { 95 | $recordingdate = date('F j, Y, g:i:s a \P\T', $recording->recordingstart); 96 | } 97 | 98 | if (empty($recordingpasscode)) { 99 | $recordingpasscode = $recording->passcode; 100 | } 101 | 102 | if ($iszoommanager && empty($recordingshowhtml)) { 103 | $isrecordinghidden = intval($recording->showrecording) === 0; 104 | $urlparams = [ 105 | 'id' => $cm->id, 106 | 'meetinguuid' => $recording->meetinguuid, 107 | 'recordingstart' => $recording->recordingstart, 108 | 'showrecording' => ($isrecordinghidden) ? 1 : 0, 109 | 'sesskey' => sesskey(), 110 | ]; 111 | // If the user is a zoom admin, show the button to toggle whether students can see the recording or not. 112 | $recordingshowurl = new moodle_url('/mod/zoom/showrecording.php', $urlparams); 113 | $recordingshowtext = get_string('recordinghide', 'mod_zoom'); 114 | if ($isrecordinghidden) { 115 | $recordingshowtext = get_string('recordingshow', 'mod_zoom'); 116 | } 117 | 118 | $btnclass = 'btn btn-'; 119 | $btnclass .= $isrecordinghidden ? 'dark' : 'primary'; 120 | $recordingshowbutton = html_writer::div($recordingshowtext, $btnclass); 121 | $recordingshowbuttonhtml = html_writer::link($recordingshowurl, $recordingshowbutton); 122 | $recordingshowhtml = html_writer::div($recordingshowbuttonhtml); 123 | } 124 | 125 | $recordingname = trim($recording->name) . ' (' . zoom_get_recording_type_string($recording->recordingtype) . ')'; 126 | $params = ['id' => $cm->id, 'recordingid' => $recording->id]; 127 | $recordingurl = new moodle_url('/mod/zoom/loadrecording.php', $params); 128 | $recordinglink = html_writer::link($recordingurl, $recordingname); 129 | $recordinglinkhtml = html_writer::span($recordinglink, 'recording-link', ['style' => 'margin-right:1rem']); 130 | $recordinghtml .= html_writer::div($recordinglinkhtml, 'recording', ['style' => 'margin-bottom:.5rem']); 131 | } 132 | } 133 | 134 | // Output only one row per grouping. 135 | $table->data[] = [$recordingdate, $recordinghtml, htmlspecialchars($recordingpasscode), $recordingshowhtml]; 136 | } 137 | } 138 | 139 | /** 140 | * Get the display name for a Zoom recording type. 141 | * 142 | * @package mod_zoom 143 | * @param string $recordingtype Zoom recording type. 144 | * @return string 145 | */ 146 | function zoom_get_recording_type_string($recordingtype) { 147 | $recordingtypestringmap = [ 148 | 'active_speaker' => 'recordingtype_active_speaker', 149 | 'audio_interpretation' => 'recordingtype_audio_interpretation', 150 | 'audio_only' => 'recordingtype_audio_only', 151 | 'audio_transcript' => 'recordingtype_audio_transcript', 152 | 'chat_file' => 'recordingtype_chat', 153 | 'closed_caption' => 'recordingtype_closed_caption', 154 | 'gallery_view' => 'recordingtype_gallery', 155 | 'poll' => 'recordingtype_poll', 156 | 'production_studio' => 'recordingtype_production_studio', 157 | 'shared_screen' => 'recordingtype_shared', 158 | 'shared_screen_with_gallery_view' => 'recordingtype_shared_gallery', 159 | 'shared_screen_with_speaker_view' => 'recordingtype_shared_speaker', 160 | 'shared_screen_with_speaker_view(CC)' => 'recordingtype_shared_speaker_cc', 161 | 'sign_interpretation' => 'recordingtype_sign', 162 | 'speaker_view' => 'recordingtype_speaker', 163 | 'summary' => 'recordingtype_summary', 164 | 'summary_next_steps' => 'recordingtype_summary_next_steps', 165 | 'summary_smart_chapters' => 'recordingtype_summary_smart_chapters', 166 | 'timeline' => 'recordingtype_timeline', 167 | ]; 168 | 169 | // Return some default string in case new recordingtype values are added in the future. 170 | if (empty($recordingtypestringmap[$recordingtype])) { 171 | return $recordingtype; 172 | } 173 | 174 | return get_string($recordingtypestringmap[$recordingtype], 'mod_zoom'); 175 | } 176 | 177 | echo html_writer::table($table); 178 | 179 | echo $OUTPUT->footer(); 180 | -------------------------------------------------------------------------------- /recreate.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Recreate a meeting that exists on Moodle but cannot be found on Zoom. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2017 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__ . '/../../config.php'); 26 | require_once(__DIR__ . '/lib.php'); 27 | require_once(__DIR__ . '/locallib.php'); 28 | 29 | require_login(); 30 | // Additional access checks in zoom_get_instance_setup(). 31 | [$course, $cm, $zoom] = zoom_get_instance_setup(); 32 | 33 | require_sesskey(); 34 | $context = context_module::instance($cm->id); 35 | // This capability is for managing Zoom instances in general. 36 | require_capability('mod/zoom:addinstance', $context); 37 | 38 | $PAGE->set_url('/mod/zoom/recreate.php', ['id' => $cm->id]); 39 | 40 | // Create a new meeting with Zoom API to replace the missing one. 41 | // We will use the logged-in user's Zoom account to recreate, 42 | // in case the meeting's former owner no longer exists on Zoom. 43 | $zoom->host_id = zoom_get_user_id(); 44 | 45 | $trackingfields = $DB->get_records('zoom_meeting_tracking_fields', ['meeting_id' => $zoom->id]); 46 | foreach ($trackingfields as $trackingfield) { 47 | $field = $trackingfield->tracking_field; 48 | $zoom->$field = $trackingfield->value; 49 | } 50 | 51 | // Set the current zoom table entry to use the new meeting (meeting_id/etc). 52 | $response = zoom_webservice()->create_meeting($zoom, $cm->id); 53 | $zoom = populate_zoom_from_response($zoom, $response); 54 | $zoom->exists_on_zoom = ZOOM_MEETING_EXISTS; 55 | $zoom->timemodified = time(); 56 | $DB->update_record('zoom', $zoom); 57 | 58 | // Return to Zoom page. 59 | redirect( 60 | new moodle_url('/mod/zoom/view.php', ['id' => $cm->id]), 61 | get_string('recreatesuccessful', 'mod_zoom') 62 | ); 63 | -------------------------------------------------------------------------------- /report.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * List all zoom meetings. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__ . '/../../config.php'); 26 | require_once(__DIR__ . '/lib.php'); 27 | require_once(__DIR__ . '/locallib.php'); 28 | require_once(__DIR__ . '/mod_form.php'); 29 | require_once($CFG->libdir . '/moodlelib.php'); 30 | 31 | require_login(); 32 | // Additional access checks in zoom_get_instance_setup(). 33 | [$course, $cm, $zoom] = zoom_get_instance_setup(); 34 | 35 | // Check capability. 36 | $context = context_module::instance($cm->id); 37 | require_capability('mod/zoom:addinstance', $context); 38 | 39 | $PAGE->set_url('/mod/zoom/report.php', ['id' => $cm->id]); 40 | 41 | $strname = $zoom->name; 42 | $strtitle = get_string('sessions', 'mod_zoom'); 43 | $PAGE->navbar->add($strtitle); 44 | $PAGE->set_title("$course->shortname: $strname"); 45 | $PAGE->set_heading($course->fullname); 46 | $PAGE->set_pagelayout('incourse'); 47 | 48 | echo $OUTPUT->header(); 49 | echo $OUTPUT->heading($strname); 50 | echo $OUTPUT->heading($strtitle, 4); 51 | 52 | $sessions = zoom_get_sessions_for_display($zoom->id); 53 | if (!empty($sessions)) { 54 | $maskparticipantdata = get_config('zoom', 'maskparticipantdata'); 55 | $table = new html_table(); 56 | $table->head = [ 57 | get_string('title', 'mod_zoom'), 58 | get_string('starttime', 'mod_zoom'), 59 | get_string('endtime', 'mod_zoom'), 60 | get_string('duration', 'mod_zoom'), 61 | get_string('participants', 'mod_zoom'), 62 | ]; 63 | $table->align = ['left', 'left', 'left', 'left', 'left']; 64 | $format = get_string('strftimedatetimeshort', 'langconfig'); 65 | 66 | foreach ($sessions as $uuid => $meet) { 67 | $row = []; 68 | $row[] = $meet['topic']; 69 | $row[] = $meet['starttime']; 70 | $row[] = $meet['endtime']; 71 | $row[] = format_time($meet['duration']); 72 | 73 | if ($meet['count'] > 0) { 74 | if ($maskparticipantdata) { 75 | $row[] = $meet['count'] 76 | . ' [' 77 | . get_string('participantdatanotavailable', 'mod_zoom') 78 | . '] ' 79 | . $OUTPUT->help_icon('participantdatanotavailable', 'mod_zoom'); 80 | } else { 81 | $url = new moodle_url('/mod/zoom/participants.php', ['id' => $cm->id, 'uuid' => $uuid]); 82 | $row[] = html_writer::link($url, $meet['count']); 83 | } 84 | } else { 85 | $row[] = 0; 86 | } 87 | 88 | $table->data[] = $row; 89 | } 90 | } 91 | 92 | if (!empty($table->data)) { 93 | echo html_writer::table($table); 94 | } else { 95 | echo $OUTPUT->notification(get_string('nomeetinginstances', 'mod_zoom'), 'notifymessage'); 96 | } 97 | 98 | echo $OUTPUT->footer(); 99 | -------------------------------------------------------------------------------- /showrecording.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Load zoom meeting recording and add a record of the view. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 Nick Stefanski 22 | * @author 2021 Jwalit Shah 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require(__DIR__ . '/../../config.php'); 27 | require_once($CFG->libdir . '/moodlelib.php'); 28 | require_once(__DIR__ . '/locallib.php'); 29 | 30 | $meetinguuid = required_param('meetinguuid', PARAM_TEXT); 31 | $recordingstart = required_param('recordingstart', PARAM_INT); 32 | $showrecording = required_param('showrecording', PARAM_INT); 33 | 34 | if (!get_config('zoom', 'viewrecordings')) { 35 | throw new moodle_exception('recordingnotvisible', 'mod_zoom', get_string('recordingnotvisible', 'zoom')); 36 | } 37 | 38 | [$course, $cm, $zoom] = zoom_get_instance_setup(); 39 | require_login($course, true, $cm); 40 | 41 | $context = context_module::instance($cm->id); 42 | $PAGE->set_context($context); 43 | require_capability('mod/zoom:addinstance', $context); 44 | 45 | $urlparams = ['id' => $cm->id]; 46 | $url = new moodle_url('/mod/zoom/recordings.php', $urlparams); 47 | if (!confirm_sesskey()) { 48 | redirect($url, get_string('sesskeyinvalid', 'mod_zoom')); 49 | } 50 | 51 | // Find the video recording and audio only recording pair that matches the criteria. 52 | $recordings = $DB->get_records('zoom_meeting_recordings', ['meetinguuid' => $meetinguuid, 'recordingstart' => $recordingstart]); 53 | if (empty($recordings)) { 54 | throw new moodle_exception('recordingnotfound', 'mod_zoom', '', get_string('recordingnotfound', 'zoom')); 55 | } 56 | 57 | $now = time(); 58 | 59 | // Toggle the showrecording value. 60 | if ($showrecording === 1 || $showrecording === 0) { 61 | foreach ($recordings as $rec) { 62 | $rec->showrecording = $showrecording; 63 | $rec->timemodified = $now; 64 | $DB->update_record('zoom_meeting_recordings', $rec); 65 | } 66 | } 67 | 68 | redirect($url); 69 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | #page-mod-zoom-view a .btn-primary .icon { 2 | color: white; 3 | } 4 | 5 | #mod-zoom-old-meetings-header { 6 | float: left; 7 | } 8 | 9 | #mod-zoom-meeting-room-participants, 10 | #mod-zoom-meeting-room-participant-groups { 11 | list-style-type: none; 12 | } 13 | 14 | #mod-zoom-meeting-rooms-list .empty-alert { 15 | margin: auto; 16 | width: 80%; 17 | padding: 40% 0; 18 | } 19 | 20 | #mod-zoom-breakout-rooms-table { 21 | width: 100%; 22 | height: 400px; 23 | } 24 | 25 | #mod-zoom-breakout-rooms-table td:first-child { 26 | width: 20%; 27 | } 28 | 29 | #mod-zoom-breakout-rooms-table td:nth-child(2) { 30 | width: 80%; 31 | } 32 | 33 | #mod-zoom-breakout-rooms-table .delete-room { 34 | margin: -34px 5px; 35 | background: transparent; 36 | border: none; 37 | } 38 | 39 | #page-mod-zoom-participants .mimetypeicon { 40 | width: 24px; 41 | height: 24px; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /templates/breakoutrooms_room_data.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_zoom/breakoutrooms_room_data 19 | 20 | Context variables required for this template: 21 | * roomid Room ID. 22 | * roomname Room name. 23 | * courseparticipants Array of participants. 24 | * coursegroups Array of groups. 25 | 26 | Example context (json): 27 | { 28 | "roomid": "1", 29 | "roomname": "room1", 30 | "courseparticipants": [ 31 | { 32 | "participantid": "2", 33 | "participantname": "John Doe", 34 | "selected": true 35 | } 36 | ], 37 | "coursegroups": [ 38 | { 39 | "groupid": "3", 40 | "groupname": "group3", 41 | "selected": true 42 | } 43 | ] 44 | } 45 | }} 46 | 47 | 48 | 49 |
50 | {{#str}} participants, zoom {{/str}} 51 | {{> zoom/breakoutrooms_room_participants }} 52 |
53 | {{#str}} participantgroups, zoom {{/str}} 54 | {{> zoom/breakoutrooms_room_groups }} 55 | -------------------------------------------------------------------------------- /templates/breakoutrooms_room_datatoclone.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_zoom/breakoutrooms_room_datatoclone 19 | 20 | Context variables required for this template: 21 | * roomid Room ID. 22 | * roomname Room name. 23 | * courseparticipants Array of participants. 24 | * coursegroups Array of groups. 25 | 26 | Example context (json): 27 | { 28 | "roomid": "1", 29 | "roomname": "room1", 30 | "courseparticipants": [ 31 | { 32 | "participantid": "2", 33 | "participantname": "John Doe", 34 | "selected": true 35 | } 36 | ], 37 | "coursegroups": [ 38 | { 39 | "groupid": "3", 40 | "groupname": "group3", 41 | "selected": true 42 | } 43 | ] 44 | } 45 | }} 46 | 63 | -------------------------------------------------------------------------------- /templates/breakoutrooms_room_groups.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_zoom/breakoutrooms_room_groups 19 | 20 | Context variables required for this template: 21 | * roomid Room ID. 22 | * roomname Room name. 23 | * coursegroups Array of groups. 24 | 25 | Example context (json): 26 | { 27 | "roomid": "1", 28 | "roomname": "room1", 29 | "coursegroups": [ 30 | { 31 | "groupid": "3", 32 | "groupname": "group3", 33 | "selected": true 34 | } 35 | ] 36 | } 37 | }} 38 |
39 |
40 | 45 |
46 |
47 | -------------------------------------------------------------------------------- /templates/breakoutrooms_room_participants.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_zoom/breakoutrooms_room_participants 19 | 20 | Context variables required for this template: 21 | * roomid Room ID. 22 | * roomname Room name. 23 | * courseparticipants Array of participants. 24 | 25 | Example context (json): 26 | { 27 | "roomid": "1", 28 | "roomname": "room1", 29 | "courseparticipants": [ 30 | { 31 | "participantid": "2", 32 | "participantname": "John Doe", 33 | "selected": true 34 | } 35 | ] 36 | } 37 | }} 38 |
39 |
40 | 45 |
46 |
47 | -------------------------------------------------------------------------------- /templates/breakoutrooms_rooms.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_zoom/breakoutrooms_rooms 19 | 20 | Context variables required for this template: 21 | * roomscount Number of rooms. 22 | * rooms Array of rooms. 23 | * roomtoclone Room that is being cloned. 24 | 25 | Example context (json): 26 | { 27 | "roomscount": 1, 28 | "rooms": [ 29 | { 30 | "roomid": "1", 31 | "roomname": "room1", 32 | "roomactive": true, 33 | "courseparticipants": [ 34 | { 35 | "participantid": "2", 36 | "participantname": "John Doe", 37 | "selected": true 38 | } 39 | ], 40 | "coursegroups": [ 41 | { 42 | "groupid": "3", 43 | "groupname": "group3", 44 | "selected": true 45 | } 46 | ] 47 | } 48 | ], 49 | "roomtoclone": [ 50 | { 51 | "toclone": "toclone", 52 | "courseparticipants": [ 53 | { 54 | "participantid": "2", 55 | "participantname": "John Doe" 56 | } 57 | ], 58 | "coursegroups": [ 59 | { 60 | "groupid": "3", 61 | "groupname": "group3" 62 | } 63 | ] 64 | } 65 | ] 66 | } 67 | }} 68 | 69 | 70 | 99 | 110 | 111 |
71 |
72 |
73 | {{#str}} rooms, zoom {{/str}} 74 | 77 |
78 | 90 | 97 |
98 |
100 |
101 |
102 | {{#rooms}} 103 |
104 | {{> zoom/breakoutrooms_room_data }} 105 |
106 | {{/rooms}} 107 |
108 |
109 |
112 | 113 | {{#roomtoclone}} 114 | {{> zoom/breakoutrooms_room_datatoclone }} 115 | {{/roomtoclone}} 116 | -------------------------------------------------------------------------------- /templates/mobile_view_page_ionic3.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_zoom/mobile_view_page_ionic3 19 | 20 | Page to view a zoom meeting 21 | 22 | Classes required for JS: 23 | * none 24 | 25 | Data attributes required for JS: 26 | * none 27 | 28 | Example context (json): 29 | { 30 | "zoom": { 31 | "intro": "Introduction String", 32 | "password": "9836451", 33 | "recurring": 1, 34 | "webinar": 1, 35 | "option_host_video": 1, 36 | "option_jbh": 1, 37 | "option_participants_video": 1 38 | }, 39 | "available": true, 40 | "status": "Finished", 41 | "start_time": "Tuesday, June 29, 2021, 1:30 PM", 42 | "duration": "1 hour", 43 | "option_audio": "Computer audio and Telephone", 44 | "cmid": 3, 45 | "courseid": 3 46 | } 47 | }} 48 | {{=<% %>=}} 49 |
50 | 51 | 52 | 53 | <%#available%> 54 | 55 | 58 | 59 | <%/available%> 60 | <%^available%> 61 | 62 |

{{ 'plugin.mod_zoom.unavailable' | translate }}

63 |
64 | <%/available%> 65 | 66 | <%#zoom.recurring%> 67 | 68 |

{{ 'plugin.mod_zoom.recurringmeetinglong' | translate }}

69 |
70 | <%/zoom.recurring%> 71 | <%^zoom.recurring%> 72 | 73 |

{{ 'plugin.mod_zoom.meeting_time' | translate }}

74 |

<% start_time %>

75 |
76 | 77 |

{{ 'plugin.mod_zoom.duration' | translate }}

78 |

<% duration %>

79 |
80 | <%/zoom.recurring%> 81 | 82 | 83 |

{{ 'plugin.mod_zoom.passwordprotected' | translate }}

84 | <%#zoom.password%>

{{ 'core.yes' | translate }}

<%/zoom.password%> 85 | <%^zoom.password%>

{{ 'core.no' | translate }}

<%/zoom.password%> 86 |
87 | 88 | <%^zoom.webinar%> 89 | 90 |

{{ 'plugin.mod_zoom.joinbeforehost' | translate }}

91 | <%#zoom.option_jbh%>

{{ 'core.yes' | translate }}

<%/zoom.option_jbh%> 92 | <%^zoom.option_jbh%>

{{ 'core.no' | translate }}

<%/zoom.option_jbh%> 93 |
94 | 95 |

{{ 'plugin.mod_zoom.starthostjoins' | translate }}

96 | <%#zoom.option_host_video%>

{{ 'core.yes' | translate }}

<%/zoom.option_host_video%> 97 | <%^zoom.option_host_video%>

{{ 'core.no' | translate }}

<%/zoom.option_host_video%> 98 |
99 | 100 |

{{ 'plugin.mod_zoom.startpartjoins' | translate }}

101 | <%#zoom.option_participants_video%>

{{ 'core.yes' | translate }}

<%/zoom.option_participants_video%> 102 | <%^zoom.option_participants_video%>

{{ 'core.no' | translate }}

<%/zoom.option_participants_video%> 103 |
104 | <%/zoom.webinar%> 105 | 106 | 107 |

{{ 'plugin.mod_zoom.option_audio' | translate }}

108 |

<% option_audio %>

109 |
110 | 111 | <%^zoom.recurring%> 112 | 113 |

{{ 'plugin.mod_zoom.status' | translate }}

114 |

<% status %>

115 |
116 | <%/zoom.recurring%> 117 |
118 |
119 | -------------------------------------------------------------------------------- /templates/mobile_view_page_latest.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_zoom/mobile_view_page_latest 19 | 20 | Page to view a zoom meeting 21 | 22 | Classes required for JS: 23 | * none 24 | 25 | Data attributes required for JS: 26 | * none 27 | 28 | Example context (json): 29 | { 30 | "zoom": { 31 | "intro": "Introduction String", 32 | "password": "9836451", 33 | "recurring": 1, 34 | "webinar": 1, 35 | "option_host_video": 1, 36 | "option_jbh": 1, 37 | "option_participants_video": 1 38 | }, 39 | "available": true, 40 | "status": "Finished", 41 | "start_time": "Tuesday, June 29, 2021, 1:30 PM", 42 | "duration": "1 hour", 43 | "option_audio": "Computer audio and Telephone", 44 | "cmid": 3, 45 | "courseid": 3 46 | } 47 | }} 48 | {{=<% %>=}} 49 |
50 | <%#canusemoduleinfo%> 51 | 52 | 53 | <%/canusemoduleinfo%> 54 | <%^canusemoduleinfo%> 55 | 56 | <%/canusemoduleinfo%> 57 | 58 | 59 | <%#available%> 60 | 61 | {{ 'plugin.mod_zoom.join_meeting' | translate }} 62 | 63 | <%/available%> 64 | <%^available%> 65 | 66 | 67 |

{{ 'plugin.mod_zoom.unavailable' | translate }}

68 |
69 |
70 | <%/available%> 71 | 72 | <%#zoom.recurring%> 73 | 74 | 75 |

{{ 'plugin.mod_zoom.recurringmeetinglong' | translate }}

76 |
77 |
78 | <%/zoom.recurring%> 79 | <%^zoom.recurring%> 80 | 81 | 82 |

{{ 'plugin.mod_zoom.meeting_time' | translate }}

83 |

<% start_time %>

84 |
85 |
86 | 87 | 88 |

{{ 'plugin.mod_zoom.duration' | translate }}

89 |

<% duration %>

90 |
91 |
92 | <%/zoom.recurring%> 93 | 94 | 95 | 96 |

{{ 'plugin.mod_zoom.passwordprotected' | translate }}

97 | <%#zoom.password%>

{{ 'core.yes' | translate }}

<%/zoom.password%> 98 | <%^zoom.password%>

{{ 'core.no' | translate }}

<%/zoom.password%> 99 |
100 |
101 | 102 | <%^zoom.webinar%> 103 | 104 | 105 |

{{ 'plugin.mod_zoom.joinbeforehost' | translate }}

106 | <%#zoom.option_jbh%>

{{ 'core.yes' | translate }}

<%/zoom.option_jbh%> 107 | <%^zoom.option_jbh%>

{{ 'core.no' | translate }}

<%/zoom.option_jbh%> 108 |
109 |
110 | 111 | 112 |

{{ 'plugin.mod_zoom.starthostjoins' | translate }}

113 | <%#zoom.option_host_video%>

{{ 'core.yes' | translate }}

<%/zoom.option_host_video%> 114 | <%^zoom.option_host_video%>

{{ 'core.no' | translate }}

<%/zoom.option_host_video%> 115 |
116 |
117 | 118 | 119 |

{{ 'plugin.mod_zoom.startpartjoins' | translate }}

120 | <%#zoom.option_participants_video%>

{{ 'core.yes' | translate }}

<%/zoom.option_participants_video%> 121 | <%^zoom.option_participants_video%>

{{ 'core.no' | translate }}

<%/zoom.option_participants_video%> 122 |
123 |
124 | <%/zoom.webinar%> 125 | 126 | 127 | 128 |

{{ 'plugin.mod_zoom.option_audio' | translate }}

129 |

<% option_audio %>

130 |
131 |
132 | 133 | <%^zoom.recurring%> 134 | 135 | 136 |

{{ 'plugin.mod_zoom.status' | translate }}

137 |

<% status %>

138 |
139 |
140 | <%/zoom.recurring%> 141 |
142 |
143 | -------------------------------------------------------------------------------- /tests/advanced_passcode_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for supporting advanced password requirements in Zoom. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | use basic_testcase; 28 | 29 | /** 30 | * PHPunit testcase class. 31 | */ 32 | final class advanced_passcode_test extends basic_testcase { 33 | /** 34 | * Fake data from get_user_security_settings(). 35 | * @var object 36 | */ 37 | private $zoomdata; 38 | 39 | /** 40 | * Check regular expressions. Compatibility support for PHPUnit. 41 | * 42 | * @param string $pattern Regular expression. 43 | * @param string $string String. 44 | * @param string $message Message. 45 | */ 46 | public static function assert_regexp($pattern, $string, $message = ''): void { 47 | if (method_exists('basic_testcase', 'assertMatchesRegularExpression')) { 48 | parent::assertMatchesRegularExpression($pattern, $string, $message); 49 | } else { 50 | parent::assertRegExp($pattern, $string, $message); 51 | } 52 | } 53 | 54 | /** 55 | * Setup to ensure that fixtures are loaded. 56 | */ 57 | public static function setUpBeforeClass(): void { 58 | global $CFG; 59 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 60 | parent::setUpBeforeClass(); 61 | } 62 | 63 | /** 64 | * Tests that a default password of 6 numbers is created when settings are null. 65 | * @covers ::zoom_create_default_passcode 66 | */ 67 | public function test_settings_default(): void { 68 | $this->zoomdata = (object) webservice::DEFAULT_MEETING_PASSWORD_REQUIREMENT; 69 | 70 | $passcode = zoom_create_default_passcode($this->zoomdata); 71 | $this->assertEquals(strlen($passcode), 6); 72 | $this->assertTrue(ctype_digit($passcode)); 73 | } 74 | 75 | /** 76 | * Tests that a password has the given minimum length. 77 | * @covers ::zoom_create_default_passcode 78 | */ 79 | public function test_settings_length(): void { 80 | $data = [ 81 | 'length' => 8, 82 | 'have_letter' => false, 83 | 'have_upper_and_lower_characters' => false, 84 | 'have_special_character' => false, 85 | ]; 86 | $this->zoomdata = (object) $data; 87 | 88 | $passcode = zoom_create_default_passcode($this->zoomdata); 89 | $this->assertEquals(strlen($passcode), 8); 90 | $this->assertTrue(ctype_digit($passcode)); 91 | } 92 | 93 | /** 94 | * Tests that a password is all numbers when the setting is specified. 95 | * @covers ::zoom_create_default_passcode 96 | */ 97 | public function test_settings_only_numeric(): void { 98 | $data = [ 99 | 'length' => 10, 100 | 'have_letter' => false, 101 | 'have_upper_and_lower_characters' => false, 102 | 'have_special_character' => false, 103 | 'only_allow_numeric' => true, 104 | ]; 105 | $this->zoomdata = (object) $data; 106 | 107 | $passcode = zoom_create_default_passcode($this->zoomdata); 108 | $this->assertEquals(strlen($passcode), 10); 109 | $this->assertTrue(ctype_digit($passcode)); 110 | } 111 | 112 | /** 113 | * Tests that a password has a letter when the setting is specified. 114 | * @covers ::zoom_create_default_passcode 115 | */ 116 | public function test_settings_letter(): void { 117 | $data = [ 118 | 'length' => null, 119 | 'have_letter' => true, 120 | 'have_upper_and_lower_characters' => false, 121 | 'have_special_character' => null, 122 | ]; 123 | $this->zoomdata = (object) $data; 124 | 125 | $passcode = zoom_create_default_passcode($this->zoomdata); 126 | $this->assertEquals(strlen($passcode), 6); 127 | $this->assert_regexp('/\d/', $passcode); 128 | $this->assert_regexp('/[a-zA-Z]/', $passcode); 129 | } 130 | 131 | /** 132 | * Tests that a password has uppercase and lowercase letters when the setting is specified. 133 | * @covers ::zoom_create_default_passcode 134 | */ 135 | public function test_settings_upper_and_lower_letters(): void { 136 | $data = [ 137 | 'length' => null, 138 | 'have_letter' => true, 139 | 'have_upper_and_lower_characters' => true, 140 | 'have_special_character' => null, 141 | ]; 142 | $this->zoomdata = (object) $data; 143 | 144 | $passcode = zoom_create_default_passcode($this->zoomdata); 145 | $this->assertEquals(strlen($passcode), 6); 146 | $this->assert_regexp('/\d/', $passcode); 147 | $this->assert_regexp('/[A-Z]/', $passcode); 148 | $this->assert_regexp('/[a-z]/', $passcode); 149 | } 150 | 151 | /** 152 | * Tests that a password has a special character when the setting is specified. 153 | * @covers ::zoom_create_default_passcode 154 | */ 155 | public function test_settings_special_character(): void { 156 | $data = [ 157 | 'length' => null, 158 | 'have_letter' => null, 159 | 'have_upper_and_lower_characters' => null, 160 | 'have_special_character' => true, 161 | ]; 162 | $this->zoomdata = (object) $data; 163 | 164 | $passcode = zoom_create_default_passcode($this->zoomdata); 165 | $this->assertEquals(strlen($passcode), 6); 166 | $this->assert_regexp('/\d/', $passcode); 167 | $this->assert_regexp('/[^a-zA-Z\d]/', $passcode); 168 | } 169 | 170 | /** 171 | * Tests that a password has correct length, a letter, and a special character when setting is specified. 172 | * @covers ::zoom_create_default_passcode 173 | */ 174 | public function test_settings_all(): void { 175 | $data = [ 176 | 'length' => 7, 177 | 'have_letter' => true, 178 | 'have_upper_and_lower_characters' => true, 179 | 'have_special_character' => true, 180 | ]; 181 | $this->zoomdata = (object) $data; 182 | 183 | $passcode = zoom_create_default_passcode($this->zoomdata); 184 | $this->assertEquals(strlen($passcode), 7); 185 | $this->assert_regexp('/\d/', $passcode); 186 | $this->assert_regexp('/[a-zA-Z]/', $passcode); 187 | $this->assert_regexp('/[^a-zA-Z\d]/', $passcode); 188 | } 189 | 190 | /** 191 | * Tests that the password description is correct when all settings are present. 192 | * @covers ::zoom_create_passcode_description 193 | */ 194 | public function test_pasword_description_all(): void { 195 | $data = [ 196 | 'length' => 9, 197 | 'have_letter' => true, 198 | 'have_number' => true, 199 | 'have_upper_and_lower_characters' => true, 200 | 'have_special_character' => true, 201 | 'consecutive_characters_length' => 4, 202 | 'only_allow_numeric' => false, 203 | ]; 204 | $this->zoomdata = (object) $data; 205 | 206 | $description = zoom_create_passcode_description($this->zoomdata); 207 | $expected = 'Passcode must include both lower and uppercase characters. Passcode must contain at least 1 number. ' . 208 | 'Passcode must have at least 1 special character (@-_*). Minimum of 9 character(s). Maximum of 3 consecutive ' . 209 | 'characters (abcd, 1111, 1234, etc.). Maximum of 10 characters.'; 210 | $this->assertEquals($description, $expected); 211 | } 212 | 213 | /** 214 | * Tests that the password description is correct when the only numeric option is present. 215 | * @covers ::zoom_create_passcode_description 216 | */ 217 | public function test_pasword_description_only_numeric(): void { 218 | $data = [ 219 | 'length' => 8, 220 | 'have_letter' => false, 221 | 'have_number' => true, 222 | 'have_upper_and_lower_characters' => false, 223 | 'have_special_character' => false, 224 | 'consecutive_characters_length' => 0, 225 | 'only_allow_numeric' => true, 226 | ]; 227 | $this->zoomdata = (object) $data; 228 | 229 | $description = zoom_create_passcode_description($this->zoomdata); 230 | $expected = 'Passcode may only contain numbers and no other characters. Minimum of 8 character(s). ' . 231 | 'Maximum of 10 characters.'; 232 | $this->assertEquals($description, $expected); 233 | } 234 | 235 | /** 236 | * Tests that the password description is correct when the default settings are present. 237 | * @covers ::zoom_create_passcode_description 238 | */ 239 | public function test_pasword_description_default(): void { 240 | $this->zoomdata = (object) webservice::DEFAULT_MEETING_PASSWORD_REQUIREMENT; 241 | 242 | $description = zoom_create_passcode_description($this->zoomdata); 243 | $expected = 'Passcode may only contain the following characters: [a-z A-Z 0-9 @ - _ *]. Maximum of 10 characters.'; 244 | $this->assertEquals($description, $expected); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /tests/error_handling_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for error handling for zoom exceptions. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2019 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | use basic_testcase; 28 | 29 | /** 30 | * PHPunit testcase class. 31 | */ 32 | final class error_handling_test extends basic_testcase { 33 | /** 34 | * Exception for when the meeting isn't found on Zoom. 35 | * @var not_found_exception 36 | */ 37 | private $meetingnotfoundexception; 38 | 39 | /** 40 | * Exception for when the user isn't found on Zoom. 41 | * @var not_found_exception 42 | */ 43 | private $usernotfoundexception; 44 | 45 | /** 46 | * Exception for when the user is found in the system but they haven't 47 | * accepted their invite, so they don't have permissions to do what was 48 | * requested. 49 | * @var not_found_exception 50 | */ 51 | private $invaliduserexception; 52 | 53 | /** 54 | * Exception for when the meeting isn't found on Zoom. 55 | * @var not_found_exception 56 | */ 57 | private $othererrorcodeexception; 58 | 59 | /** 60 | * Setup to ensure that fixtures are loaded. 61 | */ 62 | public static function setUpBeforeClass(): void { 63 | global $CFG; 64 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 65 | parent::setUpBeforeClass(); 66 | } 67 | 68 | /** 69 | * Setup before every test. 70 | */ 71 | public function setUp(): void { 72 | parent::setUp(); 73 | $this->meetingnotfoundexception = new not_found_exception('meeting not found', 3001); 74 | $this->usernotfoundexception = new not_found_exception('user not found', 1001); 75 | $this->invaliduserexception = new not_found_exception('invalid user found', 1120); 76 | $this->othererrorcodeexception = new not_found_exception('other exception', -1); 77 | } 78 | 79 | /** 80 | * Tests that uuid are encoded properly for use in web service calls. 81 | * @covers ::zoom_is_meeting_gone_error 82 | * @covers ::zoom_is_user_not_found_error 83 | */ 84 | public function test_correct_error_recognition(): void { 85 | // Check meeting not found behavior. 86 | $this->assertTrue(zoom_is_meeting_gone_error($this->meetingnotfoundexception)); 87 | $this->assertTrue(zoom_is_meeting_gone_error($this->usernotfoundexception)); 88 | $this->assertTrue(zoom_is_meeting_gone_error($this->invaliduserexception)); 89 | $this->assertFalse(zoom_is_meeting_gone_error($this->othererrorcodeexception)); 90 | 91 | // Check user not found behavior. 92 | $this->assertTrue(zoom_is_user_not_found_error($this->usernotfoundexception)); 93 | $this->assertTrue(zoom_is_user_not_found_error($this->invaliduserexception)); 94 | $this->assertFalse(zoom_is_user_not_found_error($this->meetingnotfoundexception)); 95 | $this->assertFalse(zoom_is_user_not_found_error($this->othererrorcodeexception)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/generator/lib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Zoom module test data generator class 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | class mod_zoom_generator extends testing_module_generator { 25 | /** 26 | * Creates new Zoom module instance. 27 | * @param array|stdClass $record 28 | * @param array|null $options 29 | * @return stdClass Zoom instance 30 | */ 31 | public function create_instance($record = null, ?array $options = null) { 32 | global $CFG; 33 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 34 | 35 | set_config('clientid', 'test', 'zoom'); 36 | set_config('clientsecret', 'test', 'zoom'); 37 | set_config('accountid', 'test', 'zoom'); 38 | 39 | // Mock Zoom data for testing. 40 | $defaultzoomsettings = [ 41 | 'grade' => 0, 42 | 'name' => 'Test Zoom Meeting', 43 | 'meeting_id' => 1, 44 | 'host_id' => 'test', 45 | 'meetingcode' => '', 46 | 'webinar' => 0, 47 | 'option_host_video' => 0, 48 | 'option_audio' => 0, 49 | 'recurring' => 0, 50 | 'option_participants_video' => 0, 51 | 'option_jbh' => 0, 52 | 'option_waiting_room' => 0, 53 | 'option_mute_upon_entry' => 0, 54 | 'start_time' => mktime(0, 0, 0, 2, 22, 2021), 55 | 'duration' => 60, 56 | 'exists_on_zoom' => ZOOM_MEETING_EXPIRED, 57 | ]; 58 | 59 | $record = (object) (array) $record; 60 | foreach ($defaultzoomsettings as $name => $value) { 61 | if (!isset($record->{$name})) { 62 | $record->{$name} = $value; 63 | } 64 | } 65 | 66 | return parent::create_instance($record, $options); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/mod_zoom_grade_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for supporting advanced password requirements in Zoom. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2020 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_zoom; 26 | 27 | use advanced_testcase; 28 | 29 | /** 30 | * PHPunit testcase class. 31 | */ 32 | final class mod_zoom_grade_test extends advanced_testcase { 33 | /** 34 | * @var \stdClass Course record. 35 | */ 36 | private $course; 37 | 38 | /** 39 | * @var \stdClass User record for teacher. 40 | */ 41 | private $teacher; 42 | 43 | /** 44 | * @var \stdClass User record for student. 45 | */ 46 | private $student; 47 | 48 | /** 49 | * @var \mod_zoom_generator Plugin generator for tests. 50 | */ 51 | private $generator; 52 | 53 | /** 54 | * Setup to ensure that fixtures are loaded. 55 | */ 56 | public static function setUpBeforeClass(): void { 57 | global $CFG; 58 | require_once($CFG->dirroot . '/mod/zoom/lib.php'); 59 | require_once($CFG->dirroot . '/mod/zoom/locallib.php'); 60 | parent::setUpBeforeClass(); 61 | } 62 | 63 | /** 64 | * Setup before every test. 65 | */ 66 | public function setUp(): void { 67 | parent::setUp(); 68 | $this->resetAfterTest(); 69 | $this->setAdminUser(); 70 | 71 | $this->course = $this->getDataGenerator()->create_course(); 72 | $this->teacher = $this->getDataGenerator()->create_and_enrol($this->course, 'teacher'); 73 | $this->student = $this->getDataGenerator()->create_and_enrol($this->course, 'student'); 74 | $this->generator = $this->getDataGenerator()->get_plugin_generator('mod_zoom'); 75 | } 76 | 77 | /** 78 | * Tests that Zoom grades can be added and updated in the gradebook. 79 | * @covers ::zoom_grade_item_update 80 | */ 81 | public function test_grade_added(): void { 82 | $params['course'] = $this->course->id; 83 | $params['grade'] = 100; 84 | 85 | $instance = $this->generator->create_instance($params); 86 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id); 87 | 88 | // Gradebook should be empty. 89 | $this->assertEquals(0, count($gradebook->items[0]->grades)); 90 | 91 | // Insert grade for student. 92 | $studentgrade = ['userid' => $this->student->id, 'rawgrade' => 50]; 93 | zoom_grade_item_update($instance, $studentgrade); 94 | 95 | // Gradebook should contain a grade for student. 96 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id, $this->student->id); 97 | $this->assertEquals(1, count($gradebook->items[0]->grades)); 98 | $this->assertEquals(50, $gradebook->items[0]->grades[$this->student->id]->grade); 99 | 100 | // Update grade for student. 101 | $studentgrade = ['userid' => $this->student->id, 'rawgrade' => 75]; 102 | zoom_grade_item_update($instance, $studentgrade); 103 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id, $this->student->id); 104 | 105 | // Verify grade has been updated. 106 | $this->assertEquals(1, count($gradebook->items[0]->grades)); 107 | $this->assertEquals(75, $gradebook->items[0]->grades[$this->student->id]->grade); 108 | } 109 | 110 | /** 111 | * Tests that the Zoom grade type cannot be changed to NONE if grades are already inputted. 112 | * @covers ::zoom_grade_item_update 113 | */ 114 | public function test_grade_type_not_none(): void { 115 | $params['course'] = $this->course->id; 116 | $params['grade'] = 100; 117 | 118 | $instance = $this->generator->create_instance($params); 119 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id); 120 | 121 | // Gradebook should be empty. 122 | $this->assertEquals(0, count($gradebook->items[0]->grades)); 123 | 124 | // Insert grade for student. 125 | $studentgrade = ['userid' => $this->student->id, 'rawgrade' => 100]; 126 | zoom_grade_item_update($instance, $studentgrade); 127 | 128 | // Gradebook should contain a grade for student. 129 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id, $this->student->id); 130 | $this->assertEquals(1, count($gradebook->items[0]->grades)); 131 | 132 | // Try to change grade type to NONE. 133 | $instance->grade = 0; 134 | zoom_grade_item_update($instance); 135 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id); 136 | 137 | // Verify grade type is not changed. 138 | $this->assertEquals(100, $gradebook->items[0]->grademax); 139 | } 140 | 141 | /** 142 | * Tests that the Zoom grades can be deleted. 143 | * @covers ::zoom_grade_item_delete 144 | */ 145 | public function test_grade_delete(): void { 146 | $params['course'] = $this->course->id; 147 | $params['grade'] = 100; 148 | 149 | $instance = $this->generator->create_instance($params); 150 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id); 151 | 152 | // Gradebook should be empty. 153 | $this->assertEquals(0, count($gradebook->items[0]->grades)); 154 | 155 | // Insert grade for student. 156 | $studentgrade = ['userid' => $this->student->id, 'rawgrade' => 100]; 157 | zoom_grade_item_update($instance, $studentgrade); 158 | 159 | // Gradebook should contain a grade for student. 160 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id, $this->student->id); 161 | $this->assertEquals(1, count($gradebook->items[0]->grades)); 162 | 163 | // Delete the grade items. 164 | zoom_grade_item_delete($instance); 165 | $gradebook = grade_get_grades($this->course->id, 'mod', 'zoom', $instance->id); 166 | 167 | // Verify gradebook is empty. 168 | $this->assertEmpty($gradebook->items); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /upgrade.txt: -------------------------------------------------------------------------------- 1 | == v5.4.0 == 2 | 3 | - New setting `zoom/sendicalnotifications` 4 | - New message `mod_zoom/ical_notifications` 5 | - New table `zoom_ical_notifications` 6 | - New task `mod_zoom\task\send_ical_notifications` 7 | 8 | == v5.3.0 == 9 | 10 | - New setting `zoom/protectedgroups` 11 | - Optional OAuth scope `group:read:list_groups:admin` or `group:read:admin` 12 | 13 | == v5.2.1 == 14 | 15 | - Document Zoom's new granular OAuth scopes. 16 | 17 | == v5.2.0 == 18 | 19 | - New settings `zoom/gradingmethod`, `zoom/unamedisplay` 20 | - New per activity setting `grading_method` 21 | 22 | == v5.0.0 == 23 | 24 | - Drop support for JWT authentication 25 | - Require PHP 7.1+ (Moodle 3.7+) 26 | - Drop Moodle 3.4 mobile support 27 | 28 | == v4.10.0 == 29 | 30 | - New setting `zoom/instanceusers` 31 | 32 | == v4.9.0 == 33 | 34 | - New setting `zoom/defaultregistration` 35 | - New per activity setting `registration` 36 | - Removed OAuth scope: `account:read:admin` 37 | 38 | == v4.8.0 == 39 | 40 | - New settings `zoom/accountid`, `zoom/clientid`, `zoom/clientsecret` 41 | - Reminder: You must [switch from JWT to Server-to-Server OAuth by June 2023](https://developers.zoom.us/docs/internal-apps/jwt-faq/). 42 | 43 | == v4.7.0 == 44 | 45 | - New settings `zoom/recordingoption`, `zoom/allowrecordingchangeoption` 46 | - New per activity setting `option_auto_recording` 47 | 48 | == v4.4.0 == 49 | 50 | - New settings `zoom/defaultshowschedule`, `zoom/defaultshowsecurity`, `zoom/defaultshowmedia` 51 | - New per activity settings `show_schedule`, `show_security`, `show_media` 52 | - New setting `zoom/webinardefault` 53 | 54 | == v4.3 == 55 | 56 | - New setting `zoom/viewrecordings` 57 | - New per activity setting `recordings_visible_default` 58 | 59 | == v4.2 == 60 | 61 | - New setting `zoom/defaulttrackingfields` 62 | 63 | == v4.1 == 64 | 65 | - New setting `zoom/apiidentifier` 66 | - New setting `zoom/apiendpoint` 67 | 68 | == v4.0 == 69 | 70 | - New setting `zoom/invitationremoveicallink` 71 | - Backward incompatible: exported iCal events now match Moodle's uid format 72 | 73 | == v3.7 == 74 | 75 | - New capabilities `mod/zoom:viewjoinurl` and `mod/zoom:viewdialin` 76 | 77 | == v3.5 == 78 | 79 | - Added new settings for E2EE, Webinars, Alternative hosts, Download iCal, 80 | Meeting capacity warning, and Enable meeting links 81 | 82 | == v3.4 == 83 | 84 | - Requiring passcodes is now a site wide configuration 85 | 86 | == v3.1 == 87 | 88 | - Added site config to mask participant data from appearing in reports (useful for sites that mask participant data, e.g., for HIPAA) 89 | 90 | == v3.0 == 91 | 92 | - Added more meeting options: Mute upon entry, Enable waiting room, Only authenticated users. 93 | - Added a new setting 'proxyurl' that can be used to set a proxy as hostname:port. 94 | 95 | == v2.0 == 96 | 97 | - Updated to support Zoom API V2 98 | - Support for alternative hosts 99 | 100 | == v1.4 == 101 | 102 | - Added support for webinars. 103 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Defines the version and other meta-info about the plugin. 19 | * 20 | * @package mod_zoom 21 | * @copyright 2015 UC Regents 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $plugin->component = 'mod_zoom'; 28 | $plugin->version = 2025052000; 29 | $plugin->release = 'v5.4.0'; 30 | $plugin->requires = 2019052000; 31 | $plugin->maturity = MATURITY_STABLE; 32 | $plugin->cron = 0; 33 | --------------------------------------------------------------------------------