├── .github ├── PULL_REQUEST_TEMPLATE.txt └── workflows │ └── ci.yml ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── acp.php ├── ajax.php ├── classes ├── adminsetting │ ├── adminconsent.php │ ├── courseresetteams.php │ ├── coursesync.php │ ├── detectoidc.php │ ├── healthcheck.php │ ├── moodlesetup.js │ ├── moodlesetup.php │ ├── serviceresource.js │ ├── serviceresource.php │ ├── tabs.php │ ├── toollink.php │ ├── usersynccreationrestriction.php │ ├── usersyncoptions.php │ ├── verifysetup.js │ └── verifysetup.php ├── event │ ├── api_call_failed.php │ ├── calendar_subscribed.php │ └── calendar_unsubscribed.php ├── feature │ ├── calsync │ │ ├── form │ │ │ ├── element │ │ │ │ └── calendar.php │ │ │ └── subscriptions.php │ │ ├── main.php │ │ ├── observers.php │ │ └── task │ │ │ ├── importfromoutlook.php │ │ │ └── syncoldevents.php │ ├── cohortsync │ │ └── main.php │ ├── courserequest │ │ └── main.php │ ├── coursesync │ │ ├── main.php │ │ ├── observers.php │ │ └── utils.php │ ├── sds │ │ ├── task │ │ │ └── sync.php │ │ └── utils.php │ ├── userconnections │ │ ├── filtering.php │ │ └── table.php │ └── usersync │ │ └── main.php ├── form │ ├── cohortsync.php │ ├── courserequestform.php │ ├── manualusermatch.php │ ├── teamsconnection.php │ ├── teamstabconfiguration.php │ └── usermatch.php ├── healthcheck │ ├── healthcheckinterface.php │ └── ratelimit.php ├── httpclient.php ├── httpclientinterface.php ├── oauth2 │ ├── apptoken.php │ ├── clientdata.php │ └── token.php ├── obj │ └── o365user.php ├── observers.php ├── page │ ├── acp.php │ ├── ajax.php │ ├── base.php │ └── ucp.php ├── privacy │ └── provider.php ├── rest │ ├── o365api.php │ └── unified.php ├── task │ ├── cohortsync.php │ ├── coursemembershipsync.php │ ├── coursesync.php │ ├── groupmembershipsync.php │ ├── notifysecretexpiry.php │ ├── processcourserequestapproval.php │ ├── processmatchqueue.php │ ├── updatecourserequeststatus.php │ └── usersync.php ├── tests │ └── mockhttpclient.php ├── utils.php └── webservices │ ├── create_onenoteassignment.php │ ├── delete_onenoteassignment.php │ ├── exception │ ├── assignnotfound.php │ ├── couldnotsavegrade.php │ ├── invalidassignment.php │ ├── modulenotfound.php │ └── sectionnotfound.php │ ├── read_assignments.php │ ├── read_courseusers.php │ ├── read_onenoteassignment.php │ ├── read_teachercourses.php │ ├── update_grade.php │ ├── update_onenoteassignment.php │ └── utils.php ├── cohortsync.php ├── courserequest.php ├── db ├── access.php ├── caches.php ├── events.php ├── install.php ├── install.xml ├── services.php ├── tasks.php └── upgrade.php ├── export_manifest.php ├── font ├── segoeui.ttf ├── segoeuib.ttf ├── segoeuil.ttf ├── segoeuisl.ttf └── seguisb.ttf ├── lang ├── cs │ └── local_o365.php ├── de │ └── local_o365.php ├── en │ └── local_o365.php ├── es │ └── local_o365.php ├── fi │ └── local_o365.php ├── fr │ └── local_o365.php ├── it │ └── local_o365.php ├── ja │ └── local_o365.php ├── nl │ └── local_o365.php ├── pl │ └── local_o365.php └── pt_br │ └── local_o365.php ├── lib.php ├── pix ├── color.png ├── moodle_app_id.png ├── o365color.png ├── onenotecolor.png ├── outline.png ├── outlookcolor.png ├── spinner.gif └── teams_app.png ├── scripts ├── Moodle-EntraID-PowerShell.zip └── README.md ├── settings.php ├── sso_end.php ├── sso_login.php ├── sso_start.php ├── styles.css ├── teams_tab.php ├── teams_tab_configuration.php ├── teams_tab_redirect.php ├── tests ├── coursesyncutils_test.php ├── observers_test.php ├── privacy_provider_test.php ├── token_test.php ├── usersync_test.php ├── webservices_onenoteassignment_test.php └── webservices_utils_test.php ├── ucp.php └── version.php /.github/PULL_REQUEST_TEMPLATE.txt: -------------------------------------------------------------------------------- 1 | *** PLEASE DO NOT OPEN PULL REQUESTS IN THIS REPO *** 2 | 3 | This is a read-only repository for Moodle plugins directory release process. All developments are carried out in the main project repository at https://github.com/microsoft/o365-moodle. Please create your pull requests there. 4 | 5 | Thank you. 6 | 7 | -- -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Moodle Plugin CI for auth_oidc 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - 'MOODLE_*_STABLE' 11 | pull_request: 12 | 13 | jobs: 14 | check: 15 | runs-on: ubuntu-latest 16 | 17 | services: 18 | postgres: 19 | image: postgres:13 20 | env: 21 | POSTGRES_USER: 'postgres' 22 | POSTGRES_HOST_AUTH_METHOD: 'trust' 23 | ports: 24 | - 5432:5432 25 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 26 | 27 | mariadb: 28 | image: mariadb:10 29 | env: 30 | MYSQL_USER: 'root' 31 | MYSQL_ALLOW_EMPTY_PASSWORD: "true" 32 | MYSQL_CHARACTER_SET_SERVER: "utf8mb4" 33 | MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci" 34 | MYSQL_INNODB_FILE_PER_TABLE: "1" 35 | MYSQL_INNODB_FILE_FORMAT: "Barracuda" 36 | ports: 37 | - 3306:3306 38 | options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 39 | 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | moodle-branch: ['MOODLE_405_STABLE'] 44 | php: [8.1, 8.2, 8.3] 45 | database: [pgsql, mariadb] 46 | 47 | steps: 48 | - name: Check out repository code 49 | uses: actions/checkout@v4 50 | with: 51 | path: plugin 52 | 53 | - name: Setup PHP ${{ matrix.php }} 54 | uses: shivammathur/setup-php@v2 55 | with: 56 | php-version: ${{ matrix.php }} 57 | ini-values: max_input_vars=5000 58 | coverage: none 59 | 60 | - name: Initialise moodle-plugin-ci 61 | run: | 62 | composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4 63 | echo $(cd ci/bin; pwd) >> $GITHUB_PATH 64 | echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH 65 | sudo locale-gen en_AU.UTF-8 66 | echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV 67 | 68 | - name: Install moodle-plugin-ci 69 | run: | 70 | mkdir -p ./extra-plugins 71 | git clone -b $MOODLE_BRANCH https://github.com/microsoft/moodle-auth_oidc.git ./extra-plugins/auth_oidc || git clone https://github.com/microsoft/moodle-auth_oidc.git ./extra-plugins/auth_oidc 72 | moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 --extra-plugins ./extra-plugins 73 | env: 74 | DB: ${{ matrix.database }} 75 | MOODLE_BRANCH: ${{ matrix.moodle-branch }} 76 | SHELLOPTS: errexit:nounset:xtrace 77 | 78 | - name: PHP Lint 79 | if: ${{ !cancelled() }} 80 | run: moodle-plugin-ci phplint 81 | 82 | - name: PHP Mess Detector 83 | continue-on-error: true 84 | if: ${{ !cancelled() }} 85 | run: moodle-plugin-ci phpmd 86 | 87 | - name: Moodle Code Checker 88 | if: ${{ !cancelled() }} 89 | run: moodle-plugin-ci phpcs --max-warnings 0 90 | 91 | - name: Moodle PHPDoc Checker 92 | if: ${{ !cancelled() }} 93 | run: moodle-plugin-ci phpdoc --max-warnings 0 94 | 95 | - name: Validating 96 | if: ${{ !cancelled() }} 97 | run: moodle-plugin-ci validate 98 | 99 | - name: Check upgrade savepoints 100 | if: ${{ !cancelled() }} 101 | run: moodle-plugin-ci savepoints 102 | 103 | - name: Mustache Lint 104 | if: ${{ !cancelled() }} 105 | run: moodle-plugin-ci mustache 106 | 107 | - name: Grunt 108 | if: ${{ !cancelled() }} 109 | run: moodle-plugin-ci grunt --max-lint-warnings 0 110 | 111 | - name: PHPUnit tests 112 | if: ${{ !cancelled() }} 113 | run: moodle-plugin-ci phpunit 114 | 115 | - name: Behat features 116 | id: behat 117 | if: ${{ !cancelled() }} 118 | run: moodle-plugin-ci behat --profile chrome 119 | 120 | - name: Cleanup after behat 121 | if: ${{ always() }} 122 | run: | 123 | sudo pkill -f chrome 124 | sudo pkill -f chromedriver 125 | 126 | - name: Mark cancelled jobs as failed. 127 | if: ${{ cancelled() }} 128 | run: exit 1 -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - name: selenium/standalone-chrome:3 3 | alias: behat 4 | - name: mysql:8.0 5 | alias: db 6 | command: 7 | - '--character-set-server=utf8mb4' 8 | - '--collation-server=utf8mb4_unicode_ci' 9 | - '--innodb_file_per_table=On' 10 | - '--wait-timeout=28800' 11 | - '--skip-log-bin' 12 | 13 | cache: 14 | paths: 15 | - .cache 16 | 17 | variables: 18 | DEBIAN_FRONTEND: 'noninteractive' 19 | COMPOSER_ALLOW_SUPERUSER: 1 20 | COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer" 21 | NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.cache/npm" 22 | CI_BUILD_DIR: '/tmp/plugin' 23 | MOODLE_BRANCH: 'MOODLE_405_STABLE' 24 | MOODLE_BEHAT_WWWROOT: 'http://localhost:8000' 25 | MOODLE_BEHAT_WDHOST: 'http://behat:4444/wd/hub' 26 | MOODLE_START_BEHAT_SERVERS: 'no' 27 | MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' 28 | DB: 'mysqli' 29 | 30 | stages: 31 | - moodle-plugin-ci 32 | 33 | .setupandruncheck: &setupandruncheck 34 | stage: moodle-plugin-ci 35 | before_script: 36 | - mkdir -pv "$CI_BUILD_DIR" 37 | - cp -ru "$CI_PROJECT_DIR/"* "$CI_BUILD_DIR" 38 | - mkdir -p /usr/share/man/man1 /usr/share/man/man3 /usr/share/man/man7 39 | - apt-get -qq update 40 | - apt-get -yqq install --no-install-suggests default-jre-headless default-mysql-client 41 | - 'curl -sS https://raw.githubusercontent.com/creationix/nvm/v0.39.3/install.sh | bash' 42 | - . ~/.bashrc 43 | - nvm install --default --latest-npm lts/gallium 44 | - 'curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer' 45 | - composer create-project -n --no-dev --no-progress --no-ansi moodlehq/moodle-plugin-ci /opt/mci ^4 46 | - export PATH="/opt/mci/bin:/opt/mci/vendor/bin:$PATH" 47 | - moodle-plugin-ci add-plugin --clone https://github.com/microsoft/moodle-auth_oidc.git --branch $MOODLE_BRANCH 48 | - moodle-plugin-ci install --db-host db --db-name moodle 49 | - '{ php -S 0.0.0.0:8000 -t "$CI_PROJECT_DIR/moodle" >/dev/null 2>&1 & }' 50 | - TXT_RED="\e[31m" 51 | 52 | script: 53 | - errors=() 54 | - moodle-plugin-ci phplint || errors+=("phplint") 55 | - moodle-plugin-ci phpmd || errors+=("phpmd") 56 | - moodle-plugin-ci codechecker --max-warnings 0 || errors+=("codechecker") 57 | - moodle-plugin-ci phpdoc --max-warnings 0 || errors+=("phpdoc") 58 | - moodle-plugin-ci validate || errors+=("validate") 59 | - moodle-plugin-ci savepoints || errors+=("savepoints") 60 | - moodle-plugin-ci mustache || errors+=("mustache") 61 | - moodle-plugin-ci grunt --max-lint-warnings 0 || errors+=("grunt") 62 | - moodle-plugin-ci phpunit || errors+=("phpunit") 63 | - moodle-plugin-ci behat --auto-rerun 0 --profile chrome || errors+=("behat") 64 | - |- 65 | if [ ${#errors[@]} -ne 0 ]; then 66 | echo -e "${TXT_RED}Check errors: ${errors[@]}"; 67 | exit 1; 68 | fi 69 | 70 | php81: 71 | tags: 72 | - docker 73 | image: moodlehq/moodle-php-apache:8.1 74 | <<: *setupandruncheck 75 | 76 | php82: 77 | tags: 78 | - docker 79 | image: moodlehq/moodle-php-apache:8.2 80 | <<: *setupandruncheck 81 | 82 | php83: 83 | tags: 84 | - docker 85 | image: moodlehq/moodle-php-apache:8.3 86 | <<: *setupandruncheck 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft 365 and Microsoft Entra ID Plugins for Moodle 2 | 3 | ## o365 Local Plugin 4 | 5 | This plugin provides libraries and services and power other Microsoft 365 plugins. In addition to providing implementations of Microsoft 365 apis, this plugin handles a wide variety of Moodle events to fully integrate Microsoft 365 into a Moodle installation. 6 | 7 | This is part of the suite of Microsoft 365 plugins for Moodle. 8 | 9 | This repository is updated with stable releases. To follow active development, see: https://github.com/Microsoft/o365-moodle 10 | 11 | ## Installation 12 | 13 | 1. Unpack the plugin into /local/o365 within your Moodle install. 14 | 2. From the Moodle Administration block, expand Site Administration and click "Notifications". 15 | 3. Follow the on-screen instuctions to install the plugin. 16 | 17 | For more documentation, visit https://docs.moodle.org/34/en/Office365 18 | 19 | For more information including support and instructions on how to contribute, please see: https://github.com/Microsoft/o365-moodle/blob/master/README.md 20 | 21 | ## Issues and Contributing 22 | Please post issues for this plugin to: https://github.com/Microsoft/o365-moodle/issues/ 23 | Pull requests for this plugin should be submitted against our main repository: https://github.com/Microsoft/o365-moodle 24 | 25 | ## Copyright 26 | 27 | © Microsoft, Inc. Code for this plugin is licensed under the GPLv3 license. 28 | 29 | Any Microsoft trademarks and logos included in these plugins are property of Microsoft and should not be reused, redistributed, modified, repurposed, or otherwise altered or used outside of this plugin. 30 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /acp.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Admin control panel page. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @author Lai Wei 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 25 | */ 26 | 27 | require_once(__DIR__.'/../../config.php'); 28 | 29 | require_login(); 30 | require_capability('moodle/site:config', \context_system::instance()); 31 | 32 | $mode = optional_param('mode', null, PARAM_TEXT); 33 | $url = new \moodle_url('/local/o365/acp.php', ['mode' => $mode]); 34 | $page = new \local_o365\page\acp($url, get_string('settings_header_advanced', 'local_o365')); 35 | $page->run($mode); 36 | -------------------------------------------------------------------------------- /ajax.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Page processing ajax requests. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | define('AJAX_SCRIPT', true); 27 | require_once(__DIR__.'/../../config.php'); 28 | require_login(); 29 | $mode = required_param('mode', PARAM_TEXT); 30 | require_capability('moodle/site:config', \context_system::instance()); 31 | $url = '/local/o365/ajax.php'; 32 | $page = new \local_o365\page\ajax($url, ''); 33 | $page->run($mode); 34 | -------------------------------------------------------------------------------- /classes/adminsetting/adminconsent.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Admin consent admin setting. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\adminsetting; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | global $CFG; 31 | 32 | require_once($CFG->dirroot.'/lib/adminlib.php'); 33 | 34 | /** 35 | * Admin setting to provide admin consent for API permissions. 36 | */ 37 | class adminconsent extends \admin_setting { 38 | /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */ 39 | public $paramtype; 40 | 41 | /** @var int default field size */ 42 | public $size; 43 | 44 | /** 45 | * Config text constructor 46 | * 47 | * @param string $name unique ascii name 48 | * @param string $visiblename localised 49 | * @param string $description long localised info 50 | * @param string $defaultsetting 51 | * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex 52 | * @param int $size default field size 53 | */ 54 | public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) { 55 | $this->paramtype = $paramtype; 56 | if (!is_null($size)) { 57 | $this->size = $size; 58 | } else { 59 | $this->size = ($paramtype === PARAM_INT) ? 5 : 30; 60 | } 61 | parent::__construct($name, $visiblename, $description, $defaultsetting); 62 | } 63 | 64 | /** 65 | * Return the setting 66 | * 67 | * @return mixed returns config if successful else null 68 | */ 69 | public function get_setting() { 70 | return $this->config_read($this->name); 71 | } 72 | 73 | /** 74 | * Store new setting 75 | * 76 | * @param mixed $data string or array, must not be NULL 77 | * @return string empty string if ok, string error message otherwise 78 | */ 79 | public function write_setting($data) { 80 | $this->config_write($this->name, '0'); 81 | return ''; 82 | } 83 | 84 | /** 85 | * Return an XHTML string for the setting. 86 | * 87 | * @param mixed $data 88 | * @param string $query 89 | * @return string 90 | */ 91 | public function output_html($data, $query='') { 92 | $settinghtml = ''; 93 | $setuserurl = new \moodle_url('/local/o365/acp.php', ['mode' => 'adminconsent']); 94 | 95 | $linkstr = get_string('settings_adminconsent_btn', 'local_o365'); 96 | $message = \html_writer::link($setuserurl, $linkstr, ['class' => 'btn btn-primary', 'style' => 'margin-bottom: 0.5rem']); 97 | $messageattrs = []; 98 | $settinghtml .= \html_writer::tag('div', $message, $messageattrs); 99 | 100 | return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /classes/adminsetting/courseresetteams.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Settings of Team actions on course reset. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\adminsetting; 27 | 28 | use admin_setting; 29 | 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | global $CFG; 33 | 34 | require_once($CFG->dirroot . '/local/o365/lib.php'); 35 | 36 | /** 37 | * Class courseresetteams. 38 | */ 39 | class courseresetteams extends admin_setting { 40 | /** 41 | * Return the setting. 42 | * 43 | * @return mixed|null 44 | */ 45 | public function get_setting() { 46 | return $this->config_read($this->name); 47 | } 48 | 49 | /** 50 | * Store new setting. 51 | * 52 | * @param mixed $data 53 | * 54 | * @return string 55 | */ 56 | public function write_setting($data) { 57 | $this->config_write($this->name, $data); 58 | return ''; 59 | } 60 | 61 | /** 62 | * Return HTML string for the setting. 63 | * 64 | * @param mixed $data 65 | * @param string $query 66 | * 67 | * @return string 68 | */ 69 | public function output_html($data, $query = '') { 70 | $settinghtml = ''; 71 | 72 | $options = [ 73 | COURSE_SYNC_RESET_SITE_SETTING_DO_NOTHING => new \lang_string('settings_course_reset_teams_option_do_nothing', 74 | 'local_o365'), 75 | ]; 76 | 77 | $coursesyncsetting = get_config('local_o365', 'coursesync'); 78 | if ($coursesyncsetting == 'oncustom') { 79 | $options[COURSE_SYNC_RESET_SITE_SETTING_DISCONNECT_ONLY] = new \lang_string( 80 | 'settings_course_reset_teams_option_archive_only', 'local_o365'); 81 | } 82 | 83 | $options[COURSE_SYNC_RESET_SITE_SETTING_DISCONNECT_AND_CREATE_NEW] = new \lang_string( 84 | 'settings_course_reset_teams_option_force_archive', 'local_o365'); 85 | $options[COURSE_SYNC_RESET_SITE_SETTING_PER_COURSE] = new \lang_string( 86 | 'settings_course_reset_teams_option_per_course', 'local_o365'); 87 | 88 | $currentvalue = (isset($options[$data])) ? $data : $this->get_defaultsetting(); 89 | foreach ($options as $key => $desc) { 90 | $radioattributes = [ 91 | 'type' => 'radio', 92 | 'id' => $this->get_id() . '_' . $key, 93 | 'name' => $this->get_full_name(), 94 | 'value' => $key, 95 | 'class' => 'local_o365_acp_option', 96 | ]; 97 | if ($currentvalue == $key) { 98 | $radioattributes['checked'] = 'checked'; 99 | } 100 | $settinghtml .= \html_writer::empty_tag('input', $radioattributes); 101 | $settinghtml .= \html_writer::label($desc, $this->get_id().'_'.$key, false); 102 | $settinghtml .= \html_writer::empty_tag('br'); 103 | $settinghtml .= \html_writer::empty_tag('br'); 104 | } 105 | 106 | return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /classes/adminsetting/coursesync.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Admin setting to configure course sync. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @author Lai Wei 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 25 | */ 26 | 27 | namespace local_o365\adminsetting; 28 | 29 | defined('MOODLE_INTERNAL') || die(); 30 | 31 | global $CFG; 32 | 33 | require_once($CFG->dirroot.'/lib/adminlib.php'); 34 | 35 | /** 36 | * Admin setting to configure course sync. 37 | */ 38 | class coursesync extends \admin_setting { 39 | /** 40 | * Return the setting 41 | * 42 | * @return mixed returns config if successful else null 43 | */ 44 | public function get_setting() { 45 | return $this->config_read($this->name); 46 | } 47 | 48 | /** 49 | * Store new setting 50 | * 51 | * @param mixed $data string or array, must not be NULL 52 | * @return string empty string if ok, string error message otherwise 53 | */ 54 | public function write_setting($data) { 55 | $this->config_write($this->name, $data); 56 | return ''; 57 | } 58 | 59 | /** 60 | * Return an XHTML string for the setting. 61 | * 62 | * @param mixed $data 63 | * @param string $query 64 | * @return string 65 | */ 66 | public function output_html($data, $query = '') { 67 | $settinghtml = ''; 68 | 69 | $customizeurl = new \moodle_url('/local/o365/acp.php', ['mode' => 'coursesynccustom']); 70 | $options = [ 71 | 'off' => get_string('acp_coursesynccustom_off', 'local_o365'), 72 | 'oncustom' => get_string('acp_coursesynccustom_oncustom', 'local_o365', $customizeurl->out()), 73 | 'onall' => get_string('acp_coursesynccustom_onall', 'local_o365'), 74 | ]; 75 | $curval = (isset($options[$data])) ? $data : $this->get_defaultsetting(); 76 | foreach ($options as $key => $desc) { 77 | $radioattrs = [ 78 | 'type' => 'radio', 79 | 'id' => $this->get_id().'_'.$key, 80 | 'name' => $this->get_full_name(), 81 | 'value' => $key, 82 | 'onchange' => 'teams_togglecustom()', 83 | 'class' => 'local_o365_acp_option', 84 | ]; 85 | if ($curval === $key) { 86 | $radioattrs['checked'] = 'checked'; 87 | } 88 | $settinghtml .= \html_writer::empty_tag('input', $radioattrs); 89 | $settinghtml .= \html_writer::label($desc, $this->get_id().'_'.$key, false); 90 | $settinghtml .= \html_writer::empty_tag('br'); 91 | $settinghtml .= \html_writer::empty_tag('br'); 92 | } 93 | $js = <<visiblename, $settinghtml, $this->description); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /classes/adminsetting/detectoidc.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Admin setting to detect whether oauth credentials are present in openid connect. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\adminsetting; 27 | 28 | use admin_setting; 29 | 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | global $CFG; 33 | 34 | require_once($CFG->dirroot . '/lib/adminlib.php'); 35 | require_once($CFG->dirroot . '/auth/oidc/lib.php'); 36 | 37 | /** 38 | * Admin setting to detect whether oauth credentials are present in openid connect. 39 | */ 40 | class detectoidc extends admin_setting { 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param string $name 46 | * @param string $heading 47 | * @param string $description 48 | */ 49 | public function __construct($name, $heading, $description) { 50 | $this->nosave = true; 51 | parent::__construct($name, $heading, $description, ''); 52 | } 53 | 54 | /** 55 | * Always returns true because we have no real setting. 56 | * 57 | * @return bool Always returns true 58 | */ 59 | public function get_setting() { 60 | return true; 61 | } 62 | 63 | /** 64 | * Always returns true because we have no real setting. 65 | * 66 | * @return bool Always returns true 67 | */ 68 | public function get_defaultsetting() { 69 | return true; 70 | } 71 | 72 | /** 73 | * Never write settings. 74 | * 75 | * @param mixed $data 76 | * @return string 77 | */ 78 | public function write_setting($data) { 79 | return ''; 80 | } 81 | 82 | /** 83 | * Determine whether this setup step has been completed. 84 | * 85 | * @return bool True if setup step has been completed, false otherwise. 86 | */ 87 | public static function setup_step_complete() { 88 | return auth_oidc_is_setup_complete(); 89 | } 90 | 91 | /** 92 | * Return an XHTML string for the setting. 93 | * 94 | * @param mixed $data 95 | * @param string $query 96 | * @return string 97 | */ 98 | public function output_html($data, $query='') { 99 | global $OUTPUT; 100 | $settingspage = new \moodle_url('/admin/settings.php?section=authsettingoidc'); 101 | if (static::setup_step_complete() === true) { 102 | $icon = $OUTPUT->pix_icon('t/check', 'success', 'moodle'); 103 | $message = \html_writer::tag('span', get_string('settings_detectoidc_credsvalid', 'local_o365')); 104 | $linkstr = get_string('settings_detectoidc_credsvalid_link', 'local_o365'); 105 | $link = \html_writer::link($settingspage, $linkstr, ['style' => 'margin-left: 1rem']); 106 | $html = \html_writer::tag('div', $icon.$message.$link, ['class' => 'alert-success alert local_o365_statusmessage']); 107 | } else { 108 | $icon = $OUTPUT->pix_icon('t/delete', 'success', 'moodle'); 109 | $message = \html_writer::tag('span', get_string('settings_detectoidc_credsinvalid', 'local_o365')); 110 | $linkstr = get_string('settings_detectoidc_credsinvalid_link', 'local_o365'); 111 | $link = \html_writer::link($settingspage, $linkstr, ['style' => 'margin-left: 1rem']); 112 | $html = \html_writer::tag('div', $icon.$message.$link, ['class' => 'alert-error alert local_o365_statusmessage']); 113 | } 114 | return format_admin_setting($this, $this->visiblename, $html, $this->description, true, '', null, $query); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /classes/adminsetting/healthcheck.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Admin setting to perform health check. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\adminsetting; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | global $CFG; 31 | 32 | require_once($CFG->dirroot.'/lib/adminlib.php'); 33 | 34 | /** 35 | * Admin setting to perform health check. 36 | */ 37 | class healthcheck extends \admin_setting { 38 | /** @var mixed int means PARAM_XXX type, string is a allowed format in regex */ 39 | public $paramtype; 40 | 41 | /** @var int default field size */ 42 | public $size; 43 | 44 | /** 45 | * Config text constructor 46 | * 47 | * @param string $name unique ascii name. 48 | * @param string $visiblename localised 49 | * @param string $description long localised info 50 | * @param string $defaultsetting 51 | * @param mixed $paramtype int means PARAM_XXX type, string is a allowed format in regex 52 | * @param int $size default field size 53 | */ 54 | public function __construct($name, $visiblename, $description, $defaultsetting, $paramtype=PARAM_RAW, $size=null) { 55 | $this->paramtype = $paramtype; 56 | if (!is_null($size)) { 57 | $this->size = $size; 58 | } else { 59 | $this->size = ($paramtype === PARAM_INT) ? 5 : 30; 60 | } 61 | parent::__construct($name, $visiblename, $description, $defaultsetting); 62 | } 63 | 64 | /** 65 | * Return the setting 66 | * 67 | * @return mixed returns config if successful else null 68 | */ 69 | public function get_setting() { 70 | return $this->config_read($this->name); 71 | } 72 | 73 | /** 74 | * Store new setting 75 | * 76 | * @param mixed $data string or array, must not be NULL 77 | * @return string empty string if ok, string error message otherwise 78 | */ 79 | public function write_setting($data) { 80 | $this->config_write($this->name, '0'); 81 | return ''; 82 | } 83 | 84 | /** 85 | * Return an XHTML string for the setting. 86 | * 87 | * @param mixed $data 88 | * @param string $query 89 | * @return string 90 | */ 91 | public function output_html($data, $query='') { 92 | $healthcheckurl = new \moodle_url('/local/o365/acp.php', ['mode' => 'healthcheck']); 93 | $settinghtml = ''; 94 | $settinghtml .= \html_writer::link($healthcheckurl, get_string('settings_healthcheck_linktext', 'local_o365')); 95 | return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /classes/adminsetting/moodlesetup.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Admin setting to detect and set required settings in Moodle. 19 | * 20 | * @package local_o365 21 | * @author Enovation 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\adminsetting; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | global $CFG; 31 | 32 | require_once($CFG->dirroot.'/lib/adminlib.php'); 33 | 34 | /** 35 | * Admin setting to detect and set required settings in Moodle. 36 | */ 37 | class moodlesetup extends \admin_setting { 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param string $name 43 | * @param string $heading 44 | * @param string $description 45 | */ 46 | public function __construct($name, $heading, $description) { 47 | $this->nosave = true; 48 | parent::__construct($name, $heading, $description, '0'); 49 | } 50 | 51 | /** 52 | * Return the setting 53 | * 54 | * @return mixed returns config if successful else null 55 | */ 56 | public function get_setting() { 57 | return true; 58 | } 59 | 60 | /** 61 | * Write the setting. 62 | * 63 | * We do this manually so just pretend here. 64 | * 65 | * @param mixed $data Incoming form data. 66 | * @return string Always empty string representing no issues. 67 | */ 68 | public function write_setting($data) { 69 | return ''; 70 | } 71 | 72 | /** 73 | * Return an XHTML string for the settings. 74 | * 75 | * @param mixed $data 76 | * @param string $query 77 | * @return string 78 | */ 79 | public function output_html($data, $query = '') { 80 | global $OUTPUT; 81 | 82 | $button = \html_writer::tag('button', get_string('settings_check_moodle_settings', 'local_o365'), 83 | ['class' => 'setupmoodle', 'style' => 'margin: 0 0 0.75rem']); 84 | $results = \html_writer::tag('div', '', ['class' => 'results']); 85 | $settinghtml = $button.$results; 86 | 87 | // Using a '; 90 | 91 | $ajaxurl = new \moodle_url('/local/o365/ajax.php'); 92 | $settinghtml .= ''; 106 | 107 | return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description, true, '', null, $query); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /classes/adminsetting/tabs.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A tab in the plugin configuration page. 19 | * 20 | * @package local_o365 21 | * @author Eric Bjella 22 | * @copyright 2016 Remote Learner http://www.remote-learner.net/ 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace local_o365\adminsetting; 27 | 28 | use admin_setting; 29 | use html_writer; 30 | use moodle_url; 31 | use tabobject; 32 | 33 | /** 34 | * A tab in the plugin configuration page. 35 | */ 36 | class tabs extends admin_setting { 37 | /** 38 | * @var array[] tabs. 39 | */ 40 | protected $tabs = [0 => []]; 41 | /** 42 | * @var int current active tab. 43 | */ 44 | protected $selected; 45 | /** 46 | * @var string selected tab. 47 | */ 48 | protected $section; 49 | /** 50 | * @var bool whether to reload the configuration page. 51 | */ 52 | protected $reload; 53 | /** 54 | * @var mixed the component. 55 | */ 56 | protected $component; 57 | /** 58 | * @var string the theme. 59 | */ 60 | protected $theme; 61 | 62 | /** 63 | * Config tab constructor. 64 | * 65 | * @param string $name Unique ascii name, either 'mysetting' for settings that in 66 | * config, or 'myplugin/mysetting' for ones in config_plugins. 67 | * @param string $section Section name 68 | * @param boolean $reload Whether to reload 69 | */ 70 | public function __construct($name, $section, $reload) { 71 | parent::__construct($name, '', '', ''); 72 | 73 | $this->section = $section; 74 | $this->reload = $reload; 75 | $this->component = $this->plugin; 76 | $this->theme = substr($this->component, 6); 77 | 78 | // Check for direct links. 79 | $this->selected = optional_param($this->get_full_name(), 0, PARAM_INT); 80 | 81 | if ($this->reload) { 82 | $newtab = optional_param($this->get_full_name() . '_new', -1, PARAM_INT); 83 | 84 | if ($newtab != -1) { 85 | $this->selected = $newtab; 86 | } 87 | } 88 | 89 | } 90 | 91 | /** 92 | * Return the currently selected tab. 93 | * 94 | * @return int The id of the currently selected tab. 95 | */ 96 | public function get_setting() { 97 | return $this->selected; 98 | } 99 | 100 | /** 101 | * Write settings. 102 | * 103 | * In practice this actually runs the reset, import or export sub actions. 104 | * 105 | * @param array $data The submitted data to act upon. 106 | * @return string Always returns an empty string 107 | */ 108 | public function write_setting($data) { 109 | $result = ''; 110 | 111 | if (isset($data['action'])) { 112 | 113 | if ($data['action'] == 1) { 114 | $result = $this->reset(); 115 | 116 | } else if ($data['action'] == 2) { 117 | $result = $this->import($data['picker']); 118 | 119 | } else if ($data['action'] == 3) { 120 | $result = $this->export(); 121 | } 122 | } 123 | 124 | return $result; 125 | } 126 | 127 | /** 128 | * Add a tab to the tab row 129 | * 130 | * For now we only implement a single row. Multiple rows could be added as an extension later. 131 | * 132 | * @param int $id The tab id 133 | * @param string $name The tab name 134 | * @param moodle_url|null $url An explicit URL to use instead of settings page section. 135 | * @uses $CFG 136 | */ 137 | public function addtab($id, $name, ?moodle_url $url = null) { 138 | if (empty($url)) { 139 | $urlparams = [ 140 | 'section' => $this->section, 141 | $this->get_full_name() => $id, 142 | ]; 143 | $url = new moodle_url('/admin/settings.php', $urlparams); 144 | } 145 | $tab = new tabobject($id, $url, $name); 146 | 147 | $this->tabs[0][] = $tab; 148 | } 149 | 150 | /** 151 | * Returns an HTML string 152 | * 153 | * @param mixed $data Array or string depending on setting 154 | * @param string $query Query 155 | * @return string Returns an HTML string 156 | */ 157 | public function output_html($data, $query = '') { 158 | $this->component = $this->plugin; 159 | $this->theme = substr($this->component, 6); 160 | 161 | $output = print_tabs($this->tabs, $this->selected, null, null, true); 162 | 163 | $properties = [ 164 | 'type' => 'hidden', 165 | 'name' => $this->get_full_name(), 166 | 'value' => $this->get_setting(), 167 | ]; 168 | 169 | $output .= html_writer::empty_tag('input', $properties); 170 | 171 | $properties['id'] = $this->get_id(); 172 | $properties['name'] = $this->get_full_name() . '_new'; 173 | 174 | $output .= html_writer::empty_tag('input', $properties); 175 | 176 | return $output; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /classes/adminsetting/toollink.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A link to an admin tool. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\adminsetting; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | global $CFG; 31 | 32 | require_once($CFG->dirroot.'/lib/adminlib.php'); 33 | 34 | /** 35 | * Not a real setting - just a link to an admin tool. 36 | */ 37 | class toollink extends \admin_setting { 38 | /** 39 | * Constructor. 40 | * 41 | * @param string $name 42 | * @param string $visiblename 43 | * @param string $linktext 44 | * @param string $linkurl 45 | * @param string $description 46 | */ 47 | public function __construct($name, $visiblename, $linktext, $linkurl, $description) { 48 | $this->nosave = true; 49 | $this->linktext = $linktext; 50 | $this->linkurl = $linkurl; 51 | parent::__construct($name, $visiblename, $description, ''); 52 | } 53 | 54 | /** 55 | * Get setting value, but this is not a real setting. 56 | * 57 | * @return bool Always returns true 58 | */ 59 | public function get_setting() { 60 | return true; 61 | } 62 | 63 | /** 64 | * Get default setting - always true. 65 | * 66 | * @return bool Always returns true 67 | */ 68 | public function get_defaultsetting() { 69 | return true; 70 | } 71 | 72 | /** 73 | * Never write settings. 74 | * 75 | * @param mixed $data 76 | * @return string 77 | */ 78 | public function write_setting($data) { 79 | return ''; 80 | } 81 | 82 | /** 83 | * Return an HTML string. 84 | * 85 | * @param mixed $data 86 | * @param string $query 87 | * @return string 88 | */ 89 | public function output_html($data, $query = '') { 90 | $settinghtml = \html_writer::link($this->linkurl, $this->linktext); 91 | return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description, false); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /classes/event/api_call_failed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Event fired whenever a user subscribes to a calendar. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\event; 27 | 28 | /** 29 | * Event fired whenever a user subscribes to a calendar. 30 | */ 31 | class api_call_failed extends \core\event\base { 32 | /** 33 | * Return localised event name. 34 | * 35 | * @return string 36 | */ 37 | public static function get_name() { 38 | return get_string('eventapifail', 'local_o365'); 39 | } 40 | 41 | /** 42 | * Returns non-localised event description with id's for admin use only. 43 | * 44 | * @return string 45 | */ 46 | public function get_description() { 47 | $description = $this->data['other']['message']; 48 | if (!empty($this->data['other']['debugdata'])) { 49 | $description .= ': ' . $this->data['other']['debugdata']; 50 | } 51 | return $description; 52 | } 53 | 54 | /** 55 | * Init method. 56 | * 57 | * @return void 58 | */ 59 | protected function init() { 60 | $this->context = \context_system::instance(); 61 | $this->data['crud'] = 'r'; 62 | $this->data['edulevel'] = self::LEVEL_OTHER; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /classes/event/calendar_subscribed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Event fired whenever a user subscribes to a calendar. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\event; 27 | 28 | /** 29 | * Event fired whenever a user subscribes to a calendar. 30 | */ 31 | class calendar_subscribed extends \core\event\base { 32 | /** 33 | * Return localised event name. 34 | * 35 | * @return string 36 | */ 37 | public static function get_name() { 38 | return get_string('eventcalendarsubscribed', 'local_o365'); 39 | } 40 | 41 | /** 42 | * Returns non-localised event description with id's for admin use only. 43 | * 44 | * @return string 45 | */ 46 | public function get_description() { 47 | $caltype = $this->data['other']['caltype']; 48 | $caltypeid = $this->data['other']['caltypeid']; 49 | return "The user with id '$this->userid' has subscribed to a calendar (caltype: '{$caltype}' caltypeid: '{$caltypeid}')"; 50 | } 51 | 52 | /** 53 | * Init method. 54 | * 55 | * @return void 56 | */ 57 | protected function init() { 58 | $this->context = \context_system::instance(); 59 | $this->data['crud'] = 'r'; 60 | $this->data['edulevel'] = self::LEVEL_OTHER; 61 | $this->data['objecttable'] = 'local_o365_calsub'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /classes/event/calendar_unsubscribed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Event fired whenever a user unsubscribes from a calendar. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\event; 27 | 28 | /** 29 | * Event fired whenever a user unsubscribes from a calendar. 30 | */ 31 | class calendar_unsubscribed extends \core\event\base { 32 | /** 33 | * Return localised event name. 34 | * 35 | * @return string 36 | */ 37 | public static function get_name() { 38 | return get_string('eventcalendarunsubscribed', 'local_o365'); 39 | } 40 | 41 | /** 42 | * Returns non-localised event description with id's for admin use only. 43 | * 44 | * @return string 45 | */ 46 | public function get_description() { 47 | $caltype = $this->data['other']['caltype']; 48 | $caltypeid = $this->data['other']['caltypeid']; 49 | return "The user with id '$this->userid' has unsubscribed to a calendar (caltype: '{$caltype}' caltypeid: '{$caltypeid}')"; 50 | } 51 | 52 | /** 53 | * Init method. 54 | * 55 | * @return void 56 | */ 57 | protected function init() { 58 | $this->context = \context_system::instance(); 59 | $this->data['crud'] = 'r'; 60 | $this->data['edulevel'] = self::LEVEL_OTHER; 61 | $this->data['objecttable'] = 'local_o365_calsub'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /classes/feature/sds/utils.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Utility functions for the SDS feature. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2021 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\feature\sds; 27 | 28 | use local_o365\httpclient; 29 | use local_o365\oauth2\clientdata; 30 | use local_o365\rest\unified; 31 | use moodle_exception; 32 | 33 | defined('MOODLE_INTERNAL') || die(); 34 | 35 | require_once($CFG->dirroot . '/local/o365/lib.php'); 36 | 37 | /** 38 | * Utility functions for the SDS feature. 39 | */ 40 | class utils { 41 | /** 42 | * Get the unified api client. 43 | * 44 | * @return unified|null The SDS API client. 45 | */ 46 | public static function get_apiclient(): ?unified { 47 | $httpclient = new httpclient(); 48 | try { 49 | $clientdata = clientdata::instance_from_oidc(); 50 | $unifiedresource = unified::get_tokenresource(); 51 | $unifiedtoken = \local_o365\utils::get_application_token($unifiedresource, $clientdata, $httpclient, false, false); 52 | 53 | if (!empty($unifiedtoken)) { 54 | $apiclient = new unified($unifiedtoken, $httpclient); 55 | return $apiclient; 56 | } else { 57 | mtrace('Could not construct system API user token for SDS sync task.'); 58 | } 59 | } catch (moodle_exception $e) { 60 | return null; 61 | } 62 | 63 | return null; 64 | } 65 | 66 | /** 67 | * Return the configuration status of SDS profile sync, and the name of the school if configured. 68 | * 69 | * @param unified|null $apiclient 70 | * @return array 71 | */ 72 | public static function get_profile_sync_status_with_id_name(?unified $apiclient = null): array { 73 | $profilesyncenabled = false; 74 | $schoolid = ''; 75 | $schoolname = ''; 76 | 77 | $sdsprofilesyncconfig = get_config('local_o365', 'sdsprofilesync'); 78 | 79 | if ($sdsprofilesyncconfig) { 80 | if (is_null($apiclient)) { 81 | $apiclient = static::get_apiclient(); 82 | } 83 | 84 | if ($apiclient) { 85 | try { 86 | $schools = $apiclient->get_schools(); 87 | 88 | foreach ($schools as $school) { 89 | if ($school['id'] == $sdsprofilesyncconfig) { 90 | $profilesyncenabled = true; 91 | $schoolid = $school['id']; 92 | $schoolname = $school['displayName']; 93 | break; 94 | } 95 | } 96 | } catch (moodle_exception $e) { 97 | // School invalid, reset settings. 98 | $existingsdsprofilesyncsetting = get_config('local_o365', 'sdsprofilesync'); 99 | if ($existingsdsprofilesyncsetting) { 100 | add_to_config_log('sdsprofilesync', $existingsdsprofilesyncsetting, '', 'local_o365'); 101 | } 102 | set_config('sdsprofilesync', '', 'local_o365'); 103 | } 104 | } 105 | } 106 | 107 | return [$profilesyncenabled, $schoolid, $schoolname]; 108 | } 109 | 110 | /** 111 | * Return the basic (ID and name) SDS field mappings and the additional SDS field mappings from the auth_oidc configuration. 112 | * 113 | * @return array[] 114 | */ 115 | public static function get_sds_profile_sync_api_requirements(): array { 116 | $idandnamemappings = []; 117 | $additionalprofilemappings = []; 118 | 119 | $idandnamefieldnames = ['sds_school_id', 'sds_school_name']; 120 | $additionalfieldnames = ['sds_school_role', 'sds_student_externalId', 'sds_student_birthDate', 'sds_student_grade', 121 | 'sds_student_graduationYear', 'sds_student_studentNumber', 'sds_teacher_externalId', 'sds_teacher_teacherNumber']; 122 | 123 | $authoidcconfigs = get_config('auth_oidc'); 124 | foreach ($authoidcconfigs as $configkey => $authoidcconfig) { 125 | if (stripos($configkey, 'field_map_') === 0) { 126 | // The config is about field mapping. 127 | if (in_array($authoidcconfig, $idandnamefieldnames)) { 128 | $localfieldname = substr($configkey, strlen('field_map_')); 129 | $idandnamemappings[$authoidcconfig] = $localfieldname; 130 | } else if (in_array($authoidcconfig, $additionalfieldnames)) { 131 | $localfieldname = substr($configkey, strlen('field_map_')); 132 | $additionalprofilemappings[$authoidcconfig] = $localfieldname; 133 | } 134 | } 135 | } 136 | 137 | return [$idandnamemappings, $additionalprofilemappings]; 138 | } 139 | 140 | /** 141 | * Return the ID of Moodle courses connected to SDS course sections. 142 | * 143 | * @return array 144 | */ 145 | public static function get_sds_course_ids() { 146 | global $DB; 147 | 148 | return $DB->get_fieldset_select('local_o365_objects', 'moodleid', 'type = ?', ['sdssection']); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /classes/feature/userconnections/filtering.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * User filtering class. 19 | * 20 | * @package local_o365 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | * @copyright 2016 onwards Remote-Learner Inc (http://www.remote-learner.net) 23 | */ 24 | 25 | namespace local_o365\feature\userconnections; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | global $CFG; 30 | 31 | require_once($CFG->dirroot.'/user/filters/lib.php'); 32 | 33 | /** 34 | * User filtering class. 35 | */ 36 | class filtering extends \user_filtering { 37 | 38 | /** 39 | * Creates known user filter if present. 40 | * 41 | * @param string $fieldname 42 | * @param boolean $advanced 43 | * @return object filter 44 | */ 45 | public function get_field($fieldname, $advanced) { 46 | global $DB, $USER; 47 | switch ($fieldname) { 48 | 49 | case 'username': 50 | $label = get_string('acp_userconnections_filtering_musername', 'local_o365'); 51 | return new \user_filter_text('username', $label, $advanced, 'u.username'); 52 | 53 | case 'o365username': 54 | $label = get_string('acp_userconnections_filtering_o365username', 'local_o365'); 55 | return new \user_filter_text('o365username', $label, $advanced, 'o365username'); 56 | 57 | case 'idnumber': 58 | return new \user_filter_text('idnumber', get_string('idnumber'), $advanced, 'u.idnumber'); 59 | 60 | case 'realname': 61 | $label = get_string('acp_userconnections_filtering_muserfullname', 'local_o365'); 62 | $filteron = $DB->sql_fullname(); 63 | return new \user_filter_text('realname', $label, $advanced, $filteron); 64 | 65 | case 'lastname': 66 | return new \user_filter_text('lastname', get_string('lastname'), $advanced, 'u.lastname'); 67 | 68 | case 'firstname': 69 | return new \user_filter_text('firstname', get_string('firstname'), $advanced, 'u.firstname'); 70 | 71 | case 'email': 72 | return new \user_filter_text('email', get_string('email'), $advanced, 'u.email'); 73 | 74 | default: 75 | return null; 76 | } 77 | } 78 | 79 | /** 80 | * Returns sql where statement based on active user filters. 81 | * 82 | * @param string $extra sql 83 | * @param array|null $params named params (recommended prefix ex) 84 | * @return array sql string and $params 85 | */ 86 | public function get_sql_filter($extra='', ?array $params=null) { 87 | global $SESSION; 88 | 89 | $sqls = []; 90 | if ($extra != '') { 91 | $sqls[] = $extra; 92 | } 93 | $params = (array)$params; 94 | 95 | if (!empty($SESSION->user_filtering)) { 96 | foreach ($SESSION->user_filtering as $fname => $datas) { 97 | if (!array_key_exists($fname, $this->_fields)) { 98 | continue; // Filter not used. 99 | } 100 | if ($fname == 'o365username') { 101 | continue; 102 | } 103 | $field = $this->_fields[$fname]; 104 | foreach ($datas as $i => $data) { 105 | [$s, $p] = $field->get_sql_filter($data); 106 | $sqls[] = $s; 107 | $params = $params + $p; 108 | } 109 | } 110 | } 111 | 112 | if (empty($sqls)) { 113 | return ['', []]; 114 | } else { 115 | $sqls = implode(' AND ', $sqls); 116 | return [$sqls, $params]; 117 | } 118 | } 119 | 120 | /** 121 | * Get the filter value for the "o365username" filter. 122 | * 123 | * @return array List of filter SQLs and parameters for the o365username filter. 124 | */ 125 | public function get_filter_o365username() { 126 | global $SESSION; 127 | $sqls = []; 128 | $params = []; 129 | $fname = 'o365username'; 130 | if (isset($SESSION->user_filtering[$fname])) { 131 | $datas = $SESSION->user_filtering[$fname]; 132 | $field = $this->_fields[$fname]; 133 | foreach ($datas as $i => $data) { 134 | [$s, $p] = $field->get_sql_filter($data); 135 | $sqls[] = $s; 136 | $params = $params + $p; 137 | } 138 | } 139 | 140 | if (empty($sqls)) { 141 | return ['', []]; 142 | } else { 143 | $sqls = implode(' AND ', $sqls); 144 | return [$sqls, $params]; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /classes/form/cohortsync.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Microsoft group and Moodle cohort mapping form. 19 | * 20 | * @package local_o365 21 | * @copyright Enovation Solutions Ltd. {@link https://enovation.ie} 22 | * @author Patryk Mroczko 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace local_o365\form; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | require_once($CFG->libdir . '/formslib.php'); 31 | 32 | use html_table; 33 | use html_writer; 34 | use moodle_url; 35 | use moodleform; 36 | 37 | /** 38 | * Class cohortsync. 39 | * 40 | * @package local_o365\form 41 | */ 42 | class cohortsync extends moodleform { 43 | /** 44 | * Define the form elements. 45 | */ 46 | public function definition(): void { 47 | $mform = $this->_form; 48 | 49 | $cohortsyncmain = $this->_customdata['cohortsyncmain']; 50 | 51 | // Display instructions. 52 | $description = html_writer::div(get_string('cohortsync_desc', 'local_o365'), 'alert alert-info'); 53 | $mform->addElement('html', $description); 54 | 55 | // Get group and cohort options. 56 | $cohortsyncmain->fetch_groups_from_cache(); 57 | $existingmappings = $cohortsyncmain->get_mappings(); 58 | $mappedcohortids = []; 59 | $mappedgroupoids = []; 60 | 61 | foreach ($existingmappings as $mapping) { 62 | $mappedgroupoids[] = $mapping->objectid; 63 | $mappedcohortids[] = $mapping->moodleid; 64 | } 65 | 66 | $systemcohorts = $cohortsyncmain->get_cohortlist(); 67 | 68 | $groupoptions = []; 69 | 70 | foreach ($cohortsyncmain->get_grouplist() as $group) { 71 | if (!in_array($group['id'], $mappedgroupoids)) { 72 | $groupoptions[$group['id']] = $group['displayName']; 73 | } 74 | } 75 | 76 | $cohortoptions = []; 77 | $buttonattributes = []; 78 | foreach ($systemcohorts as $cohort) { 79 | if (!in_array($cohort->id, $mappedcohortids)) { 80 | $cohortoptions[$cohort->id] = $cohort->name; 81 | } 82 | } 83 | 84 | natcasesort($groupoptions); 85 | natcasesort($cohortoptions); 86 | 87 | // Display group selector. 88 | if (empty($groupoptions)) { 89 | $buttonattributes['disabled'] = 'disabled'; 90 | $groupoptions[''] = get_string('cohortsync_emptygroups', 'local_o365'); 91 | } 92 | $mform->addElement('select', 'groupoid', get_string('cohortsync_select_group', 'local_o365'), $groupoptions, 93 | ['class' => 'group-select']); 94 | 95 | // Display cohort selector. 96 | if (empty($cohortoptions)) { 97 | $buttonattributes['disabled'] = 'disabled'; 98 | $cohortoptions[''] = get_string('cohortsync_emptycohorts', 'local_o365'); 99 | } 100 | $mform->addElement('select', 'cohortid', get_string('cohortsync_select_cohort', 'local_o365'), $cohortoptions, 101 | ['class' => 'cohort-select']); 102 | 103 | // Display submit button. 104 | $mform->addElement('submit', 'action', get_string('cohortsync_addmapping', 'local_o365'), $buttonattributes); 105 | 106 | // Display existing mappings. 107 | $existingmappingscontent = html_writer::start_div('existing-mappings'); 108 | $existingmappingscontent .= html_writer::tag('h4', get_string('cohortsync_tabledesc', 'local_o365')); 109 | 110 | if (empty($existingmappings)) { 111 | $existingmappingscontent .= html_writer::tag('p', get_string('cohortsync_emptymatchings', 'local_o365')); 112 | } else { 113 | $existingmappingstable = new html_table(); 114 | $existingmappingstable->attributes['class'] = 'generaltable mod_index'; 115 | $existingmappingstable->head = [ 116 | get_string('cohortsync_tablehead_group', 'local_o365'), 117 | get_string('cohortsync_tablehead_cohort', 'local_o365'), 118 | get_string('cohortsync_tablehead_actions', 'local_o365'), 119 | ]; 120 | foreach ($existingmappings as $mapping) { 121 | $groupname = $cohortsyncmain->get_group_name_by_group_oid($mapping->objectid); 122 | $cohortname = $cohortsyncmain->get_cohort_name_by_cohort_id($mapping->moodleid); 123 | 124 | $cohorturl = new moodle_url('/cohort/edit.php', ['id' => $mapping->moodleid]); 125 | 126 | $deletemappingurl = new moodle_url('/local/o365/cohortsync.php', 127 | ['action' => 'delete', 'connectionid' => $mapping->id]); 128 | $existingmappingstable->data[] = [ 129 | $groupname, 130 | html_writer::link($cohorturl, $cohortname), 131 | html_writer::link($deletemappingurl, get_string('cohortsync_deletemapping', 'local_o365'), 132 | ['class' => 'btn btn-primary']), 133 | ]; 134 | } 135 | $existingmappingscontent .= html_writer::table($existingmappingstable); 136 | } 137 | 138 | $existingmappingscontent .= html_writer::end_div(); 139 | 140 | $mform->addElement('html', $existingmappingscontent); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /classes/form/manualusermatch.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Manual user match form. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\form; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | global $CFG; 31 | 32 | require_once($CFG->dirroot.'/lib/formslib.php'); 33 | 34 | /** 35 | * Single, manual user match form. 36 | */ 37 | class manualusermatch extends \moodleform { 38 | /** 39 | * Form definition. 40 | */ 41 | protected function definition() { 42 | global $USER, $DB; 43 | 44 | if (!empty($this->_customdata['userid'])) { 45 | $userrec = $DB->get_record('user', ['id' => $this->_customdata['userid']]); 46 | } else { 47 | $userrec = $DB->get_record('user', ['id' => $USER->id]); 48 | } 49 | 50 | $authconfig = get_config('auth_oidc'); 51 | $opname = (!empty($authconfig->opname)) ? $authconfig->opname : get_string('pluginname', 'auth_oidc'); 52 | 53 | $mform =& $this->_form; 54 | $mform->addElement('html', \html_writer::tag('h4', get_string('acp_userconnections_manualmatch_title', 'local_o365'))); 55 | $mform->addElement('html', \html_writer::div(get_string('acp_userconnections_manualmatch_details', 'local_o365'))); 56 | $mform->addElement('html', '
'); 57 | 58 | $mform->addElement('header', 'userdetails', get_string('userdetails')); 59 | 60 | $musernametext = fullname($userrec).' ('.$userrec->username.')'; 61 | $label = get_string('acp_userconnections_manualmatch_musername', 'local_o365'); 62 | $mform->addElement('static', 'musername', $label, $musernametext); 63 | $mform->addElement('text', 'o365username', get_string('acp_userconnections_manualmatch_o365username', 'local_o365')); 64 | $mform->addElement('checkbox', 'uselogin', get_string('acp_userconnections_manualmatch_uselogin', 'local_o365')); 65 | $mform->setType('o365username', PARAM_TEXT); 66 | 67 | $this->add_action_buttons(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /classes/form/teamsconnection.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Single team connection form. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\form; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | global $CFG; 31 | 32 | require_once($CFG->dirroot.'/lib/formslib.php'); 33 | 34 | /** 35 | * Single Teams connection form. 36 | */ 37 | class teamsconnection extends \moodleform { 38 | /** 39 | * Form definition. 40 | */ 41 | protected function definition() { 42 | $mform = &$this->_form; 43 | 44 | $mform->addElement('select', 'team', get_string('acp_teamconnections_form_team', 'local_o365'), 45 | $this->_customdata['teamsoptions']); 46 | 47 | $this->add_action_buttons(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /classes/form/teamstabconfiguration.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A form for configuring of Moodle tab in Teams. 19 | * 20 | * @package local_o365 21 | * @author Enovation Solutions 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2016 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\form; 27 | 28 | use moodleform; 29 | 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | global $CFG; 33 | 34 | require_once($CFG->dirroot . '/lib/formslib.php'); 35 | 36 | /** 37 | * A form for configuring of Moodle tab in Teams. 38 | * 39 | * @author Lai Wei 40 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 | * @copyright (C) 2018 onwards Microsoft, Inc. (http://microsoft.com/) 42 | */ 43 | class teamstabconfiguration extends moodleform { 44 | 45 | /** 46 | * Definition of the form. 47 | */ 48 | public function definition() { 49 | $mform = $this->_form; 50 | 51 | $courseoptions = self::get_course_options(); 52 | if ($courseoptions) { 53 | // User can access at least one course, show tab name field and course selector. 54 | $mform->addElement('text', 'local_o365_teams_tab_name', get_string('tab_name', 'local_o365'), 55 | ['onchange' => 'onTabNameChange()']); 56 | $mform->setType('local_o365_teams_tab_name', PARAM_TEXT); 57 | $tabname = get_config('local_o365', 'teams_moodle_tab_name'); 58 | if (!$tabname) { 59 | $tabname = 'Moodle'; 60 | } 61 | $mform->setDefault('local_o365_teams_tab_name', $tabname); 62 | 63 | $courseselector = $mform->createElement('select', 'local_o365_teams_course', 64 | get_string('course_selector_label', 'local_o365'), 65 | $courseoptions, ['onchange' => 'onCourseChange()']); 66 | $courseselector->setSize(100); 67 | $courseselector->setMultiple(true); 68 | 69 | $mform->addElement($courseselector); 70 | $mform->setType('course', PARAM_INT); 71 | 72 | } else { 73 | // User cannot access any course, show message. 74 | $messagehtml = \html_writer::tag('p', get_string('teams_no_course', 'local_o365')); 75 | $mform->addElement('html', $messagehtml); 76 | } 77 | } 78 | 79 | /** 80 | * Return a list of courses that the user has access to, to be used as options in the drop down list. 81 | * 82 | * @return array 83 | */ 84 | private function get_course_options() { 85 | global $DB, $USER; 86 | 87 | $courseoptions = []; 88 | 89 | if (is_siteadmin($USER->id)) { 90 | $courses = $DB->get_records('course', ['visible' => 1]); 91 | unset($courses[SITEID]); 92 | } else { 93 | $courses = enrol_get_users_courses($USER->id, true, null, 'fullname'); 94 | } 95 | 96 | foreach ($courses as $course) { 97 | $courseoptions[$course->id] = $course->fullname . ' (' . $course->shortname . ')'; 98 | } 99 | 100 | asort($courseoptions); 101 | 102 | return $courseoptions; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /classes/form/usermatch.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * User match form. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\form; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | global $CFG; 31 | 32 | require_once($CFG->dirroot.'/lib/formslib.php'); 33 | 34 | /** 35 | * o365 User Match Form. 36 | */ 37 | class usermatch extends \moodleform { 38 | /** 39 | * Form definition. 40 | */ 41 | protected function definition() { 42 | $mform =& $this->_form; 43 | $mform->addElement('filepicker', 'matchdatafile', get_string('file'), null, []); 44 | $this->add_action_buttons(false, get_string('acp_usermatch_upload_submit', 'local_o365')); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /classes/healthcheck/healthcheckinterface.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Interface for all health checks. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\healthcheck; 27 | 28 | /** 29 | * Interface for all health checks. 30 | */ 31 | interface healthcheckinterface { 32 | /** 33 | * @var int SEVERITY_OK 34 | */ 35 | const SEVERITY_OK = 0; 36 | /** 37 | * @var int SEVERITY_TRIVIAL 38 | */ 39 | const SEVERITY_TRIVIAL = 1; 40 | /** 41 | * @var int SEVERITY_WARNING 42 | */ 43 | const SEVERITY_WARNING = 2; 44 | /** 45 | * @var int SEVERITY_FATAL 46 | */ 47 | const SEVERITY_FATAL = 3; 48 | 49 | /** 50 | * Run the health check. 51 | * 52 | * @return array Array of result data. Must include: 53 | * bool result Whether the health check passed or not. 54 | * int severity If the health check failed, how bad a problem is it? This is one of the SEVERITY_* constants. 55 | * string message A message to show the user. 56 | */ 57 | public function run(); 58 | 59 | /** 60 | * Get a human-readable name for the health check. 61 | * 62 | * @return string A name for the health check. 63 | */ 64 | public function get_name(); 65 | } 66 | -------------------------------------------------------------------------------- /classes/healthcheck/ratelimit.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Checks current recorded rate limit. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\healthcheck; 27 | 28 | /** 29 | * Checks current recorded rate limit 30 | */ 31 | class ratelimit implements \local_o365\healthcheck\healthcheckinterface { 32 | /** 33 | * Run the health check. 34 | * 35 | * @return array Array of result data. Must include: 36 | * bool result Whether the health check passed or not. 37 | * int severity If the health check failed, how bad a problem is it? This is one of the SEVERITY_* constants. 38 | * string message A message to show the user. 39 | * string fixlink If the healthcheck failed, a link to help resolve the problem. 40 | */ 41 | public function run() { 42 | $ratelimitdisabled = get_config('local_o365', 'ratelimitdisabled'); 43 | if (!empty($ratelimitdisabled)) { 44 | return [ 45 | 'result' => false, 46 | 'severity' => static::SEVERITY_TRIVIAL, 47 | 'message' => get_string('healthcheck_ratelimit_result_disabled', 'local_o365'), 48 | ]; 49 | } 50 | 51 | $ratelimit = get_config('local_o365', 'ratelimit'); 52 | $ratelimit = explode(':', $ratelimit, 2); 53 | 54 | if (!empty($ratelimit[0]) && $ratelimit[1] > (time() - (10 * MINSECS))) { 55 | $a = new \stdClass; 56 | $a->level = $ratelimit[0]; 57 | $a->timestart = date('c', $ratelimit[1]); 58 | if ($ratelimit[0] < 4) { 59 | return [ 60 | 'result' => false, 61 | 'severity' => static::SEVERITY_TRIVIAL, 62 | 'message' => get_string('healthcheck_ratelimit_result_notice', 'local_o365', $a), 63 | ]; 64 | } else { 65 | return [ 66 | 'result' => false, 67 | 'severity' => static::SEVERITY_TRIVIAL, 68 | 'message' => get_string('healthcheck_ratelimit_result_warning', 'local_o365', $a), 69 | ]; 70 | } 71 | } else { 72 | return [ 73 | 'result' => true, 74 | 'severity' => static::SEVERITY_OK, 75 | 'message' => get_string('healthcheck_ratelimit_result_passed', 'local_o365'), 76 | ]; 77 | } 78 | } 79 | 80 | /** 81 | * Get a human-readable name for the health check. 82 | * 83 | * @return string A name for the health check. 84 | */ 85 | public function get_name() { 86 | return get_string('healthcheck_ratelimit_title', 'local_o365'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /classes/httpclientinterface.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * HTTP Client Interface. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365; 27 | 28 | /** 29 | * HTTP Client Interface. 30 | */ 31 | interface httpclientinterface { 32 | /** 33 | * HTTP POST method 34 | * 35 | * @param string $url 36 | * @param array|string $params 37 | * @param array $options 38 | * @return bool 39 | */ 40 | public function post($url, $params = '', $options = []); 41 | 42 | /** 43 | * HTTP GET method 44 | * 45 | * @param string $url 46 | * @param array $params 47 | * @param array $options 48 | * @return bool 49 | */ 50 | public function get($url, $params = [], $options = []); 51 | 52 | /** 53 | * HTTP PATCH method 54 | * 55 | * @param string $url 56 | * @param array|string $params 57 | * @param array $options 58 | * @return bool 59 | */ 60 | public function patch($url, $params = '', $options = []); 61 | 62 | /** 63 | * HTTP DELETE method 64 | * 65 | * @param string $url 66 | * @param array $param 67 | * @param array $options 68 | * @return bool 69 | */ 70 | public function delete($url, $param = [], $options = []); 71 | 72 | /** 73 | * Set HTTP Request Header 74 | * 75 | * @param array $header 76 | */ 77 | public function setheader($header); 78 | 79 | /** 80 | * Resets the HTTP Request headers (to prepare for the new request) 81 | */ 82 | public function resetheader(); 83 | } 84 | -------------------------------------------------------------------------------- /classes/oauth2/clientdata.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Class representing oauth2 client data. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\oauth2; 27 | 28 | use moodle_exception; 29 | 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | require_once($CFG->dirroot . '/auth/oidc/lib.php'); 33 | 34 | /** 35 | * Class representing oauth2 client data. 36 | */ 37 | class clientdata { 38 | /** @var string The registerd client ID. */ 39 | protected $clientid; 40 | 41 | /** @var string The registered client secreet. */ 42 | protected $clientsecret; 43 | 44 | /** @var string The authorization endpoint URI. */ 45 | protected $authendpoint; 46 | 47 | /** @var string The token endpoint URI. */ 48 | protected $tokenendpoint; 49 | 50 | /** @var bool The app-only token endpoint URI. */ 51 | protected $apptokenendpoint = false; 52 | 53 | /** 54 | * Constructor. 55 | * 56 | * @param string $clientid The registerd client ID. 57 | * @param string $clientsecret The registered client secreet. 58 | * @param string $authendpoint The authorization endpoint URI. 59 | * @param string $tokenendpoint The token endpoint URI. 60 | * @param string $apptokenendpoint The app-only token endpoint URI. 61 | */ 62 | public function __construct($clientid, $clientsecret, $authendpoint, $tokenendpoint, $apptokenendpoint = null) { 63 | $this->clientid = $clientid; 64 | $this->clientsecret = $clientsecret; 65 | $this->authendpoint = $authendpoint; 66 | $this->tokenendpoint = $tokenendpoint; 67 | if (!empty($apptokenendpoint)) { 68 | $this->apptokenendpoint = $apptokenendpoint; 69 | } else { 70 | $tenant = get_config('local_o365', 'entratenant'); 71 | if (!empty($tenant)) { 72 | $this->apptokenendpoint = static::get_apptokenendpoint_from_tenant($tenant); 73 | } else { 74 | $tenantid = get_config('local_o365', 'entratenantid'); 75 | if (!empty($tenantid)) { 76 | $this->apptokenendpoint = static::get_apptokenendpoint_from_tenant($tenantid); 77 | } else { 78 | \local_o365\utils::debug('Did not populate clientdata:apptokenendpoint because no tenant was present', 79 | __METHOD__); 80 | } 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Return the app token end point of the tenant. 87 | * 88 | * @param string $tenant 89 | * @return string 90 | */ 91 | public static function get_apptokenendpoint_from_tenant($tenant) { 92 | $idptype = get_config('auth_oidc', 'idptype'); 93 | if ($idptype == AUTH_OIDC_IDP_TYPE_MICROSOFT_ENTRA_ID) { 94 | return 'https://login.microsoftonline.com/' . $tenant . '/oauth2/token'; 95 | } else if ($idptype == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) { 96 | return 'https://login.microsoftonline.com/' . $tenant . '/oauth2/v2.0/token'; 97 | } else { 98 | return ''; 99 | } 100 | } 101 | 102 | /** 103 | * Get an instance from auth_oidc config. 104 | * 105 | * @param string|null $tenant 106 | * @return clientdata The constructed client data creds. 107 | * @throws moodle_exception 108 | */ 109 | public static function instance_from_oidc($tenant = null) { 110 | $cfg = get_config('auth_oidc'); 111 | 112 | if (!auth_oidc_is_setup_complete()) { 113 | throw new moodle_exception('erroracpauthoidcnotconfig', 'local_o365'); 114 | } 115 | 116 | $apptokenendpoint = null; 117 | if (!empty($tenant)) { 118 | $apptokenendpoint = static::get_apptokenendpoint_from_tenant($tenant); 119 | } 120 | $clientsecret = null; 121 | if (property_exists($cfg, 'clientsecret')) { 122 | $clientsecret = $cfg->clientsecret; 123 | } 124 | 125 | return new static($cfg->clientid, $clientsecret, $cfg->authendpoint, $cfg->tokenendpoint, $apptokenendpoint); 126 | } 127 | 128 | /** 129 | * Get the registered client ID. 130 | * 131 | * @return string The registered client ID. 132 | */ 133 | public function get_clientid() { 134 | return $this->clientid; 135 | } 136 | 137 | /** 138 | * Get the registered client secret. 139 | * 140 | * @return string The registered client secret. 141 | */ 142 | public function get_clientsecret() { 143 | return $this->clientsecret; 144 | } 145 | /** 146 | * Get the authorization endpoint URI. 147 | * 148 | * @return string The authorization endpoint URI. 149 | */ 150 | public function get_authendpoint() { 151 | return $this->authendpoint; 152 | } 153 | 154 | /** 155 | * Get the token endpoint URI. 156 | * 157 | * @return string The token endpoint URI. 158 | */ 159 | public function get_tokenendpoint() { 160 | return $this->tokenendpoint; 161 | } 162 | 163 | /** 164 | * Get the token for app-only authentication. 165 | * 166 | * @return string The app-only token endpoint URI. 167 | */ 168 | public function get_apptokenendpoint() { 169 | return $this->apptokenendpoint; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /classes/obj/o365user.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Class representing Microsoft 365 user information. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\obj; 27 | 28 | /** 29 | * Class representing Microsoft 365 user information. 30 | */ 31 | class o365user { 32 | /** 33 | * @var int|null 34 | */ 35 | protected $muserid = null; 36 | /** 37 | * @var string|null 38 | */ 39 | protected $oidctoken = null; 40 | /** 41 | * @var string|null 42 | */ 43 | public $objectid = null; 44 | /** 45 | * @var string|null 46 | */ 47 | public $username = null; 48 | /** 49 | * @var string|null 50 | */ 51 | public $useridentifier = null; 52 | /** @var string|null */ 53 | public $upn = null; 54 | 55 | /** 56 | * Constructor. 57 | * 58 | * @param int $userid 59 | * @param string $oidctoken 60 | */ 61 | protected function __construct($userid, $oidctoken) { 62 | $this->muserid = $userid; 63 | $this->oidctoken = $oidctoken; 64 | $this->objectid = $oidctoken->oidcuniqid; 65 | $this->username = $oidctoken->oidcusername; 66 | $this->useridentifier = $oidctoken->useridentifier; 67 | $this->upn = $oidctoken->oidcusername; 68 | } 69 | 70 | /** 71 | * Return ID token. 72 | * 73 | * @return mixed 74 | */ 75 | public function get_idtoken() { 76 | return $this->oidctoken->idtoken; 77 | } 78 | 79 | /** 80 | * Create a new instance of the o365user object from the user ID. 81 | * 82 | * @param int $userid 83 | * @return o365user|null 84 | */ 85 | public static function instance_from_muserid($userid) { 86 | global $DB; 87 | 88 | $tokenresource = \local_o365\rest\unified::get_tokenresource(); 89 | $params = ['userid' => $userid, 'tokenresource' => $tokenresource]; 90 | $oidctoken = $DB->get_record('auth_oidc_token', $params); 91 | if (empty($oidctoken)) { 92 | return null; 93 | } 94 | return new \local_o365\obj\o365user($userid, $oidctoken); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /classes/page/base.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Basic page-style class handling page setup and page modes. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\page; 27 | 28 | /** 29 | * Basic page-style class handling page setup and page modes. 30 | */ 31 | class base { 32 | /** @var string The page's URL (relative to Moodle root). */ 33 | protected $url = ''; 34 | 35 | /** @var string The page's title. */ 36 | protected $title = ''; 37 | 38 | /** @var \context The page's context. */ 39 | protected $context = null; 40 | 41 | /** 42 | * Constructor. 43 | * 44 | * @param string $url The page's URL (relative to Moodle root). 45 | * @param string $title The page's title. 46 | * @param \context $context The page's context. 47 | */ 48 | public function __construct($url, $title, $context = null) { 49 | global $PAGE; 50 | if (empty($context)) { 51 | $context = \context_system::instance(); 52 | } 53 | $this->set_context($context); 54 | $this->set_title($title); 55 | $this->set_url($url); 56 | $PAGE->set_pagelayout('standard'); 57 | $this->add_navbar(); 58 | } 59 | 60 | /** 61 | * Add base navbar for this page. 62 | */ 63 | protected function add_navbar() { 64 | global $PAGE; 65 | $PAGE->navbar->add($this->title, $this->url); 66 | } 67 | 68 | /** 69 | * Hook function run before the main page mode. 70 | * 71 | * @return bool True. 72 | */ 73 | public function header() { 74 | return true; 75 | } 76 | 77 | /** 78 | * Set the title of the page. 79 | * 80 | * @param string $title The title of the page. 81 | */ 82 | public function set_title($title) { 83 | global $PAGE; 84 | $this->title = $title; 85 | $PAGE->set_title($this->title); 86 | $PAGE->set_heading($this->title); 87 | } 88 | 89 | /** 90 | * Set the URL of the page. 91 | * 92 | * @param string|moodle_url $url The new page URL. 93 | */ 94 | public function set_url($url) { 95 | global $PAGE; 96 | $this->url = (string)$url; 97 | $PAGE->set_url($this->url); 98 | } 99 | 100 | /** 101 | * Set the context of the page. 102 | * 103 | * @param \context $context The new page context. 104 | */ 105 | public function set_context(\context $context) { 106 | global $PAGE; 107 | $this->context = $context; 108 | $PAGE->set_context($this->context); 109 | } 110 | 111 | /** 112 | * Default action. 113 | */ 114 | public function mode_default() { 115 | return true; 116 | } 117 | 118 | /** 119 | * Standard page header. 120 | */ 121 | protected function standard_header() { 122 | global $OUTPUT; 123 | echo $OUTPUT->header(); 124 | echo \html_writer::tag('h2', $this->title); 125 | } 126 | 127 | /** 128 | * Standard page footer. 129 | */ 130 | protected function standard_footer() { 131 | global $OUTPUT; 132 | echo $OUTPUT->footer(); 133 | } 134 | 135 | /** 136 | * Run a page mode. 137 | * 138 | * @param string $mode The page mode to run. 139 | */ 140 | public function run($mode) { 141 | $this->header(); 142 | $methodname = (!empty($mode)) ? 'mode_'.$mode : 'mode_default'; 143 | if (!method_exists($this, $methodname)) { 144 | $methodname = 'mode_default'; 145 | } 146 | $this->$methodname(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /classes/task/cohortsync.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A scheduled task to process Microsoft group and Moodle cohort mapping. 19 | * 20 | * @package local_o365 21 | * @copyright Enovation Solutions Ltd. {@link https://enovation.ie} 22 | * @author Patryk Mroczko 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace local_o365\task; 27 | 28 | use core\exception\moodle_exception; 29 | use core\task\scheduled_task; 30 | use local_o365\feature\cohortsync\main; 31 | use local_o365\utils; 32 | 33 | /** 34 | * A scheduled task to process Microsoft group and Moodle cohort mapping. 35 | */ 36 | class cohortsync extends scheduled_task { 37 | /** 38 | * Get the name of the task. 39 | * 40 | * @return string 41 | */ 42 | public function get_name(): string { 43 | return get_string('cohortsync_taskname', 'local_o365'); 44 | } 45 | 46 | /** 47 | * Execute the scheduled task. 48 | * 49 | * @return bool 50 | */ 51 | public function execute(): bool { 52 | try { 53 | $graphclient = main::get_unified_api(__METHOD__); 54 | if (empty($graphclient)) { 55 | utils::mtrace("Failed to get Graph API client. Exiting.", 1); 56 | 57 | return true; 58 | } 59 | } catch (moodle_exception $e) { 60 | utils::mtrace("Failed to get Graph API client. Exiting.", 1); 61 | 62 | return true; 63 | } 64 | 65 | $cohortsyncmain = new main($graphclient); 66 | $this->execute_sync($cohortsyncmain); 67 | 68 | return true; 69 | } 70 | 71 | /** 72 | * Execute synchronization. 73 | * 74 | * @param main $cohortsync 75 | * @return void 76 | */ 77 | private function execute_sync(main $cohortsync): void { 78 | if ($cohortsync->update_groups_cache()) { 79 | utils::clean_up_not_found_groups(); 80 | } else { 81 | utils::mtrace("Failed to update groups cache. Exiting.", 1); 82 | 83 | return; 84 | } 85 | 86 | utils::mtrace("Start processing cohort mappings.", 1); 87 | $grouplist = $cohortsync->get_grouplist(); 88 | utils::mtrace("Found " . count($grouplist) . " groups.", 2); 89 | $grouplistbyoid = []; 90 | foreach ($grouplist as $group) { 91 | $grouplistbyoid[$group->objectid] = $group; 92 | } 93 | 94 | $mappings = $cohortsync->get_mappings(); 95 | 96 | if (empty($mappings)) { 97 | utils::mtrace("No mappings found. Nothing to process. Exiting.", 1); 98 | utils::mtrace("", 1); 99 | 100 | return; 101 | } 102 | utils::mtrace("Found " . count($mappings) . " mappings.", 2); 103 | 104 | $cohorts = $cohortsync->get_cohortlist(); 105 | 106 | foreach ($mappings as $key => $mapping) { 107 | // Verify that the group still exists. 108 | if (!in_array($mapping->objectid, array_keys($grouplistbyoid))) { 109 | $cohortsync->delete_mapping_by_group_oid_and_cohort_id($mapping->objectid, $mapping->moodleid); 110 | utils::mtrace("Deleted mapping for non-existing group ID {$mapping->objectid}.", 3); 111 | unset($mappings[$key]); 112 | } 113 | 114 | // Verify that the cohort still exists. 115 | if (!in_array($mapping->moodleid, array_keys($cohorts))) { 116 | $cohortsync->delete_mapping_by_group_oid_and_cohort_id($mapping->objectid, $mapping->moodleid); 117 | utils::mtrace("Deleted mapping for non-existing cohort ID {$mapping->moodleid}.", 3); 118 | unset($mappings[$key]); 119 | } 120 | } 121 | 122 | foreach ($mappings as $mapping) { 123 | utils::mtrace("Processing mapping for group ID {$mapping->objectid} and cohort ID {$mapping->moodleid}.", 3); 124 | $cohortsync->sync_members_by_group_oid_and_cohort_id($mapping->objectid, $mapping->moodleid); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /classes/task/coursemembershipsync.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A scheduled task to sync Microsoft Teams owners and members to Moodle courses. 19 | * 20 | * @package local_o365 21 | * @copyright Enovation Solutions Ltd. {@link https://enovation.ie} 22 | * @author Patryk Mroczko 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace local_o365\task; 27 | 28 | use core\task\scheduled_task; 29 | use local_o365\feature\coursesync\main; 30 | use local_o365\utils; 31 | 32 | defined('MOODLE_INTERNAL') || die(); 33 | 34 | require_once($CFG->dirroot . '/local/o365/lib.php'); 35 | 36 | /** 37 | * A scheduled task to sync Microsoft Teams owners and members to Moodle courses. 38 | * 39 | * @package local_o365 40 | * @subpackage local_o365\task 41 | */ 42 | class coursemembershipsync extends scheduled_task { 43 | /** 44 | * Get the name of the task. 45 | * 46 | * @return string 47 | */ 48 | public function get_name(): string { 49 | return get_string('task_coursemembershipsync', 'local_o365'); 50 | } 51 | 52 | /** 53 | * Execute the task. 54 | * 55 | * @return bool 56 | */ 57 | public function execute(): bool { 58 | // If the sync direction is Moodle to Teams, we don't want to sync the course membership. 59 | $courseusersyncdirection = get_config('local_o365', 'courseusersyncdirection'); 60 | if ($courseusersyncdirection == COURSE_USER_SYNC_DIRECTION_MOODLE_TO_TEAMS) { 61 | mtrace('Sync direction is Moodle to Teams. Exiting.'); 62 | return false; 63 | } 64 | 65 | // Get role IDs from config. 66 | $ownerroleid = get_config('local_o365', 'coursesyncownerrole'); 67 | $memberroleid = get_config('local_o365', 'coursesyncmemberrole'); 68 | if (!$ownerroleid || !$memberroleid) { 69 | mtrace('Owner or member role ID is not set. Exiting.'); 70 | return false; 71 | } 72 | 73 | if (utils::is_connected() !== true || \local_o365\feature\coursesync\utils::is_enabled() !== true) { 74 | return false; 75 | } 76 | 77 | $graphclient = \local_o365\feature\coursesync\utils::get_unified_api(); 78 | 79 | if ($graphclient) { 80 | $coursesync = new main($graphclient, true); 81 | $coursesenabled = \local_o365\feature\coursesync\utils::get_enabled_courses(true); 82 | 83 | $connectedusers = utils::get_connected_users(); 84 | 85 | // Sync the course membership for each course. 86 | foreach ($coursesenabled as $courseid) { 87 | $coursesync->process_course_team_user_sync_from_microsoft_to_moodle($courseid, '', $connectedusers); 88 | } 89 | } 90 | 91 | return true; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /classes/task/coursesync.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Create any needed groups in Microsoft 365. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\task; 27 | 28 | use core\task\scheduled_task; 29 | use local_o365\feature\coursesync\main; 30 | use local_o365\utils; 31 | use moodle_exception; 32 | 33 | /** 34 | * Create any needed groups in Microsoft 365. 35 | */ 36 | class coursesync extends scheduled_task { 37 | /** 38 | * Get a descriptive name for this task (shown to admins). 39 | * 40 | * @return string 41 | */ 42 | public function get_name() { 43 | return get_string('task_coursesync', 'local_o365'); 44 | } 45 | 46 | /** 47 | * Do the job. 48 | * 49 | * @return bool|void 50 | */ 51 | public function execute() { 52 | global $SESSION; 53 | 54 | $SESSION->o365_groups_not_exist = []; 55 | $SESSION->o365_newly_created_groups = []; 56 | $SESSION->o365_users_not_exist = []; 57 | 58 | if (utils::is_connected() !== true) { 59 | return false; 60 | } 61 | 62 | if (\local_o365\feature\coursesync\utils::is_enabled() !== true) { 63 | mtrace('Course synchronisation not enabled, skipping...'); 64 | return true; 65 | } 66 | 67 | try { 68 | $graphclient = utils::get_api(); 69 | } catch (moodle_exception $e) { 70 | utils::debug('Exception: ' . $e->getMessage(), __METHOD__, $e); 71 | return false; 72 | } 73 | 74 | $coursesync = new main($graphclient, true); 75 | $coursesync->sync_courses(); 76 | if ($coursesync->update_teams_cache()) { 77 | $coursesync->cleanup_teams_connections(); 78 | } 79 | $coursesync->cleanup_course_connection_records(); 80 | 81 | if (utils::update_groups_cache($graphclient, 1)) { 82 | $coursesync->save_not_found_groups(); 83 | utils::clean_up_not_found_groups(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /classes/task/groupmembershipsync.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Ad-hoc task to sync Moodle course role assignment changes to Microsoft Groups. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2022 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\task; 27 | 28 | use core\task\adhoc_task; 29 | use local_o365\feature\coursesync\main; 30 | use local_o365\utils; 31 | 32 | defined('MOODLE_INTERNAL') || die(); 33 | 34 | require_once($CFG->dirroot . '/local/o365/lib.php'); 35 | 36 | /** 37 | * Ad-hoc task to sync Moodle course role assignment changes to Microsoft Groups. 38 | * 39 | * @package local_o365 40 | * @subpackage local_o365\task 41 | */ 42 | class groupmembershipsync extends adhoc_task { 43 | /** 44 | * Check if the course sync feature is enabled, get all courses that are enabled for sync, and resync owners and members. 45 | * 46 | * @return false|void 47 | */ 48 | public function execute() { 49 | // If the sync direction is Teams to Moodle, we don't want to sync the course membership. Exiting. 50 | $courseusersyncdirection = get_config('local_o365', 'courseusersyncdirection'); 51 | if ($courseusersyncdirection == COURSE_USER_SYNC_DIRECTION_TEAMS_TO_MOODLE) { 52 | mtrace('Sync direction is Teams to Moodle. Exiting.'); 53 | return false; 54 | } 55 | 56 | if (utils::is_connected() !== true || \local_o365\feature\coursesync\utils::is_enabled() !== true) { 57 | return false; 58 | } 59 | 60 | $graphclient = \local_o365\feature\coursesync\utils::get_unified_api(); 61 | if ($graphclient) { 62 | $coursesync = new main($graphclient); 63 | 64 | $coursesenabled = \local_o365\feature\coursesync\utils::get_enabled_courses(true); 65 | foreach ($coursesenabled as $courseid) { 66 | $coursesync->process_course_team_user_sync_from_moodle_to_microsoft($courseid); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /classes/task/processcourserequestapproval.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * An adhoc task to process course request approval. 19 | * 20 | * @package local_o365 21 | * @copyright Enovation Solutions Ltd. {@link https://enovation.ie} 22 | * @author Patryk Mroczko 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace local_o365\task; 27 | 28 | use core\task\adhoc_task; 29 | use local_o365\feature\courserequest\main; 30 | use local_o365\feature\coursesync\utils; 31 | use moodle_exception; 32 | use stdClass; 33 | 34 | /** 35 | * Adhoc task for processing course request approval. 36 | */ 37 | class processcourserequestapproval extends adhoc_task { 38 | /** 39 | * Execute the task. 40 | * 41 | * @return bool 42 | */ 43 | public function execute(): bool { 44 | global $DB; 45 | 46 | $coursedata = $this->get_custom_data(); 47 | 48 | $courserequestdata = $coursedata->customrequest; 49 | 50 | // If the core course request still exists, it means that the course has not been initiated using a custom course request 51 | // from Teams feature. 52 | if ($DB->record_exists('course_request', ['id' => $courserequestdata->requestid])) { 53 | mtrace("... Course request with shortname {$coursedata->shortname} still exists. " . 54 | "Course with ID {$coursedata->courseid} was not initiated using a custom course request from Teams feature. " . 55 | "Exiting."); 56 | 57 | return true; 58 | } 59 | 60 | $apiclient = main::get_unified_api(); 61 | if (empty($apiclient)) { 62 | throw new moodle_exception('errorcannotgetapiclient', 'local_o365'); 63 | } 64 | $courserequest = new main($apiclient); 65 | 66 | mtrace("... Start sync between Moodle Course with ID {$coursedata->courseid} and Microsoft Team with OID " . 67 | "{$courserequestdata->teamoid}..."); 68 | 69 | $courserequest->enrol_team_owners_and_members_in_course_by_team_oid_and_course_id($courserequestdata->teamoid, 70 | $coursedata->courseid); 71 | 72 | // Update custom course request status. 73 | $courserequestdata->requeststatus = main::COURSE_REQUEST_STATUS_APPROVED; 74 | $courserequestdata->courseid = $coursedata->courseid; 75 | $DB->update_record('local_o365_course_request', $courserequestdata); 76 | 77 | // Add course and Teams connection records. 78 | $grouprecord = new stdClass(); 79 | $grouprecord->type = 'group'; 80 | $grouprecord->subtype = 'course'; 81 | $grouprecord->objectid = $courserequestdata->teamoid; 82 | $grouprecord->moodleid = $coursedata->courseid; 83 | $grouprecord->o365name = $courserequestdata->teamname; 84 | $grouprecord->timecreated = time(); 85 | $grouprecord->timemodified = $grouprecord->timecreated; 86 | $DB->insert_record('local_o365_objects', $grouprecord); 87 | 88 | $grouprecord->subtype = 'teamfromgroup'; 89 | // Ideally, this should be "coursefromteam", a new subtype to indicate that the course is created from a Team. 90 | $DB->insert_record('local_o365_objects', $grouprecord); 91 | 92 | // Save course sync status. 93 | utils::set_course_sync_enabled($coursedata->courseid); 94 | 95 | // Update teams cache record status. 96 | if ($teamcacherecord = $DB->get_record('local_o365_teams_cache', ['objectid' => $courserequestdata->teamoid])) { 97 | if ($teamcacherecord->locked != TEAM_LOCKED) { 98 | $teamcacherecord->locked = TEAM_LOCKED; 99 | $DB->update_record('local_o365_teams_cache', $teamcacherecord); 100 | } 101 | } 102 | 103 | return true; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /classes/task/updatecourserequeststatus.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A scheduled task to clean up custom course request records. 19 | * 20 | * @package local_o365 21 | * @copyright Enovation Solutions Ltd. {@link https://enovation.ie} 22 | * @author Patryk Mroczko 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace local_o365\task; 27 | 28 | use core\task\scheduled_task; 29 | use local_o365\feature\courserequest\main; 30 | 31 | /** 32 | * Scheduled task for updating status of custom course requests. 33 | */ 34 | class updatecourserequeststatus extends scheduled_task { 35 | /** 36 | * Get task name. 37 | * 38 | * @return string 39 | */ 40 | public function get_name() { 41 | return get_string('courserequest_updatecourserequeststatus_taskname', 'local_o365'); 42 | } 43 | 44 | /** 45 | * Execute the task. 46 | * 47 | * @return void 48 | */ 49 | public function execute() { 50 | global $DB; 51 | 52 | $customcourserequests = $DB->get_records('local_o365_course_request', 53 | ['requeststatus' => main::COURSE_REQUEST_STATUS_PENDING]); 54 | 55 | if (empty($customcourserequests)) { 56 | mtrace('... No custom course requests to process.'); 57 | 58 | return true; 59 | } 60 | 61 | $count = count($customcourserequests); 62 | mtrace("... Processing {$count} custom course requests."); 63 | 64 | foreach ($customcourserequests as $customcourserequest) { 65 | if (!$DB->record_exists('course_request', ['id' => $customcourserequest->requestid])) { 66 | $DB->set_field('local_o365_course_request', 'requeststatus', main::COURSE_REQUEST_STATUS_REJECTED, 67 | ['id' => $customcourserequest->id]); 68 | mtrace("...... Course request with id {$customcourserequest->requestid} does not exists. " . 69 | "Status of custom course request with id {$customcourserequest->id} was updated to 'Rejected'."); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /classes/tests/mockhttpclient.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A mock HTTP client allowing set responses. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365\tests; 27 | 28 | use moodle_exception; 29 | 30 | /** 31 | * A mock HTTP client allowing set responses. 32 | * 33 | * @codeCoverageIgnore 34 | */ 35 | class mockhttpclient extends \local_o365\httpclient { 36 | /** @var string The stored set response. */ 37 | protected $mockresponse = ''; 38 | 39 | /** @var int The index of the current response. */ 40 | protected $curresponse = 0; 41 | 42 | /** @var array Array of executed requests. */ 43 | protected $requests = []; 44 | 45 | /** 46 | * Set a response to return. 47 | * 48 | * @param string $response The response to return. 49 | */ 50 | public function set_response($response) { 51 | $this->set_responses([$response]); 52 | } 53 | 54 | /** 55 | * Get executed requests. 56 | * 57 | * @return array Array of executed requests. 58 | */ 59 | public function get_requests() { 60 | return $this->requests; 61 | } 62 | 63 | /** 64 | * Set multiple responses. 65 | * 66 | * Responses will be returned in sequence every time $this->request is called. I.e. The first 67 | * time request() is called, the first item in the response array will be returned, the second time it's 68 | * called the second item will be returned, etc. 69 | * 70 | * @param array $responses Array of responses. 71 | */ 72 | public function set_responses(array $responses) { 73 | $this->curresponse = 0; 74 | $this->mockresponse = $responses; 75 | } 76 | 77 | /** 78 | * Return the set response instead of making the actual HTTP request. 79 | * 80 | * @param string $url The request URL 81 | * @param array $options Additional curl options. 82 | * @return string The set response. 83 | * @throws moodle_exception If no responses are available. 84 | */ 85 | protected function request($url, $options = []) { 86 | $this->requests[] = [ 87 | 'url' => $url, 88 | 'options' => $options, 89 | ]; 90 | if (isset($this->mockresponse[$this->curresponse])) { 91 | $response = $this->mockresponse[$this->curresponse]; 92 | $this->curresponse++; 93 | return $response; 94 | } else { 95 | $this->curresponse = 0; 96 | if (!isset($this->mockresponse[$this->curresponse])) { 97 | throw new moodle_exception('errornoresponsesavailable', 'local_o365'); 98 | } 99 | return $this->mockresponse[$this->curresponse]; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /classes/webservices/create_onenoteassignment.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Create assignment API class. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | use context_course; 31 | use core_external\external_api; 32 | use core_external\external_function_parameters; 33 | use core_external\external_single_structure; 34 | use core_external\external_value; 35 | 36 | global $CFG; 37 | 38 | require_once($CFG->dirroot . '/course/modlib.php'); 39 | 40 | /** 41 | * Create assignment API class. 42 | */ 43 | class create_onenoteassignment extends external_api { 44 | /** 45 | * Returns description of method parameters. 46 | * 47 | * @return external_function_parameters The parameters object for this webservice method. 48 | */ 49 | public static function assignment_create_parameters() { 50 | return new external_function_parameters([ 51 | 'data' => new external_single_structure([ 52 | 'name' => new external_value(PARAM_TEXT, 'name'), 53 | 'course' => new external_value(PARAM_INT, 'course id'), 54 | 'intro' => new external_value(PARAM_TEXT, 'intro', VALUE_DEFAULT, ''), 55 | 'section' => new external_value(PARAM_INT, 'section', VALUE_DEFAULT, 0), 56 | 'visible' => new external_value(PARAM_BOOL, 'visible', VALUE_DEFAULT, false), 57 | 'duedate' => new external_value(PARAM_INT, 'duedate', VALUE_DEFAULT, 0), 58 | ]), 59 | ]); 60 | } 61 | 62 | /** 63 | * Performs assignment creation. 64 | * 65 | * @param array $data The incoming data parameter. 66 | * @return array An array of parameters, if successful. 67 | */ 68 | public static function assignment_create($data) { 69 | global $CFG; 70 | 71 | $params = self::validate_parameters(self::assignment_create_parameters(), ['data' => $data]); 72 | $params = $params['data']; 73 | 74 | $context = context_course::instance($params['course']); 75 | self::validate_context($context); 76 | 77 | $defaults = [ 78 | 'submissiondrafts' => 0, 79 | 'requiresubmissionstatement' => 0, 80 | 'sendnotifications' => 0, 81 | 'sendlatenotifications' => 0, 82 | 'duedate' => 0, 83 | 'cutoffdate' => 0, 84 | 'allowsubmissionsfromdate' => 0, 85 | 'gradingduedate' => 0, 86 | 'completionsubmit' => 0, 87 | 'teamsubmission' => 0, 88 | 'requireallteammemberssubmit' => 0, 89 | 'blindmarking' => 0, 90 | 'markingworkflow' => 0, 91 | 'markingallocation' => 0, 92 | 'grade' => (isset($CFG->gradepointdefault)) ? $CFG->gradepointdefault : 100, 93 | ]; 94 | 95 | $course = get_course($params['course']); 96 | 97 | $modinfo = [ 98 | 'modulename' => 'assign', 99 | 'course' => $course->id, 100 | 'section' => $params['section'], 101 | 'visible' => (int) $params['visible'], 102 | 'duedate' => (int) $params['duedate'], 103 | 'name' => $params['name'], 104 | 'cmidnumber' => '', 105 | 'introeditor' => ['text' => $params['intro'], 'format' => FORMAT_HTML, 'itemid' => null], 106 | 'assignsubmission_onenote_enabled' => 1, 107 | 'assignsubmission_onenote_maxfiles' => 1, 108 | 'assignsubmission_onenote_maxsizebytes' => 1024, 109 | ]; 110 | 111 | $modinfo = array_merge($defaults, $modinfo); 112 | $modinfo = create_module((object) $modinfo, $course); 113 | 114 | $modinfo = utils::get_assignment_return_info($modinfo->coursemodule, $modinfo->course); 115 | 116 | return ['data' => [$modinfo]]; 117 | } 118 | 119 | /** 120 | * Returns description of method result value. 121 | * 122 | * @return external_single_structure Object describing return parameters for this webservice method. 123 | */ 124 | public static function assignment_create_returns() { 125 | return utils::get_assignment_return_info_schema(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /classes/webservices/delete_onenoteassignment.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Delete assignment API class. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | use context_course; 31 | use core_external\external_api; 32 | use core_external\external_function_parameters; 33 | use core_external\external_single_structure; 34 | use core_external\external_value; 35 | 36 | global $CFG; 37 | 38 | require_once($CFG->dirroot . '/course/modlib.php'); 39 | 40 | /** 41 | * Delete assignment API class. 42 | */ 43 | class delete_onenoteassignment extends external_api { 44 | /** 45 | * Returns description of method parameters. 46 | * 47 | * @return external_function_parameters The parameters object for this webservice method. 48 | */ 49 | public static function assignment_delete_parameters() { 50 | return new external_function_parameters([ 51 | 'data' => new external_single_structure([ 52 | 'coursemodule' => new external_value(PARAM_INT, 'course module id'), 53 | 'course' => new external_value(PARAM_INT, 'course id'), 54 | ]), 55 | ]); 56 | } 57 | 58 | /** 59 | * Performs assignment read. 60 | * 61 | * @param array $data The incoming data parameter. 62 | * @return array An array of parameters, if successful. 63 | */ 64 | public static function assignment_delete($data) { 65 | global $DB; 66 | 67 | $params = self::validate_parameters(self::assignment_delete_parameters(), ['data' => $data]); 68 | $params = $params['data']; 69 | 70 | [$course, $module, $assign] = utils::verify_assignment($params['coursemodule'], 71 | $params['course']); 72 | 73 | $context = context_course::instance($params['course']); 74 | self::validate_context($context); 75 | 76 | // Course_delete_module will throw exception if error, so we can return true b/c if we get there it was successful. 77 | course_delete_module($module->id); 78 | 79 | return ['result' => true]; 80 | } 81 | 82 | /** 83 | * Returns description of method result value. 84 | * 85 | * @return external_single_structure Object describing return parameters for this webservice method. 86 | */ 87 | public static function assignment_delete_returns() { 88 | $params = [ 89 | 'result' => new external_value(PARAM_BOOL, 'success/failure'), 90 | ]; 91 | 92 | return new external_single_structure($params); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /classes/webservices/exception/assignnotfound.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception thrown when an associated assignment record is not found for a given course module. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices\exception; 27 | 28 | use moodle_exception; 29 | 30 | /** 31 | * Exception thrown when an associated assignment record is not found for a given course module. 32 | */ 33 | class assignnotfound extends moodle_exception { 34 | /** 35 | * Constructor. 36 | * 37 | * @param string $errorcode The name of the string from error.php to print 38 | * @param string $module name of module 39 | * @param string $link The url where the user will be prompted to continue, or site index page if no url is provided. 40 | * @param mixed $a Extra words and phrases that might be required in the error string 41 | * @param string $debuginfo optional debugging information 42 | */ 43 | public function __construct($errorcode = '', $module = '', $link = '', $a = null, $debuginfo = null) { 44 | return parent::__construct('webservices_error_assignnotfound', 'local_o365'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /classes/webservices/exception/couldnotsavegrade.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception thrown when a grade could not be saved in local_o365_update_grade. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices\exception; 27 | 28 | use moodle_exception; 29 | 30 | /** 31 | * Exception thrown when a grade could not be saved in local_o365_update_grade. 32 | */ 33 | class couldnotsavegrade extends moodle_exception { 34 | /** 35 | * Constructor. 36 | * 37 | * @param string $errorcode The name of the string from error.php to print 38 | * @param string $module name of module 39 | * @param string $link The url where the user will be prompted to continue, or site index page if no url is provided. 40 | * @param mixed $a Extra words and phrases that might be required in the error string 41 | * @param string $debuginfo optional debugging information 42 | */ 43 | public function __construct($errorcode = '', $module = '', $link = '', $a = null, $debuginfo = null) { 44 | return parent::__construct('webservices_error_couldnotsavegrade', 'local_o365'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /classes/webservices/exception/invalidassignment.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception thrown when a module is called that is not a OneNote assignment. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices\exception; 27 | 28 | use moodle_exception; 29 | 30 | /** 31 | * Exception thrown when a module is called that is not a OneNote assignment. 32 | */ 33 | class invalidassignment extends moodle_exception { 34 | /** 35 | * Constructor. 36 | * 37 | * @param string $errorcode The name of the string from error.php to print 38 | * @param string $module name of module 39 | * @param string $link The url where the user will be prompted to continue, or site index page if no url is provided. 40 | * @param mixed $a Extra words and phrases that might be required in the error string 41 | * @param string $debuginfo optional debugging information 42 | */ 43 | public function __construct($errorcode = '', $module = '', $link = '', $a = null, $debuginfo = null) { 44 | return parent::__construct('webservices_error_invalidassignment', 'local_o365'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /classes/webservices/exception/modulenotfound.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception thrown when a module that does not exist is called. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices\exception; 27 | 28 | use moodle_exception; 29 | 30 | /** 31 | * Exception thrown when a module that does not exist is called. 32 | */ 33 | class modulenotfound extends moodle_exception { 34 | /** 35 | * Constructor. 36 | * 37 | * @param string $errorcode The name of the string from error.php to print 38 | * @param string $module name of module 39 | * @param string $link The url where the user will be prompted to continue, or site index page if no url is provided. 40 | * @param mixed $a Extra words and phrases that might be required in the error string 41 | * @param string $debuginfo optional debugging information 42 | */ 43 | public function __construct($errorcode = '', $module = '', $link = '', $a = null, $debuginfo = null) { 44 | return parent::__construct('webservices_error_modulenotfound', 'local_o365'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /classes/webservices/exception/sectionnotfound.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Exception thrown when a course section that does not exist is used to update an assignment. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices\exception; 27 | 28 | use moodle_exception; 29 | 30 | /** 31 | * Exception thrown when a course section that does not exist is used to update an assignment. 32 | */ 33 | class sectionnotfound extends moodle_exception { 34 | /** 35 | * Constructor. 36 | * 37 | * @param string $errorcode The name of the string from error.php to print 38 | * @param string $module name of module 39 | * @param string $link The url where the user will be prompted to continue, or site index page if no url is provided. 40 | * @param mixed $a Extra words and phrases that might be required in the error string 41 | * @param string $debuginfo optional debugging information 42 | */ 43 | public function __construct($errorcode = '', $module = '', $link = '', $a = null, $debuginfo = null) { 44 | return parent::__construct('webservices_error_sectionnotfound', 'local_o365'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /classes/webservices/read_onenoteassignment.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Read assignment API class. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | use context_course; 31 | use core_external\external_api; 32 | use core_external\external_function_parameters; 33 | use core_external\external_single_structure; 34 | use core_external\external_value; 35 | 36 | global $CFG; 37 | 38 | require_once($CFG->dirroot . '/course/modlib.php'); 39 | 40 | /** 41 | * Read assignment API class. 42 | */ 43 | class read_onenoteassignment extends external_api { 44 | /** 45 | * Returns description of method parameters. 46 | * 47 | * @return external_function_parameters The parameters object for this webservice method. 48 | */ 49 | public static function assignment_read_parameters() { 50 | return new external_function_parameters([ 51 | 'data' => new external_single_structure([ 52 | 'coursemodule' => new external_value(PARAM_INT, 'course module id'), 53 | 'course' => new external_value(PARAM_INT, 'course id'), 54 | ]), 55 | ]); 56 | } 57 | 58 | /** 59 | * Performs assignment read. 60 | * 61 | * @param array $data The incoming data parameter. 62 | * @return array An array of parameters, if successful. 63 | */ 64 | public static function assignment_read($data) { 65 | $params = self::validate_parameters(self::assignment_read_parameters(), ['data' => $data]); 66 | $params = $params['data']; 67 | [$course, $module, $assign] = utils::verify_assignment($params['coursemodule'], 68 | $params['course']); 69 | 70 | $context = context_course::instance($params['course']); 71 | self::validate_context($context); 72 | 73 | $modinfo = utils::get_assignment_return_info($module->id, $course->id); 74 | 75 | return ['data' => [$modinfo]]; 76 | } 77 | 78 | /** 79 | * Returns description of method result value. 80 | * 81 | * @return external_single_structure Object describing return parameters for this webservice method. 82 | */ 83 | public static function assignment_read_returns() { 84 | return utils::get_assignment_return_info_schema(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /classes/webservices/read_teachercourses.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Get a list of courses where the current user is a teacher. 19 | * 20 | * @package local_o365 21 | * @author 2011 Jerome Mouneyrac, modified 2016 James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright 2011 Jerome Mouneyrac 24 | */ 25 | 26 | namespace local_o365\webservices; 27 | 28 | use context_course; 29 | use moodle_exception; 30 | 31 | defined('MOODLE_INTERNAL') || die(); 32 | 33 | use core_external\external_api; 34 | use core_external\external_function_parameters; 35 | use core_external\external_multiple_structure; 36 | use core_external\external_single_structure; 37 | use core_external\external_value; 38 | 39 | global $CFG; 40 | 41 | require_once($CFG->dirroot . '/course/modlib.php'); 42 | 43 | /** 44 | * Get a list of courses where the current user is a teacher. 45 | */ 46 | class read_teachercourses extends external_api { 47 | /** 48 | * Returns description of method parameters 49 | * 50 | * @return external_function_parameters 51 | */ 52 | public static function teachercourses_read_parameters() { 53 | return new external_function_parameters([ 54 | 'courseids' => new external_multiple_structure( 55 | new external_value(PARAM_INT, 'course id, empty to retrieve all courses'), 56 | '0 or more course ids', 57 | VALUE_DEFAULT, 58 | [] 59 | ), 60 | ]); 61 | } 62 | 63 | /** 64 | * Get list of courses user is enrolled in (only active enrolments are returned). 65 | * Please note the current user must be able to access the course, otherwise the course is not included. 66 | * 67 | * @param array $courseids 68 | * @return array of courses 69 | */ 70 | public static function teachercourses_read($courseids = []) { 71 | global $USER; 72 | 73 | // Validate params. 74 | $params = self::validate_parameters( 75 | self::teachercourses_read_parameters(), 76 | [ 77 | 'courseids' => $courseids, 78 | ] 79 | ); 80 | 81 | $courseids = (!empty($params['courseids']) && is_array($params['courseids'])) ? array_flip($params['courseids']) : []; 82 | 83 | // Get courses. 84 | $fields = 'id, shortname, fullname, idnumber, visible, format, showgrades, lang, enablecompletion'; 85 | $courses = enrol_get_users_courses($USER->id, true, $fields); 86 | 87 | $result = []; 88 | 89 | foreach ($courses as $course) { 90 | if (!empty($courseids) && !isset($courseids[$course->id])) { 91 | continue; 92 | } 93 | 94 | $context = context_course::instance($course->id, IGNORE_MISSING); 95 | 96 | // Validate the user can execute functions in this course. 97 | try { 98 | static::validate_context($context); 99 | } catch (moodle_exception $e) { 100 | continue; 101 | } 102 | 103 | // We'll use the grade:edit capability to define "teacher". 104 | if (!has_capability('moodle/grade:edit', $context)) { 105 | continue; 106 | } 107 | 108 | $result[] = [ 109 | 'id' => $course->id, 110 | 'shortname' => $course->shortname, 111 | 'fullname' => $course->fullname, 112 | 'idnumber' => $course->idnumber, 113 | 'visible' => $course->visible, 114 | 'format' => $course->format, 115 | 'showgrades' => $course->showgrades, 116 | 'lang' => $course->lang, 117 | 'enablecompletion' => $course->enablecompletion, 118 | ]; 119 | } 120 | 121 | return $result; 122 | } 123 | 124 | /** 125 | * Returns description of method result value 126 | * 127 | * @return external_multiple_structure 128 | */ 129 | public static function teachercourses_read_returns() { 130 | return new external_multiple_structure( 131 | new external_single_structure( 132 | [ 133 | 'id' => new external_value(PARAM_INT, 'id of course'), 134 | 'shortname' => new external_value(PARAM_RAW, 'short name of course'), 135 | 'fullname' => new external_value(PARAM_RAW, 'long name of course'), 136 | 'idnumber' => new external_value(PARAM_RAW, 'id number of course'), 137 | 'visible' => new external_value(PARAM_INT, '1 means visible, 0 means hidden course'), 138 | 'format' => new external_value(PARAM_PLUGIN, 'course format: weeks, topics, social, site', VALUE_OPTIONAL), 139 | 'showgrades' => new external_value(PARAM_BOOL, 'true if grades are shown, otherwise false', VALUE_OPTIONAL), 140 | 'lang' => new external_value(PARAM_LANG, 'forced course language', VALUE_OPTIONAL), 141 | 'enablecompletion' => new external_value(PARAM_BOOL, 'true if completion is enabled, otherwise false', 142 | VALUE_OPTIONAL), 143 | ] 144 | ) 145 | ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /classes/webservices/utils.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Webservices utilities. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | namespace local_o365\webservices; 27 | 28 | use core_external\external_multiple_structure; 29 | use core_external\external_single_structure; 30 | use core_external\external_value; 31 | use local_o365\webservices\exception as exception; 32 | use moodle_exception; 33 | 34 | /** 35 | * Webservices utilities. 36 | */ 37 | class utils { 38 | /** 39 | * Verify this is an assignment we can affect. 40 | * 41 | * This checks whether the given module ID is a OneNote assignment. 42 | * 43 | * @param int $coursemoduleid The course module ID. 44 | * @param int $courseid 45 | * @return array Whether we can proceed or not. 46 | * @throws exception\invalidassignment If the assignment is not a OneNote assignment. 47 | */ 48 | public static function verify_assignment($coursemoduleid, $courseid) { 49 | global $DB; 50 | 51 | [$course, $module, $assign] = static::get_assignment_info($coursemoduleid, $courseid); 52 | 53 | require_capability('moodle/course:manageactivities', \context_module::instance($module->id)); 54 | 55 | $pluginconfigparams = [ 56 | 'assignment' => $assign->id, 57 | 'plugin' => 'onenote', 58 | 'subtype' => 'assignsubmission', 59 | 'name' => 'enabled', 60 | ]; 61 | $assignpluginconfig = $DB->get_record('assign_plugin_config', $pluginconfigparams); 62 | 63 | if (empty($assignpluginconfig) || empty($assignpluginconfig->value)) { 64 | throw new exception\invalidassignment(); 65 | } 66 | 67 | return [$course, $module, $assign]; 68 | } 69 | 70 | /** 71 | * Get the external structure schema when returning information about an assignment. 72 | * 73 | * @return external_single_structure The return data schema. 74 | */ 75 | public static function get_assignment_return_info_schema() { 76 | $params = [ 77 | 'data' => new external_multiple_structure( 78 | new external_single_structure([ 79 | 'course' => new external_value(PARAM_INT, 'course id'), 80 | 'coursemodule' => new external_value(PARAM_INT, 'coursemodule id'), 81 | 'name' => new external_value(PARAM_TEXT, 'name'), 82 | 'intro' => new external_value(PARAM_TEXT, 'intro'), 83 | 'section' => new external_value(PARAM_INT, 'section'), 84 | 'visible' => new external_value(PARAM_INT, 'visible'), 85 | 'instance' => new external_value(PARAM_INT, 'instance id'), 86 | ]) 87 | ), 88 | ]; 89 | 90 | return new external_single_structure($params); 91 | } 92 | 93 | /** 94 | * Get assignment, module, and course information based on a coursemoduleid and courseid. 95 | * 96 | * @param int $coursemoduleid The course module ID. 97 | * @param int $courseid The course id the module belongs to. 98 | * @return array Array of assignment information, following the same schema as get_assignment_info_schema. 99 | * @throws moodle_exception 100 | */ 101 | public static function get_assignment_info($coursemoduleid, $courseid) { 102 | global $DB; 103 | $course = get_course($courseid); 104 | 105 | $module = $DB->get_record('course_modules', ['id' => $coursemoduleid]); 106 | if (empty($module)) { 107 | throw new exception\modulenotfound(); 108 | } 109 | 110 | $assign = $DB->get_record('assign', ['id' => $module->instance]); 111 | if (empty($assign)) { 112 | throw new exception\assignnotfound(); 113 | } 114 | 115 | return [$course, $module, $assign]; 116 | } 117 | 118 | /** 119 | * Get assignment info to return when returning assignment info. 120 | * 121 | * @param int $coursemoduleid The course module ID. 122 | * @param int $courseid The course id the module belongs to. 123 | * @return array Array of assignment information, following the same schema as get_assignment_info_schema. 124 | */ 125 | public static function get_assignment_return_info($coursemoduleid, $courseid) { 126 | [$course, $module, $assign] = static::get_assignment_info($coursemoduleid, $courseid); 127 | 128 | return [ 129 | 'course' => $course->id, 130 | 'coursemodule' => $module->id, 131 | 'name' => $assign->name, 132 | 'intro' => $assign->intro, 133 | 'section' => $module->section, 134 | 'visible' => $module->visible, 135 | 'instance' => $module->instance, 136 | ]; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /cohortsync.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A page to manage Microsoft group and Moodle cohort mapping. 19 | * 20 | * @package local_o365 21 | * @copyright Enovation Solutions Ltd. {@link https://enovation.ie} 22 | * @author Patryk Mroczko 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(__DIR__ . '/../../config.php'); 27 | 28 | require_once($CFG->libdir . '/adminlib.php'); 29 | 30 | use local_o365\feature\cohortsync\main; 31 | use local_o365\form\cohortsync; 32 | 33 | require_login(); 34 | require_capability('moodle/site:config', context_system::instance()); 35 | 36 | $pageurl = new moodle_url('/local/o365/cohortsync.php'); 37 | 38 | $PAGE->set_url($pageurl); 39 | $PAGE->set_context(context_system::instance()); 40 | $PAGE->set_title(get_string('cohortsync_title', 'local_o365')); 41 | $PAGE->set_pagelayout('admin'); 42 | $PAGE->set_heading(get_string('cohortsync_title', 'local_o365')); 43 | 44 | $PAGE->set_primary_active_tab('siteadminnode'); 45 | $PAGE->set_secondary_active_tab('modules'); 46 | 47 | $PAGE->navbar->add(get_string('administrationsite'), new moodle_url('/admin/search.php')); 48 | $PAGE->navbar->add(get_string('localplugins'), new moodle_url('/admin/category.php', ['category' => 'localplugins'])); 49 | $PAGE->navbar->add(get_string('pluginname', 'local_o365'), new moodle_url('/admin/settings.php', ['section' => 'local_o365'])); 50 | $PAGE->navbar->add(get_string('settings_cohortsync_title', 'local_o365'), new moodle_url('/local/o365/cohortsync.php')); 51 | 52 | $apiclient = main::get_unified_api(__METHOD__); 53 | if (empty($apiclient)) { 54 | throw new moodle_exception('cohortsync_unifiedapierror', 'local_o365'); 55 | } 56 | $cohortsyncmain = new main($apiclient); 57 | $cohortsyncmain->fetch_groups_from_cache(); 58 | 59 | $cohortsyncform = new cohortsync(null, ['cohortsyncmain' => $cohortsyncmain]); 60 | 61 | $action = optional_param('action', '', PARAM_ALPHA); 62 | if ($action == 'delete') { 63 | $connectionid = required_param('connectionid', PARAM_INT); 64 | if (!$connectionrecord = $DB->get_record('local_o365_objects', ['id' => $connectionid])) { 65 | throw new moodle_exception('cohortsync_connectionnotfound', 'local_o365'); 66 | } 67 | if ($connectionrecord->type != 'group' || $connectionrecord->subtype != 'cohort') { 68 | throw new moodle_exception('cohortsync_connectionnotcohortsync', 'local_o365'); 69 | } 70 | 71 | $cohortsyncmain->delete_mapping_by_id($connectionid); 72 | 73 | redirect($pageurl, get_string('cohortsync_mappingdeleted', 'local_o365')); 74 | } 75 | 76 | if ($fromform = $cohortsyncform->get_data()) { 77 | $groupoid = $fromform->groupoid; 78 | $cohortid = $fromform->cohortid; 79 | 80 | if ($cohortsyncmain->add_mapping($groupoid, $cohortid)) { 81 | redirect($pageurl, get_string('cohortsync_mappingadded', 'local_o365')); 82 | } else { 83 | throw new moodle_exception('cohortsync_mappingfailed', 'local_o365'); 84 | } 85 | } 86 | 87 | echo $OUTPUT->header(); 88 | 89 | $cohortsyncform->display(); 90 | 91 | echo $OUTPUT->footer(); 92 | -------------------------------------------------------------------------------- /courserequest.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * A page that allows authorised users to make a course request based on a Microsoft Team. 19 | * 20 | * @package local_o365 21 | * @copyright Enovation Solutions Ltd. {@link https://enovation.ie} 22 | * @author Patryk Mroczko 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(__DIR__ . '/../../config.php'); 27 | require_once($CFG->dirroot . '/course/lib.php'); 28 | 29 | use local_o365\feature\courserequest\main; 30 | use local_o365\form\courserequestform; 31 | 32 | // Where we came from. Used in a number of redirects. 33 | $url = new moodle_url('/local/o365/courserequest.php'); 34 | $return = optional_param('return', null, PARAM_ALPHANUMEXT); 35 | $categoryid = optional_param('category', null, PARAM_INT); 36 | if ($return === 'management') { 37 | $url->param('return', $return); 38 | $returnurl = new moodle_url('/course/management.php', ['categoryid' => $CFG->defaultrequestcategory]); 39 | } else { 40 | $returnurl = new moodle_url('/course/index.php'); 41 | } 42 | 43 | $PAGE->set_url($url); 44 | 45 | // Check permissions. 46 | require_login(null, false); 47 | if (isguestuser()) { 48 | throw new moodle_exception('guestsarenotallowed', '', $returnurl); 49 | } 50 | if (empty($CFG->enablecourserequests)) { 51 | throw new moodle_exception('courserequestdisabled', '', $returnurl); 52 | } 53 | 54 | if ($CFG->lockrequestcategory) { 55 | // Course request category is locked, user will always request in the default request category. 56 | $categoryid = null; 57 | } else if (!$categoryid) { 58 | // Category selection is enabled but category is not specified. 59 | // Find a category where user has capability to request courses (preferably the default category). 60 | $list = core_course_category::make_categories_list('moodle/course:request'); 61 | $categoryid = array_key_exists($CFG->defaultrequestcategory, $list) ? $CFG->defaultrequestcategory : key($list); 62 | } 63 | 64 | $context = context_coursecat::instance($categoryid ?: $CFG->defaultrequestcategory); 65 | $PAGE->set_context($context); 66 | require_capability('moodle/course:request', $context); 67 | 68 | // Set up the form. 69 | $data = $categoryid ? (object) ['category' => $categoryid] : null; 70 | $data = course_request::prepare($data); 71 | $requestform = new courserequestform($url); 72 | $requestform->set_data($data); 73 | 74 | $strtitle = get_string('courserequest_title', 'local_o365'); 75 | $PAGE->set_title($strtitle); 76 | $coursecategory = core_course_category::get($categoryid, MUST_EXIST, true); 77 | $PAGE->set_heading($coursecategory->get_formatted_name()); 78 | $PAGE->set_primary_active_tab('home'); 79 | $PAGE->set_secondary_navigation(false); 80 | 81 | // Standard form processing if statement. 82 | if ($requestform->is_cancelled()) { 83 | redirect($returnurl); 84 | 85 | } else if ($data = $requestform->get_data()) { 86 | $apiclient = main::get_unified_api(); 87 | if (empty($apiclient)) { 88 | throw new moodle_exception('courserequest_graphapi_disabled', 'local_o365', $returnurl); 89 | } 90 | $courserequestmain = new main($apiclient); 91 | 92 | $teamdata = $courserequestmain->get_user_team_details_by_team_oid($data->team); 93 | 94 | if (!$teamdata) { 95 | throw new moodle_exception('courserequest_invalid_team', 'local_o365', $returnurl); 96 | } 97 | 98 | $data->reason .= get_string('courserequest_customrequestnote', 'local_o365', 99 | ['name' => $teamdata['name'], 'url' => $teamdata['url']]); 100 | 101 | $request = course_request::create($data); 102 | 103 | if (!$courserequestmain->save_custom_course_request_data($request, $teamdata)) { 104 | throw new moodle_exception('courserequestfailed', '', $returnurl); 105 | } 106 | 107 | // And redirect back to the course listing. 108 | notice(get_string('courserequestsuccess'), $returnurl); 109 | } 110 | 111 | $categoryurl = new moodle_url('/course/index.php'); 112 | if ($categoryid) { 113 | $categoryurl->param('categoryid', $categoryid); 114 | } 115 | navigation_node::override_active_url($categoryurl); 116 | 117 | $PAGE->navbar->add($strtitle); 118 | echo $OUTPUT->header(); 119 | echo $OUTPUT->heading($strtitle); 120 | // Show the request form. 121 | $requestform->display(); 122 | echo $OUTPUT->footer(); 123 | -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Capability definition. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $capabilities = [ 29 | 'local/o365:managegroups' => [ 30 | 'riskbitmask' => RISK_SPAM | RISK_XSS, 31 | 'captype' => 'write', 32 | 'contextlevel' => CONTEXT_COURSE, 33 | 'archetypes' => [ 34 | 'editingteacher' => CAP_ALLOW, 35 | 'manager' => CAP_ALLOW, 36 | ], 37 | ], 38 | 'local/o365:viewgroups' => [ 39 | 'riskbitmask' => RISK_SPAM | RISK_XSS, 40 | 'captype' => 'read', 41 | 'contextlevel' => CONTEXT_COURSE, 42 | 'archetypes' => [ 43 | 'student' => CAP_ALLOW, 44 | ], 45 | ], 46 | 'local/o365:manageconnectionlink' => [ 47 | 'riskbitmask' => RISK_CONFIG, 48 | 'captype' => 'write', 49 | 'contextlevel' => CONTEXT_USER, 50 | 'archetypes' => [ 51 | 'manager' => CAP_ALLOW, 52 | ], 53 | ], 54 | 'local/o365:manageconnectionunlink' => [ 55 | 'riskbitmask' => RISK_CONFIG, 56 | 'captype' => 'write', 57 | 'contextlevel' => CONTEXT_USER, 58 | 'archetypes' => [ 59 | 'manager' => CAP_ALLOW, 60 | ], 61 | ], 62 | 'local/o365:teamowner' => [ 63 | 'riskbitmask' => RISK_PERSONAL, 64 | 'captype' => 'write', 65 | 'contextlevel' => CONTEXT_COURSE, 66 | 'archetypes' => [ 67 | 'editingteacher' => CAP_ALLOW, 68 | ], 69 | ], 70 | 'local/o365:teammember' => [ 71 | 'riskbitmask' => RISK_SPAM, 72 | 'captype' => 'write', 73 | 'contextlevel' => CONTEXT_COURSE, 74 | 'archetypes' => [ 75 | 'student' => CAP_ALLOW, 76 | ], 77 | ], 78 | ]; 79 | -------------------------------------------------------------------------------- /db/caches.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Cache definition. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $definitions = [ 29 | 'groups' => [ 30 | 'mode' => cache_store::MODE_SESSION, 31 | ], 32 | ]; 33 | -------------------------------------------------------------------------------- /db/install.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin installation script. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | require_once($CFG->dirroot.'/local/o365/lib.php'); 29 | 30 | /** 31 | * Installation script. 32 | */ 33 | function xmldb_local_o365_install() { 34 | local_o365_set_default_user_sync_suspension_feature_schedule(); 35 | } 36 | -------------------------------------------------------------------------------- /db/services.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Web service definition. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft Open Technologies, Inc. (http://msopentech.com/) 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $functions = [ 29 | 'local_o365_create_onenoteassignment' => [ 30 | 'classname' => '\local_o365\webservices\create_onenoteassignment', 31 | 'methodname' => 'assignment_create', 32 | 'classpath' => 'local/o365/classes/webservices/create_onenoteassignment.php', 33 | 'description' => 'Create an assignment', 34 | 'type' => 'write', 35 | ], 36 | 'local_o365_get_onenoteassignment' => [ 37 | 'classname' => '\local_o365\webservices\read_onenoteassignment', 38 | 'methodname' => 'assignment_read', 39 | 'classpath' => 'local/o365/classes/webservices/read_onenoteassignment.php', 40 | 'description' => 'Get an assignment', 41 | 'type' => 'read', 42 | ], 43 | 'local_o365_update_onenoteassignment' => [ 44 | 'classname' => '\local_o365\webservices\update_onenoteassignment', 45 | 'methodname' => 'assignment_update', 46 | 'classpath' => 'local/o365/classes/webservices/update_onenoteassignment.php', 47 | 'description' => 'Update an assignment', 48 | 'type' => 'write', 49 | ], 50 | 'local_o365_delete_onenoteassignment' => [ 51 | 'classname' => '\local_o365\webservices\delete_onenoteassignment', 52 | 'methodname' => 'assignment_delete', 53 | 'classpath' => 'local/o365/classes/webservices/delete_onenoteassignment.php', 54 | 'description' => 'Delete an assignment', 55 | 'type' => 'write', 56 | ], 57 | 'local_o365_get_teachercourses' => [ 58 | 'classname' => '\local_o365\webservices\read_teachercourses', 59 | 'methodname' => 'teachercourses_read', 60 | 'classpath' => 'local/o365/classes/webservices/read_teachercourses.php', 61 | 'description' => 'Get a list of courses that the current user is a teacher in.', 62 | 'type' => 'read', 63 | ], 64 | 'local_o365_get_course_users' => [ 65 | 'classname' => '\local_o365\webservices\read_courseusers', 66 | 'methodname' => 'courseusers_read', 67 | 'classpath' => 'local/o365/classes/webservices/read_courseusers.php', 68 | 'description' => 'Get a list of students in a course.', 69 | 'type' => 'read', 70 | ], 71 | 'local_o365_get_assignments' => [ 72 | 'classname' => '\local_o365\webservices\read_assignments', 73 | 'methodname' => 'assignments_read', 74 | 'classpath' => 'local/o365/classes/webservices/read_assignments.php', 75 | 'description' => 'Get a list of courses and assignments for the user', 76 | 'type' => 'read', 77 | ], 78 | 'local_o365_update_grade' => [ 79 | 'classname' => '\local_o365\webservices\update_grade', 80 | 'methodname' => 'grade_update', 81 | 'classpath' => 'local/o365/classes/webservices/update_grade.php', 82 | 'description' => 'Update a grade.', 83 | 'type' => 'write', 84 | ], 85 | ]; 86 | 87 | // Pre-built service. 88 | $services = [ 89 | 'Moodle Microsoft 365 Webservices' => [ 90 | 'functions' => [ 91 | 'local_o365_create_onenoteassignment', 92 | 'local_o365_get_onenoteassignment', 93 | 'local_o365_update_onenoteassignment', 94 | 'local_o365_delete_onenoteassignment', 95 | 'local_o365_get_teachercourses', 96 | 'local_o365_get_course_users', 97 | 'local_o365_get_assignments', 98 | 'local_o365_update_grade', 99 | 'mod_assign_get_assignments', 100 | 'mod_assign_get_grades', 101 | 'mod_assign_save_grade', 102 | ], 103 | 'restrictedusers' => 0, 104 | 'enabled' => 0, 105 | 'shortname' => 'o365_webservices', 106 | ], 107 | ]; 108 | -------------------------------------------------------------------------------- /db/tasks.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Scheduled and adhoc tasks definition. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $tasks = [ 29 | [ 30 | 'classname' => 'local_o365\task\usersync', 31 | 'blocking' => 0, 32 | 'minute' => '0', 33 | 'hour' => '1', 34 | 'day' => '*', 35 | 'dayofweek' => '*', 36 | 'month' => '*', 37 | ], 38 | [ 39 | 'classname' => 'local_o365\task\coursesync', 40 | 'blocking' => 0, 41 | 'minute' => '*', 42 | 'hour' => '*', 43 | 'day' => '*', 44 | 'dayofweek' => '*', 45 | 'month' => '*', 46 | ], 47 | [ 48 | 'classname' => 'local_o365\feature\calsync\task\importfromoutlook', 49 | 'blocking' => 0, 50 | 'minute' => '*', 51 | 'hour' => '*', 52 | 'day' => '*', 53 | 'dayofweek' => '*', 54 | 'month' => '*', 55 | ], 56 | [ 57 | 'classname' => 'local_o365\task\processmatchqueue', 58 | 'blocking' => 0, 59 | 'minute' => '*', 60 | 'hour' => '*', 61 | 'day' => '*', 62 | 'dayofweek' => '*', 63 | 'month' => '*', 64 | ], 65 | [ 66 | 'classname' => 'local_o365\feature\sds\task\sync', 67 | 'blocking' => 0, 68 | 'minute' => '1', 69 | 'hour' => '3', 70 | 'day' => '*', 71 | 'dayofweek' => '*', 72 | 'month' => '*', 73 | ], 74 | [ 75 | 'classname' => 'local_o365\task\notifysecretexpiry', 76 | 'blocking' => 0, 77 | 'minute' => 0, 78 | 'hour' => '3', 79 | 'day' => '*', 80 | 'dayofweek' => '*', 81 | 'month' => '*', 82 | ], 83 | [ 84 | 'classname' => 'local_o365\task\cohortsync', 85 | 'blocking' => 0, 86 | 'minute' => '*/5', 87 | 'hour' => '*', 88 | 'day' => '*', 89 | 'dayofweek' => '*', 90 | 'month' => '*', 91 | ], 92 | [ 93 | 'classname' => 'local_o365\task\updatecourserequeststatus', 94 | 'blocking' => 0, 95 | 'minute' => '*', 96 | 'hour' => '*', 97 | 'day' => '*', 98 | 'dayofweek' => '*', 99 | 'month' => '*', 100 | ], 101 | [ 102 | 'classname' => 'local_o365\task\coursemembershipsync', 103 | 'blocking' => 0, 104 | 'minute' => '*/5', 105 | 'hour' => '*', 106 | 'day' => '*', 107 | 'dayofweek' => '*', 108 | 'month' => '*', 109 | ], 110 | ]; 111 | -------------------------------------------------------------------------------- /export_manifest.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Download tab manifest file. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2018 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | require_once(__DIR__ . '/../../config.php'); 27 | require_once($CFG->libdir . '/filestorage/zip_archive.php'); 28 | require_once($CFG->dirroot . '/local/o365/lib.php'); 29 | 30 | require_admin(); 31 | 32 | // Mark manifest file as downloaded. 33 | $existingmanifestdownloadedsetting = get_config('local_o365', 'manifest_downloaded'); 34 | if (!$existingmanifestdownloadedsetting) { 35 | add_to_config_log('manifest_downloaded', $existingmanifestdownloadedsetting, true, 'local_o365'); 36 | } 37 | set_config('manifest_downloaded', true, 'local_o365'); 38 | purge_all_caches(); 39 | 40 | [$errorcode, $manifestfilepath] = local_o365_create_manifest_file(); 41 | 42 | if ($manifestfilepath) { 43 | // Download manifest file. 44 | header("Content-type: application/zip"); 45 | header("Content-Disposition: attachment; filename=manifest.zip"); 46 | header("Content-length: " . filesize($manifestfilepath)); 47 | header("Pragma: no-cache"); 48 | header("Expires: 0"); 49 | readfile($manifestfilepath); 50 | } else { 51 | throw new moodle_exception($errorcode, 'local_o365'); 52 | } 53 | -------------------------------------------------------------------------------- /font/segoeui.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/font/segoeui.ttf -------------------------------------------------------------------------------- /font/segoeuib.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/font/segoeuib.ttf -------------------------------------------------------------------------------- /font/segoeuil.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/font/segoeuil.ttf -------------------------------------------------------------------------------- /font/segoeuisl.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/font/segoeuisl.ttf -------------------------------------------------------------------------------- /font/seguisb.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/font/seguisb.ttf -------------------------------------------------------------------------------- /pix/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/color.png -------------------------------------------------------------------------------- /pix/moodle_app_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/moodle_app_id.png -------------------------------------------------------------------------------- /pix/o365color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/o365color.png -------------------------------------------------------------------------------- /pix/onenotecolor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/onenotecolor.png -------------------------------------------------------------------------------- /pix/outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/outline.png -------------------------------------------------------------------------------- /pix/outlookcolor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/outlookcolor.png -------------------------------------------------------------------------------- /pix/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/spinner.gif -------------------------------------------------------------------------------- /pix/teams_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/pix/teams_app.png -------------------------------------------------------------------------------- /scripts/Moodle-EntraID-PowerShell.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/moodle-local_o365/c5fb69368eebb8cf3a115ace46c0d8fb674fbdcf/scripts/Moodle-EntraID-PowerShell.zip -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Moodle Entra ID App Registration Script 2 | 3 | This PowerShell script automates the process of creating and configuring a Microsoft Entra ID application registration for Moodle integration. 4 | 5 | ## Prerequisites 6 | 7 | - PowerShell 7.5 or later on any supported platform (Windows, MacOS, Linux) 8 | - Administrator access to your Microsoft Entra ID tenant 9 | - A Moodle server with HTTPS enabled 10 | 11 | ## Installation 12 | 1. Download and extract the `Moodle-EntraID-PowerShell.zip` file. 13 | 2. Open the extracted folder, which contains the script files: 14 | - `Moodle-EntraID-Script.ps1` 15 | - `Json/permissions.json` 16 | - `Json/EntraIDOptionalClaims.json` 17 | - `Assets/moodle-logo.jpg` 18 | 19 | ## Usage 20 | 1. Open PowerShell 7 21 | 2. Navigate to the directory containing the script 22 | 3. Run the script: 23 | ```powershell 24 | ./Moodle-EntraID-Script.ps1 25 | ``` 26 | 4. Follow the prompts: 27 | - Enter a name for your Microsoft Entra ID application 28 | - Enter your Moodle server URL (must start with https://) 29 | - Choose whether to grant admin consent 30 | 31 | 5. The script will output your Application (Client) ID and Client Secret. Save these credentials securely as they will be needed for Moodle configuration. 32 | 33 | ## What the Script Does 34 | 35 | - Creates a Microsoft Entra ID application registration 36 | - Configures required API permissions 37 | - Sets up authentication URLs 38 | - Configures optional claims 39 | - Adds Teams integration support 40 | - Sets up front-channel logout URL 41 | - Grants admin consent for required permissions 42 | - Generates a client secret 43 | - Sets application logo 44 | 45 | ## Troubleshooting 46 | 47 | - If you get permission errors, make sure you have administrator rights in your Microsoft Entra ID tenant 48 | - If the script fails, you can safely run it again 49 | - Make sure all required files are present in their correct locations 50 | 51 | ## Security Notes 52 | 53 | - Store the generated client secret securely 54 | - Only run this script while connected to a trusted network 55 | - Use the generated credentials only for your Moodle configuration 56 | 57 | ## Support 58 | 59 | For issues with: 60 | - The script: Please report issues to [the repository](https://github.com/microsoft/o365-moodle/issues) 61 | - Microsoft Entra ID: Contact Microsoft Support 62 | - Moodle integration: Refer to [the Moodle documentation](https://docs.moodle.org/405/en/Microsoft_365) 63 | 64 | ## Code of Conduct 65 | 66 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 67 | 68 | ## Copyright 69 | 70 | © Microsoft, Inc. Code for this script is licensed under the GPLv3 license. 71 | 72 | Any Microsoft trademarks and logos included in these plugins are property of Microsoft and should not be reused, redistributed, modified, repurposed, or otherwise altered or used outside of this plugin. -------------------------------------------------------------------------------- /sso_end.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * This page contains the SSO login page. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2018 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | // phpcs:ignore moodle.Files.RequireLogin.Missing -- This file is called from Microsoft Teams tab. 27 | require_once(__DIR__ . '/../../config.php'); 28 | 29 | echo ""; 30 | echo ""; 31 | 32 | $js = ' 33 | microsoftTeams.initialize(); 34 | 35 | // ADAL.js configuration 36 | let config = { 37 | clientId: "' . get_config('auth_oidc', 'clientid') . '", 38 | redirectUri: "' . $CFG->wwwroot . '/local/o365/sso_end.php", 39 | cacheLocation: "localStorage", 40 | navigateToLoginRequestUrl: false, 41 | }; 42 | 43 | let authContext = new AuthenticationContext(config); 44 | 45 | if (authContext.isCallback(window.location.hash)) { 46 | authContext.handleWindowCallback(window.location.hash); 47 | if (authContext.getCachedUser()) { 48 | microsoftTeams.authentication.notifySuccess(); 49 | } else { 50 | microsoftTeams.authentication.notifyFailure(authContext.getLoginError()); 51 | } 52 | } 53 | '; 54 | 55 | echo html_writer::script($js); 56 | -------------------------------------------------------------------------------- /sso_login.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * This page logs in user using SSO. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2018 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | // phpcs:ignore moodle.Files.RequireLogin.Missing -- This file is called from Microsoft Teams tab. 27 | require_once(__DIR__ . '/../../config.php'); 28 | require_once($CFG->dirroot . '/local/o365/lib.php'); 29 | 30 | $url = new moodle_url('/local/o365/sso_login.php'); 31 | 32 | $PAGE->set_context(context_system::instance()); 33 | 34 | $authtoken = local_o365_get_auth_token(); 35 | 36 | [$headerencoded, $payloadencoded, $signatureencoded] = explode('.', $authtoken); 37 | 38 | $payload = json_decode(local_o365_base64urldecode($payloadencoded)); 39 | 40 | $loginsuccess = false; 41 | if ($authoidctoken = $DB->get_record('auth_oidc_token', ['oidcusername' => $payload->upn])) { 42 | if ($user = core_user::get_user($authoidctoken->userid)) { 43 | $_POST['code'] = $authoidctoken->authcode; 44 | $user = authenticate_user_login($user->username, $user->password, true); 45 | if ($user) { 46 | complete_user_login($user); 47 | $loginsuccess = true; 48 | } 49 | } 50 | } 51 | 52 | if ($loginsuccess) { 53 | http_response_code(200); 54 | } else { 55 | http_response_code(401); 56 | } 57 | -------------------------------------------------------------------------------- /sso_start.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * This page contains the SSO login page. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2018 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | // phpcs:ignore moodle.Files.RequireLogin.Missing -- This file is called from Microsoft Teams tab. 27 | require_once(__DIR__ . '/../../config.php'); 28 | 29 | echo ""; 30 | echo ""; 31 | 32 | $js = ' 33 | microsoftTeams.initialize(); 34 | 35 | // Get the tab context, and use the information to navigate to Microsoft login page 36 | microsoftTeams.getContext(function (context) { 37 | // ADAL.js configuration 38 | let config = { 39 | tenant: context.tid, 40 | clientId: "' . get_config('auth_oidc', 'clientid') . '", 41 | redirectUri: "' . $CFG->wwwroot . '/local/o365/sso_end.php", 42 | cacheLocation: "localStorage", 43 | navigateToLoginRequestUrl: false, 44 | 45 | // Setup extra query parameters for ADAL 46 | // - openid and profile scope adds profile information to the id_token 47 | // - login_hint provides the expected user name 48 | extraQueryParameters: "scope=openid+profile&login_hint=" + encodeURIComponent(context.loginHint), 49 | }; 50 | 51 | // Navigate to the Entra ID login page 52 | let authContext = new AuthenticationContext(config); 53 | authContext.login(); 54 | }); 55 | '; 56 | 57 | echo html_writer::script($js); 58 | -------------------------------------------------------------------------------- /teams_tab_redirect.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Redirect visitor to site page - triggered when a page that should be accessed from iframe is accessed directly. 19 | * 20 | * @package local_o365 21 | * @author Lai Wei 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2018 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | // phpcs:ignore moodle.Files.RequireLogin.Missing -- This file is called from Microsoft Teams tab. 27 | require_once(__DIR__ . '/../../config.php'); 28 | 29 | unset($SESSION->theme); 30 | throw new moodle_exception('errornodirectaccess', 'local_o365'); 31 | -------------------------------------------------------------------------------- /tests/coursesyncutils_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Test cases for course sync feature utility class. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @author Lai Wei 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 25 | */ 26 | 27 | namespace local_o365; 28 | 29 | use externallib_advanced_testcase; 30 | use local_o365\feature\coursesync\utils; 31 | 32 | defined('MOODLE_INTERNAL') || die(); 33 | 34 | global $CFG; 35 | 36 | require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 37 | 38 | /** 39 | * Tests \local_o365\feature\coursesync\utils. 40 | * 41 | * @group local_o365 42 | */ 43 | final class coursesyncutils_test extends externallib_advanced_testcase { 44 | /** 45 | * Perform setup before every test. This tells Moodle's phpunit to reset the database after every test. 46 | */ 47 | protected function setUp(): void { 48 | parent::setUp(); 49 | $this->resetAfterTest(true); 50 | } 51 | 52 | /** 53 | * Test is_enabled() method. 54 | * 55 | * @covers \local_o365\feature\coursesync\utils::is_enabled 56 | */ 57 | public function test_is_enabled(): void { 58 | global $DB; 59 | 60 | $DB->delete_records('config_plugins', ['name' => 'coursesync', 'plugin' => 'local_o365']); 61 | $this->assertFalse(utils::is_enabled()); 62 | 63 | set_config('coursesync', '', 'local_o365'); 64 | $this->assertFalse(utils::is_enabled()); 65 | 66 | set_config('coursesync', 'onall', 'local_o365'); 67 | $this->assertTrue(utils::is_enabled()); 68 | 69 | set_config('coursesync', 'off', 'local_o365'); 70 | $this->assertFalse(utils::is_enabled()); 71 | 72 | set_config('coursesync', 'oncustom', 'local_o365'); 73 | $this->assertTrue(utils::is_enabled()); 74 | 75 | set_config('coursesync', 'off', 'local_o365'); 76 | $this->assertFalse(utils::is_enabled()); 77 | } 78 | 79 | /** 80 | * Test get_enabled_courses() method. 81 | * 82 | * @covers \local_o365\feature\coursesync\utils::get_enabled_courses 83 | */ 84 | public function test_get_enabled_courses(): void { 85 | global $DB; 86 | 87 | $DB->delete_records('config_plugins', ['name' => 'coursesync', 'plugin' => 'local_o365']); 88 | $actual = utils::get_enabled_courses(); 89 | $this->assertIsArray($actual); 90 | $this->assertEmpty($actual); 91 | 92 | set_config('coursesync', 'off', 'local_o365'); 93 | set_config('coursesynccustom', json_encode([1 => 1]), 'local_o365'); 94 | $actual = utils::get_enabled_courses(); 95 | $this->assertIsArray($actual); 96 | $this->assertEmpty($actual); 97 | 98 | set_config('coursesync', 'onall', 'local_o365'); 99 | set_config('coursesynccustom', json_encode([1 => 1]), 'local_o365'); 100 | $actual = utils::get_enabled_courses(); 101 | $this->assertTrue($actual); 102 | 103 | set_config('coursesync', 'oncustom', 'local_o365'); 104 | set_config('coursesynccustom', json_encode([1 => 1]), 'local_o365'); 105 | $actual = utils::get_enabled_courses(); 106 | $this->assertIsArray($actual); 107 | $this->assertEquals([1], $actual); 108 | } 109 | 110 | /** 111 | * Test course_is_group_enabled() method. 112 | * 113 | * @covers \local_o365\feature\coursesync\utils::is_course_sync_enabled 114 | */ 115 | public function test_course_is_group_enabled(): void { 116 | global $DB; 117 | 118 | $DB->delete_records('config_plugins', ['name' => 'coursesync', 'plugin' => 'local_o365']); 119 | $DB->delete_records('config_plugins', ['name' => 'coursesynccustom', 'plugin' => 'local_o365']); 120 | $actual = utils::is_course_sync_enabled(3); 121 | $this->assertFalse($actual); 122 | 123 | set_config('coursesync', 'off', 'local_o365'); 124 | set_config('coursesynccustom', json_encode([1 => 1, 3 => 1]), 'local_o365'); 125 | $actual = utils::is_course_sync_enabled(3); 126 | $this->assertFalse($actual); 127 | 128 | set_config('coursesync', 'onall', 'local_o365'); 129 | set_config('coursesynccustom', json_encode([2 => 1]), 'local_o365'); 130 | $actual = utils::is_course_sync_enabled(3); 131 | $this->assertTrue($actual); 132 | 133 | set_config('coursesync', 'oncustom', 'local_o365'); 134 | set_config('coursesynccustom', json_encode([2 => 1]), 'local_o365'); 135 | $actual = utils::is_course_sync_enabled(3); 136 | $this->assertFalse($actual); 137 | 138 | set_config('coursesync', 'oncustom', 'local_o365'); 139 | set_config('coursesynccustom', json_encode([2 => 1, 3 => 1]), 'local_o365'); 140 | $actual = utils::is_course_sync_enabled(3); 141 | $this->assertTrue($actual); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/observers_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Test cases for observer functions. 19 | * 20 | * @package local_o365 21 | * @author Remote-Learner.net Inc 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2016 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365; 27 | 28 | use advanced_testcase; 29 | 30 | /** 31 | * Tests event observers. 32 | * 33 | * @group local_o365 34 | * @group office365 35 | */ 36 | final class observers_test extends advanced_testcase { 37 | /** 38 | * Perform setup before every test. This tells Moodle's phpunit to reset the database after every test. 39 | */ 40 | protected function setUp(): void { 41 | parent::setUp(); 42 | $this->resetAfterTest(true); 43 | } 44 | 45 | /** 46 | * Test users disconnect method. 47 | * 48 | * @covers \local_o365\observers::handle_oidc_user_disconnected 49 | */ 50 | public function test_user_disconnected(): void { 51 | $user = $this->getDataGenerator()->create_user(['auth' => 'oidc']); 52 | $this->create_member_entities($user->id); 53 | $this->assertTrue($this->has_member_entities($user->id)); 54 | $eventdata = ['objectid' => $user->id, 'userid' => $user->id]; 55 | $event = \auth_oidc\event\user_disconnected::create($eventdata); 56 | $event->trigger(); 57 | $this->assertFalse($this->has_member_entities($user->id)); 58 | } 59 | 60 | /** 61 | * Test users deleted method. 62 | * 63 | * @covers \local_o365\observers::handle_user_deleted 64 | */ 65 | public function test_user_deleted(): void { 66 | $user = $this->getDataGenerator()->create_user(['auth' => 'oidc']); 67 | $this->create_member_entities($user->id); 68 | $this->assertTrue($this->has_member_entities($user->id)); 69 | delete_user($user); 70 | $this->assertFalse($this->has_member_entities($user->id)); 71 | } 72 | 73 | /** 74 | * Create Microsoft 365 entities. 75 | * 76 | * @param int $userid 77 | */ 78 | public function create_member_entities($userid) { 79 | global $DB; 80 | $token = (object) [ 81 | 'user_id' => $userid, 82 | 'scope' => 'scope', 83 | 'tokenresource' => 'resource', 84 | 'token' => rand() * 1000, 85 | 'expiry' => time() + 1000000, 86 | 'refreshtoken' => time() + 100000, 87 | ]; 88 | $DB->insert_record('local_o365_token', $token); 89 | $entraiduserdata = (object) [ 90 | 'type' => 'user', 91 | 'subtype' => '', 92 | 'objectid' => '', 93 | 'moodleid' => $userid, 94 | 'o365name' => 'test@example.onmicrosoft.com', 95 | 'timecreated' => time(), 96 | 'timemodified' => time(), 97 | ]; 98 | $DB->insert_record('local_o365_objects', $entraiduserdata); 99 | $DB->insert_record('local_o365_connections', ['muserid' => $userid]); 100 | $object = (object) [ 101 | 'muserid' => $userid, 102 | 'assigned' => 1, 103 | 'photoid' => 'abc', 104 | 'photoupdated' => 1, 105 | ]; 106 | $DB->insert_record('local_o365_appassign', $object); 107 | } 108 | 109 | /** 110 | * Test if user has any entities. 111 | * 112 | * @param int $userid User id to check for Microsoft 365 entities. 113 | * @return boolean Returns true if any entities exist for user. 114 | */ 115 | public function has_member_entities($userid) { 116 | global $DB; 117 | $result = $DB->count_records('local_o365_token', ['user_id' => $userid]); 118 | $result = $result || $DB->count_records('local_o365_objects', ['type' => 'user', 'moodleid' => $userid]); 119 | $result = $result || $DB->count_records('local_o365_connections', ['muserid' => $userid]); 120 | 121 | return $result || $DB->count_records('local_o365_appassign', ['muserid' => $userid]); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/token_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Token test cases. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | namespace local_o365; 27 | 28 | use advanced_testcase; 29 | 30 | /** 31 | * Tests \local_o365\oauth2\token 32 | * 33 | * @group local_o365 34 | * @group office365 35 | */ 36 | final class token_test extends advanced_testcase { 37 | /** 38 | * Perform setup before every test. This tells Moodle's phpunit to reset the database after every test. 39 | */ 40 | protected function setUp(): void { 41 | parent::setUp(); 42 | $this->resetAfterTest(true); 43 | } 44 | 45 | /** 46 | * Test refresh method. 47 | * 48 | * @covers \local_o365\oauth2\token::refresh 49 | */ 50 | public function test_refresh(): void { 51 | global $USER, $DB; 52 | $this->setAdminUser(); 53 | $now = time(); 54 | 55 | $httpclient = new \local_o365\tests\mockhttpclient(); 56 | $newtokenresponse = [ 57 | 'access_token' => 'newtoken', 58 | 'expires_on' => $now + 1000, 59 | 'refresh_token' => 'newrefreshtoken', 60 | 'scope' => 'newscope', 61 | 'resource' => 'newresource', 62 | ]; 63 | $newtokenresponse = json_encode($newtokenresponse); 64 | $httpclient->set_response($newtokenresponse); 65 | 66 | $oidcconfig = (object)[ 67 | 'clientid' => 'clientid', 68 | 'clientsecret' => 'clientsecret', 69 | 'authendpoint' => 'http://example.com/auth', 70 | 'tokenendpoint' => 'http://example.com/token', 71 | ]; 72 | 73 | $tokenrec = (object)[ 74 | 'token' => 'oldtoken', 75 | 'expiry' => $now - 1000, 76 | 'refreshtoken' => 'refreshtoken', 77 | 'scope' => 'oldscope', 78 | 'tokenresource' => 'oldresource', 79 | 'user_id' => $USER->id, 80 | ]; 81 | $tokenrec->id = $DB->insert_record('local_o365_token', $tokenrec); 82 | 83 | $clientdata = new \local_o365\oauth2\clientdata($oidcconfig->clientid, $oidcconfig->clientsecret, 84 | $oidcconfig->authendpoint, $oidcconfig->tokenendpoint); 85 | $token = new \local_o365\oauth2\token($tokenrec->token, $tokenrec->expiry, $tokenrec->refreshtoken, 86 | $tokenrec->scope, $tokenrec->tokenresource, $tokenrec->user_id, $clientdata, $httpclient); 87 | $token->refresh(); 88 | 89 | $this->assertEquals(1, $DB->count_records('local_o365_token')); 90 | 91 | $tokenrec = $DB->get_record('local_o365_token', ['id' => $tokenrec->id]); 92 | $this->assertEquals('newtoken', $tokenrec->token); 93 | $this->assertEquals('newrefreshtoken', $tokenrec->refreshtoken); 94 | $this->assertEquals('newscope', $tokenrec->scope); 95 | $this->assertEquals('newresource', $tokenrec->tokenresource); 96 | $this->assertEquals($now + 1000, $tokenrec->expiry); 97 | 98 | $this->assertEquals('newtoken', $token->get_token()); 99 | $this->assertEquals('newrefreshtoken', $token->get_refreshtoken()); 100 | $this->assertEquals('newscope', $token->get_scope()); 101 | $this->assertEquals('newresource', $token->get_tokenresource()); 102 | $this->assertEquals($now + 1000, $token->get_expiry()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ucp.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * User control panel page. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 24 | */ 25 | 26 | require_once(__DIR__ . '/../../config.php'); 27 | require_login(); 28 | $action = optional_param('action', null, PARAM_TEXT); 29 | $ucptitle = get_string('ucp_title', 'local_o365'); 30 | $url = '/local/o365/ucp.php'; 31 | $page = new \local_o365\page\ucp($url, $ucptitle); 32 | $page->run($action); 33 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin version information. 19 | * 20 | * @package local_o365 21 | * @author James McQuillan 22 | * @author Lai Wei 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | * @copyright (C) 2014 onwards Microsoft, Inc. (http://microsoft.com/) 25 | */ 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | $plugin->version = 2024100710; 30 | $plugin->requires = 2024100700; 31 | $plugin->release = '4.5.2'; 32 | $plugin->component = 'local_o365'; 33 | $plugin->maturity = MATURITY_STABLE; 34 | $plugin->dependencies = [ 35 | 'auth_oidc' => 2024100710, 36 | ]; 37 | --------------------------------------------------------------------------------