├── .gitignore ├── README.mediawiki ├── classes ├── plugininfo │ └── ltiproviderextension.php └── table_syncreport.php ├── db ├── access.php ├── install.xml ├── subplugins.php └── upgrade.php ├── edit.php ├── edit_form.php ├── extension └── scormbridge │ ├── lang │ └── en │ │ └── ltiproviderextension_scormbridge.php │ ├── lib.php │ ├── logout.php │ ├── tracking.js.php │ └── version.php ├── ims-blti ├── LICENSE.txt ├── OAuth.php ├── OAuthBody.php ├── TrivialOAuthDataStore.php ├── blti.php └── blti_util.php ├── index.php ├── js └── syncreport.js ├── lang └── en │ └── local_ltiprovider.php ├── lib.php ├── locallib.php ├── modinfo ├── chat.php ├── forum.php └── quiz.php ├── services.php ├── settings.php ├── styles.js.php ├── styles.php ├── syncmembers.php ├── syncreport.php ├── test ├── cron.php ├── forcesendgrades.php ├── lms.php └── misc.php ├── tool.php └── version.php /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /stuff -------------------------------------------------------------------------------- /README.mediawiki: -------------------------------------------------------------------------------- 1 | IMS LTI PROVIDER PLUGIN FOR MOODLE 2 | 3 | == Description == 4 | 5 | === About IMS LTI === 6 | 7 | According IMS: 8 | 9 | ''IMS is developing Learning Tools Interoperability (LTI) to allow remote tools and content to be integrated into a Learning Management System (LMS).'' 10 | 11 | === About this plugin === 12 | 13 | This is a local plugin for making Moodle a LTI provider tool. 14 | 15 | It can be use to provide access to full courses or activities from remote systems (other Moodle installations, Sakai, any LMS LTI consumer compliant) 16 | 17 | Please note that since Moodle 2.2 there is a core activity plugin called "External tool" that is a LTI consumer. 18 | 19 | === Why this plugin === 20 | 21 | This plugin allow remote systems users (LTI consumers) access to Moodle courses or Moodle activities inside a course. 22 | 23 | Moodle (version 2.2 and onwards) is a LTI consumer tool also. 24 | 25 | You can use this plugin to share activities and courses between Moodle installations without configuring a Moodle network. 26 | 27 | You can also share activities and courses with other LTI consumer tools like Sakai 28 | 29 | You have a detailed view of this plugin possibilities in [http://www.somerandomthoughts.com/blog/2012/01/08/review-lti-provider-for-moodle-2-2/ this post by Gavin Henrik] 30 | 31 | == Main feautres == 32 | 33 | Provide access to full courses or single activities. 34 | 35 | Change the navigation block of a course or activity for displaying information and links only regarding to your current course. 36 | 37 | Send backs course or activity final grades to the LTI consumer tool 38 | 39 | Modify the course or activity page for hiding the header, footer and left or right blocks 40 | 41 | == Plugin version 2.4 and above features == 42 | 43 | === The plugin settings link is displayed in the settings block, instead the course one === 44 | 45 | === Several new settings for control different features of the plugin: === 46 | - How the user profile is updated 47 | - Default authentication method 48 | - Format of the course shortname, fullname and idnumber (using LTI variables) 49 | - Roles allowed to create new contexts 50 | - Roles allowed to create new resources 51 | 52 | === The remote tool can be opened using the context_id === 53 | 54 | The tool can be opened using also the context_id instead the current internal Moodle id 55 | 56 | === Support for context memberships service === 57 | 58 | See http://developers.imsglobal.org/ext_membership.html 59 | 60 | === LTI custom parameters to force settings on SSO === 61 | 62 | See https://tracker.moodle.org/browse/CONTRIB-4502 63 | 64 | === Service for context (course) creation, using other courses as template === 65 | 66 | Service URL local/ltiprovider/services.php 67 | 68 | Custom parameters: 69 | 70 | custom_service = create_context 71 | 72 | custom_context_template = Moodle idnumber for a course to be used as a template (the course will be duplicate) 73 | 74 | The course will be created populating the fullname, shortname and idnumber configured in the plugin settings 75 | 76 | === Service for resources duplication === 77 | 78 | Service URL local/ltiprovider/services.php 79 | 80 | Custom parameters: 81 | 82 | custom_service = duplicate_resource 83 | 84 | custom_resource_link_copy_id = Moodle idnumber of the activity to be duplicated in the current context 85 | 86 | === SSO to resources === 87 | 88 | If the context resource_link_id matches to an activity idnumber, the user will be redirect to that activity in Moodle 89 | 90 | === Automatic creation of resources (moodle activities) === 91 | 92 | If this additional parameter is present in the request custom_resource_link_type (mod_forum, etc...) and also resource_link_title and resource_link_description a new moodle activity will be created 93 | 94 | See: https://tracker.moodle.org/browse/CONTRIB-4409 95 | 96 | === Automatic creation of contexts on SSO === 97 | 98 | Two additional request parameters are required: 99 | 100 | custom_create_context (0 or 1) 101 | 102 | custom_context_template (Moodle course idnumber) 103 | 104 | 105 | === Resources duplication on SSO == 106 | 107 | Additional parameter required: 108 | 109 | custom_resource_link_copy_id = Moodle idnumber of the activity to be duplicated in the current context 110 | 111 | 112 | == Installing and configuring == 113 | 114 | Follow instructions here: http://moodle.org/plugins/pluginversions.php?plugin=local_ltiprovider 115 | 116 | '''Important''' If you are using Moodle 2.2 or above, please, be sure that this option: 117 | 118 | Home / > Site administration / > Security / > HTTP security Allow frame embedding 119 | 120 | Is checked, if you leave this option unchecked your provider site will not be "embedable" via an iframe in other sites. 121 | 122 | Once installed, a new link called "LTI Provider" will be displayed in the course navigation block . 123 | 124 | In this page, you can add, modify and disable the tools provided in your course. 125 | 126 | Please note that you can provide a tool n times with different configurations 127 | 128 | There are options for hiding the page header, footer, and left and right blocks and also options for force the Moodle navigation inside a course or activity. 129 | 130 | There are also options for assign different roles in the course or activity to the remote users. 131 | 132 | Once added a tool, you will need to use two settings in your consumer tool: 133 | 134 | * Shared secret 135 | 136 | * Launch URL 137 | 138 | Your consumer tool will ask you for a consumer private key, you can use a random string (please, do not use the shared secret as the private key) 139 | 140 | Configure your consumer tool with these two settings. That's all 141 | 142 | For a more detailed view of the plugin options see [http://www.somerandomthoughts.com/blog/2012/01/08/review-lti-provider-for-moodle-2-2/ this detailed review of the plugin by Gavin Henrik] 143 | 144 | == How it works == 145 | 146 | === User authentication === 147 | 148 | * Users are created automatically in their first access to the system. 149 | * Users are created with a hashed username and also with an auth method that disable direct login to Moodle. 150 | * Users are allways enrolled in the course where the activities are. 151 | 152 | You can choose which role has the Learner and the Teacher from the remote system. 153 | 154 | There is also settings for setting Users profile default values (email visible, etc...) 155 | 156 | If you are going to have courses with local and remote users enrolled, I recommend you to create these new roles: 157 | 158 | * External teacher 159 | * External student 160 | 161 | === Grading === 162 | 163 | A cron job checks periodically activities for sending back grades (overall course grade or activity grade). 164 | 165 | In order to work correctly, your php.ini settings file needs to have the following setting enabled: 166 | 167 | allow_url_fopen = On 168 | 169 | 170 | == Credits == 171 | 172 | Juan Leyva 173 | 174 | http://moodle.org/user/profile.php?id=49568 175 | 176 | The Universitat Oberta de Catalunya (UOC) has sponsored the version 2.3 of this plugin 177 | 178 | == See also == 179 | 180 | [http://www.somerandomthoughts.com/blog/2012/01/08/review-lti-provider-for-moodle-2-2/ Review: LTI Provider by Gavin Henrik] 181 | 182 | [http://moodle.org/plugins/pluginversions.php?plugin=local_ltiprovider Plugin entry] 183 | 184 | [https://github.com/jleyva/moodle-local_ltiprovider Github page] 185 | 186 | [[Category: Contributed code]] 187 | -------------------------------------------------------------------------------- /classes/plugininfo/ltiproviderextension.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * LTI Provider source plugin info. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2014 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace local_ltiprovider\plugininfo; 27 | 28 | use core\plugininfo\base; 29 | 30 | defined('MOODLE_INTERNAL') || die(); 31 | 32 | 33 | class ltiproviderextension extends base { 34 | // Accepts 100% base implementation. 35 | 36 | } -------------------------------------------------------------------------------- /classes/table_syncreport.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Table for displaying syncreports. 19 | * 20 | * 21 | * @package local 22 | * @subpackage ltiprovider 23 | * @copyright 2017 Juan Leyva , Antoni Bertran 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | defined('MOODLE_INTERNAL') || die; 28 | require_once($CFG->libdir . '/tablelib.php'); 29 | 30 | 31 | class local_ltiprovider_table_syncreport extends table_sql { 32 | 33 | /** @var stdClass lti_toolprovider parameters */ 34 | protected $lti_toolprovider; 35 | 36 | /** @var stdClass filters parameters */ 37 | protected $filterparams; 38 | 39 | /** 40 | * Sets up the table_log parameters. 41 | * 42 | * @param string $uniqueid unique id of form. 43 | * @param stdClass $filterparams (optional) filter params. 44 | * - int courseid: id of course 45 | * - int userid: user id 46 | * - int|string modid: Module id or "site_errors" to view site errors 47 | * - int groupid: Group id 48 | * - \core\log\sql_reader logreader: reader from which data will be fetched. 49 | * - int edulevel: educational level. 50 | * - string action: view action 51 | * - int date: Date from which logs to be viewed. 52 | */ 53 | public function __construct($uniqueid, $lti_toolprovider, $filterparams=null) { 54 | parent::__construct($uniqueid); 55 | 56 | $this->set_attribute('class', 'generaltable generalbox'); 57 | $this->set_attribute('aria-live', 'polite'); 58 | $this->lti_toolprovider = $lti_toolprovider; 59 | $this->filterparams = $filterparams; 60 | $params = array('id' => $lti_toolprovider->id); 61 | if ($this->filterparams) { 62 | if (!empty($this->filterparams->firstname)) { 63 | $params['firstname'] = $this->filterparams->firstname; 64 | } 65 | if (!empty($this->filterparams->lastname)) { 66 | $params['lastname'] = $this->filterparams->lastname; 67 | } 68 | if (!empty($this->filterparams->sifirst)) { 69 | $params['sifirst'] = $this->filterparams->sifirst; 70 | } 71 | if (!empty($this->filterparams->silast)) { 72 | $params['silast'] = $this->filterparams->silast; 73 | } 74 | } 75 | $this->define_baseurl(new moodle_url('/local/ltiprovider/syncreport.php', $params)); 76 | 77 | $this->define_columns(array('checkbox', 'fullnameuser', 'lastsync', 'lastgrade', 'forcesendbutton', 'serviceurl', 'sourceid')); 78 | $this->define_headers(array( 79 | get_string('select'), 80 | get_string('fullnameuser'), 81 | get_string('time'), 82 | get_string('grade'), 83 | '', 84 | get_string('gradessourceid', 'local_ltiprovider'), 85 | get_string('gradesserviceurl', 'local_ltiprovider') 86 | ) 87 | ); 88 | $this->no_sorting('checkbox'); 89 | $this->no_sorting('forcesendbutton'); 90 | $this->no_sorting('serviceurl'); 91 | $this->no_sorting('sourceid'); 92 | $this->collapsible(true); 93 | $this->sortable(true); 94 | $this->pageable(true); 95 | $this->is_downloadable(false); 96 | } 97 | 98 | /** 99 | * Generate select checkbox column. 100 | * 101 | * @param stdClass $row_report event data. 102 | * @return string HTML for the username column 103 | */ 104 | public function col_checkbox($row_report) { 105 | 106 | $checked = ''; 107 | return ''; 108 | 109 | } 110 | 111 | /** 112 | * Generate the username column. 113 | * 114 | * @param stdClass $row_report event data. 115 | * @return string HTML for the username column 116 | */ 117 | public function col_fullnameuser($row_report) { 118 | 119 | return fullname($row_report); 120 | 121 | } 122 | 123 | /** 124 | * Generate the lastsync column. 125 | * 126 | * @param stdClass $row_report event data. 127 | * @return string HTML for the time column 128 | */ 129 | public function col_time($row_report) { 130 | $recenttimestr = get_string('strftimerecent', 'core_langconfig'); 131 | return userdate($row_report->lastsync, $recenttimestr); 132 | } 133 | 134 | /** 135 | * Generate the forcesendbutton column. 136 | * 137 | * @param stdClass $row_report event data. 138 | * @return string HTML for the course column. 139 | */ 140 | public function col_forcesendbutton($row_report) { 141 | global $OUTPUT; 142 | $forcesendurl = new \moodle_url('test/forcesendgrades.php', array('toolid' => $this->lti_toolprovider->id, 'userid' => $row_report->id, 'printresponse' => 1)); 143 | $forcesendbutton = $OUTPUT->single_button($forcesendurl, get_string('forcesendgrades', 'local_ltiprovider')); 144 | 145 | return $forcesendbutton; 146 | } 147 | 148 | 149 | /** 150 | * Generate the sourceid column. 151 | * 152 | * @param stdClass $row_report event data. 153 | * @return string HTML for the related username column 154 | */ 155 | public function col_sourceid($row_report) { 156 | 157 | return s($row_report->sourceid); 158 | } 159 | 160 | /** 161 | * Builds the SQL query. 162 | * 163 | * @param bool $count When true, return the count SQL. 164 | * @return array containing sql to use and an array of params. 165 | */ 166 | protected function get_sql_and_params($count = false) { 167 | global $DB; 168 | $fields = 'u.*, g.serviceurl, g.sourceid, g.lastgrade, g.lastsync'; 169 | list($extra_sql, $params) = $this->get_sql_filters(); 170 | 171 | if ($count) { 172 | $select = "COUNT(1)"; 173 | } else { 174 | $select = "$fields"; 175 | } 176 | 177 | $sql = "SELECT $select 178 | FROM {local_ltiprovider_user} g JOIN {user} u 179 | ON u.id = g.userid 180 | WHERE g.lastsync > 0 AND g.toolid = :toolid ". 181 | $extra_sql; 182 | $params = array_merge($params, array('toolid' => $this->lti_toolprovider->id)); 183 | 184 | // Add order by if needed. 185 | if (!$count && $sqlsort = $this->get_sql_sort()) { 186 | if (strpos($sqlsort, 'fullnameuser')!==false) { 187 | $sqlsort = str_replace('fullnameuser', $DB->sql_fullname(), $sqlsort); 188 | } 189 | $sql .= " ORDER BY " . $sqlsort; 190 | } 191 | 192 | return array($sql, $params); 193 | } 194 | 195 | /** 196 | * Get the SQL filters 197 | * @return array 198 | */ 199 | private function get_sql_filters() { 200 | global $DB; 201 | $params = array(); 202 | $extra_sql = ''; 203 | if ($this->filterparams) { 204 | 205 | if (!empty($this->filterparams->firstname)) { 206 | $field = 'firstname'; 207 | $name = 'firstname'; 208 | $value = $this->filterparams->firstname; 209 | $extra_sql .= ' AND ' . $DB->sql_like($field, ":$name", false, false); 210 | $params[$name] = "%$value%"; 211 | } 212 | if (!empty($this->filterparams->lastname)) { 213 | $field = 'lastname'; 214 | $name = 'lastname'; 215 | $value = $this->filterparams->lastname; 216 | $extra_sql .= ' AND ' . $DB->sql_like($field, ":$name", false, false); 217 | $params[$name] = "%$value%"; 218 | } 219 | if (!empty($this->filterparams->sifirst)) { 220 | $field = 'firstname'; 221 | $name = 'sifirst'; 222 | $value = $this->filterparams->sifirst; 223 | $extra_sql .= ' AND ' . $DB->sql_like($field, ":$name", false, false); 224 | $params[$name] = "$value%"; 225 | } 226 | if (!empty($this->filterparams->silast)) { 227 | $field = 'lastname'; 228 | $name = 'silast'; 229 | $value = $this->filterparams->silast; 230 | $extra_sql .= ' AND ' . $DB->sql_like($field, ":$name", false, false); 231 | $params[$name] = "$value%"; 232 | } 233 | } 234 | 235 | return array($extra_sql, $params); 236 | } 237 | 238 | /** 239 | * Query the reader. Store results in the object for use by build_table. 240 | * 241 | * @param int $pagesize size of page for paginated displayed table. 242 | * @param bool $useinitialsbar do you want to use the initials bar. 243 | */ 244 | public function query_db($pagesize, $useinitialsbar = true) { 245 | 246 | global $DB; 247 | 248 | list($countsql, $countparams) = $this->get_sql_and_params(true); 249 | list($sql, $params) = $this->get_sql_and_params(); 250 | $total = $DB->count_records_sql($countsql, $countparams); 251 | $this->pagesize($pagesize, $total); 252 | $this->rawdata = $DB->get_records_sql($sql, $params, $this->get_page_start(), $this->get_page_size()); 253 | 254 | // Set initial bars. 255 | if ($useinitialsbar) { 256 | $this->initialbars($total > $pagesize); 257 | } 258 | 259 | } 260 | 261 | /** 262 | * Renders html to display a syncreport search form 263 | * 264 | * @param int $tool_id the lti tool id 265 | * @param string $value default value to populate the search field 266 | * @return string 267 | */ 268 | function syncreport_search_form($tool_id, $value_firstname = '', $value_lastname = '', $firstinitial='', $lastinitial = '') { 269 | $formid = 'ltiprovidersyncreport'; 270 | $inputid = 'coursesearchbox'; 271 | $inputidlastname= 'coursesearchboxlastname'; 272 | $inputsize = 30; 273 | 274 | $strsearchfirstname = get_string('firstname'); 275 | $strsearchlastname = get_string('lastname'); 276 | $searchurl = new moodle_url('/local/ltiprovider/syncreport.php'); 277 | $url = new moodle_url('/local/ltiprovider/syncreport.php', array('id' => $tool_id)); 278 | $strall = get_string('all'); 279 | $alpha = explode(',', get_string('alphabet', 'langconfig')); 280 | 281 | $output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get')); 282 | $output .= html_writer::start_tag('fieldset', array('class' => 'ltiprovidersearchbox invisiblefieldset')); 283 | $output .= html_writer::tag('label', $strsearchfirstname.': ', array('for' => $inputid)); 284 | $output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid, 285 | 'size' => $inputsize, 'name' => 'search_firstname', 'value' => s($value_firstname))); 286 | $output .= html_writer::start_tag('span', array('class' => 'initialbar firstinitial')); 287 | if (!empty($firstinitial)) { 288 | $output .= html_writer::link($url.'&sifirst='.'&silast='.$lastinitial, $strall); 289 | } else { 290 | $output .= html_writer::tag('strong', $strall); 291 | } 292 | foreach ($alpha as $letter) { 293 | if ($letter == $firstinitial) { 294 | $output .= html_writer::tag('strong', $letter); 295 | } else { 296 | $output .= html_writer::link($url.'&sifirst='.$letter.'&silast='.$lastinitial, $letter); 297 | } 298 | } 299 | $output .= html_writer::end_tag('span'); 300 | 301 | 302 | $output .= html_writer::tag('label', $strsearchlastname.': ', array('for' => $inputidlastname)); 303 | $output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputidlastname, 304 | 'size' => $inputsize, 'name' => 'search_lastname', 'value' => s($value_lastname))); 305 | $output .= html_writer::start_tag('span', array('class' => 'initialbar lastinitial')); 306 | 307 | if (!empty($lastinitial)) { 308 | $output .= html_writer::link($url.'&silast='.'&sifirst='.$firstinitial, $strall); 309 | } else { 310 | $output .= html_writer::tag('strong', $strall); 311 | } 312 | foreach ($alpha as $letter) { 313 | if ($letter == $lastinitial) { 314 | $output .= html_writer::tag('strong', $letter); 315 | } else { 316 | $output .= html_writer::link($url.'&silast='.$letter.'&sifirst='.$firstinitial, $letter); 317 | } 318 | } 319 | $output .= html_writer::end_tag('span'); 320 | 321 | 322 | 323 | $output .= html_writer::start_tag('div'); 324 | $output .= html_writer::empty_tag('input', array('type' => 'submit', 325 | 'value' => get_string('go'))); 326 | $output .= html_writer::end_tag('div'); 327 | $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'id', 328 | 'value' => $tool_id)); 329 | $output .= html_writer::end_tag('fieldset'); 330 | $output .= html_writer::end_tag('form'); 331 | 332 | 333 | 334 | return $output; 335 | } 336 | 337 | 338 | 339 | 340 | } 341 | -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Capability definitions. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | $capabilities = array( 29 | 30 | 'local/ltiprovider:manage' => array( 31 | 32 | 'riskbitmask' => RISK_SPAM, 33 | 34 | 'captype' => 'write', 35 | 'contextlevel' => CONTEXT_COURSE, 36 | 'archetypes' => array( 37 | 'editingteacher' => CAP_ALLOW, 38 | 'manager' => CAP_ALLOW 39 | ) 40 | ), 41 | 'local/ltiprovider:view' => array( 42 | 43 | 'riskbitmask' => RISK_SPAM, 44 | 45 | 'captype' => 'write', 46 | 'contextlevel' => CONTEXT_COURSE, 47 | 'archetypes' => array( 48 | 'teacher' => CAP_ALLOW, 49 | 'editingteacher' => CAP_ALLOW, 50 | 'manager' => CAP_ALLOW 51 | ) 52 | ) 53 | ); 54 | -------------------------------------------------------------------------------- /db/install.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 |
-------------------------------------------------------------------------------- /db/subplugins.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * This file keeps track of upgrades to the ltiprovider plugin 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2014 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $subplugins = array( 29 | 'ltiproviderextension' => 'local/ltiprovider/extension', 30 | ); -------------------------------------------------------------------------------- /db/upgrade.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * This file keeps track of upgrades to the ltiprovider plugin 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | function xmldb_local_ltiprovider_upgrade($oldversion) { 29 | global $CFG, $DB, $OUTPUT; 30 | 31 | $dbman = $DB->get_manager(); 32 | 33 | if ($oldversion < 2011121703) { 34 | 35 | $table = new xmldb_table('local_ltiprovider'); 36 | 37 | $field = new xmldb_field('enrolperiod', XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'sendgrades'); 38 | if (!$dbman->field_exists($table, $field)) { 39 | $dbman->add_field($table, $field); 40 | } 41 | 42 | $field = new xmldb_field('enrolstartdate', XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'enrolperiod'); 43 | if (!$dbman->field_exists($table, $field)) { 44 | $dbman->add_field($table, $field); 45 | } 46 | 47 | $field = new xmldb_field('enrolenddate', XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'enrolstartdate'); 48 | if (!$dbman->field_exists($table, $field)) { 49 | $dbman->add_field($table, $field); 50 | } 51 | 52 | $field = new xmldb_field('maxenrolled', XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'enrolenddate'); 53 | if (!$dbman->field_exists($table, $field)) { 54 | $dbman->add_field($table, $field); 55 | } 56 | 57 | upgrade_plugin_savepoint(true, 2011121703, 'local', 'ltiprovider'); 58 | } 59 | 60 | if ($oldversion < 2011121707) { 61 | 62 | $table = new xmldb_table('local_ltiprovider'); 63 | 64 | $field = new xmldb_field('userprofileupdate', XMLDB_TYPE_INTEGER, 1, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 1, 'maxenrolled'); 65 | if (!$dbman->field_exists($table, $field)) { 66 | $dbman->add_field($table, $field); 67 | } 68 | $field = new xmldb_field('syncmembers', XMLDB_TYPE_INTEGER, 1, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'userprofileupdate'); 69 | if (!$dbman->field_exists($table, $field)) { 70 | $dbman->add_field($table, $field); 71 | } 72 | $field = new xmldb_field('syncmode', XMLDB_TYPE_INTEGER, 2, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'syncmembers'); 73 | if (!$dbman->field_exists($table, $field)) { 74 | $dbman->add_field($table, $field); 75 | } 76 | $field = new xmldb_field('syncperiod', XMLDB_TYPE_INTEGER, 10, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'syncmode'); 77 | if (!$dbman->field_exists($table, $field)) { 78 | $dbman->add_field($table, $field); 79 | } 80 | 81 | $table = new xmldb_table('local_ltiprovider_user'); 82 | 83 | $field = new xmldb_field('membershipsurl', XMLDB_TYPE_TEXT, "small", null, null, null, null, 'lastaccess'); 84 | if (!$dbman->field_exists($table, $field)) { 85 | $dbman->add_field($table, $field); 86 | } 87 | $field = new xmldb_field('membershipsid', XMLDB_TYPE_TEXT, "small", null, null, null, null, 'membershipsurl'); 88 | if (!$dbman->field_exists($table, $field)) { 89 | $dbman->add_field($table, $field); 90 | } 91 | 92 | upgrade_plugin_savepoint(true, 2011121707, 'local', 'ltiprovider'); 93 | } 94 | 95 | if ($oldversion < 2014080102) { 96 | 97 | $table = new xmldb_table('local_ltiprovider'); 98 | 99 | $field = new xmldb_field('requirecompletion', XMLDB_TYPE_INTEGER, 2, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'lastsync'); 100 | if (!$dbman->field_exists($table, $field)) { 101 | $dbman->add_field($table, $field); 102 | } 103 | 104 | upgrade_plugin_savepoint(true, 2014080102, 'local', 'ltiprovider'); 105 | } 106 | 107 | if ($oldversion < 2014080103) { 108 | 109 | $table = new xmldb_table('local_ltiprovider'); 110 | 111 | $field = new xmldb_field('enrolinst', XMLDB_TYPE_INTEGER, 2, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 1, 'requirecompletion'); 112 | if (!$dbman->field_exists($table, $field)) { 113 | $dbman->add_field($table, $field); 114 | } 115 | $field = new xmldb_field('enrollearn', XMLDB_TYPE_INTEGER, 2, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 1, 'enrolinst'); 116 | if (!$dbman->field_exists($table, $field)) { 117 | $dbman->add_field($table, $field); 118 | } 119 | 120 | upgrade_plugin_savepoint(true, 2014080103, 'local', 'ltiprovider'); 121 | } 122 | 123 | if ($oldversion < 2016020101) { 124 | 125 | $table = new xmldb_table('local_ltiprovider'); 126 | 127 | $field = new xmldb_field('addtogroup', XMLDB_TYPE_CHAR, '64', null, XMLDB_NOTNULL, null, null, 'enrollearn'); 128 | if (!$dbman->field_exists($table, $field)) { 129 | $dbman->add_field($table, $field); 130 | } 131 | upgrade_plugin_savepoint(true, 2016020101, 'local', 'ltiprovider'); 132 | } 133 | 134 | if ($oldversion < 2016020103) { 135 | 136 | $table = new xmldb_table('local_ltiprovider'); 137 | 138 | $field = new xmldb_field('sendcompletion', XMLDB_TYPE_INTEGER, 2, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0, 'addtogroup'); 139 | if (!$dbman->field_exists($table, $field)) { 140 | $dbman->add_field($table, $field); 141 | } 142 | 143 | upgrade_plugin_savepoint(true, 2016020103, 'local', 'ltiprovider'); 144 | } 145 | 146 | return true; 147 | } 148 | -------------------------------------------------------------------------------- /edit.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Edit a tool provided in a course 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | require_once($CFG->dirroot.'/local/ltiprovider/lib.php'); 28 | require_once($CFG->dirroot.'/local/ltiprovider/edit_form.php'); 29 | 30 | $id = optional_param('id', -1, PARAM_INT); // user id; -1 if creating new tool 31 | $courseid = optional_param('courseid', 0, PARAM_INT); // course id (defaults to Site) 32 | $delete = optional_param('delete', 0, PARAM_BOOL); 33 | $confirm = optional_param('confirm', 0, PARAM_BOOL); 34 | $hide = optional_param('hide', 0, PARAM_INT); 35 | $show = optional_param('show', 0, PARAM_INT); 36 | 37 | if ($id > 0) { 38 | if (! ($tool = $DB->get_record('local_ltiprovider', array('id'=>$id)))) { 39 | print_error('invalidtoolid', 'local_ltiprovider'); 40 | } 41 | $courseid = $tool->courseid; 42 | } else { 43 | $tool = new stdClass(); 44 | $tool->id = -1; 45 | $tool->courseid = $courseid; 46 | } 47 | 48 | if (! ($course = $DB->get_record('course', array('id'=>$courseid)))) { 49 | print_error('invalidcourseid', 'error'); 50 | } 51 | 52 | $PAGE->set_url('/local/ltiprovider/edit.php', array('id' => $id, 'courseid' => $courseid)); 53 | 54 | context_helper::preload_course($course->id); 55 | if (!$context = context_course::instance($course->id)) { 56 | print_error('nocontext'); 57 | } 58 | 59 | require_login($course); 60 | require_capability('local/ltiprovider:manage', $context); 61 | 62 | $returnurl = new moodle_url('/local/ltiprovider/index.php', array('courseid' => $courseid)); 63 | 64 | $strheading = get_string('providetool', 'local_ltiprovider'); 65 | $PAGE->set_context($context); 66 | 67 | if ($delete and $tool->id) { 68 | $PAGE->url->param('delete', 1); 69 | if ($confirm and confirm_sesskey()) { 70 | local_ltiprovider_delete_tool($tool); 71 | redirect($returnurl); 72 | } 73 | $strheading = get_string('deletetool', 'local_ltiprovider'); 74 | $PAGE->navbar->add($strheading); 75 | $PAGE->set_title($strheading); 76 | $PAGE->set_heading($COURSE->fullname); 77 | 78 | echo $OUTPUT->header(); 79 | echo $OUTPUT->heading($strheading); 80 | $yesurl = new moodle_url('/local/ltiprovider/edit.php', array('id'=>$tool->id, 'delete'=>1, 'confirm'=>1, 'sesskey'=>sesskey())); 81 | $message = get_string('delconfirm', 'local_ltiprovider'); 82 | echo $OUTPUT->confirm($message, $yesurl, $returnurl); 83 | echo $OUTPUT->footer(); 84 | die; 85 | } 86 | 87 | if ((!empty($hide) or !empty($show)) and $tool->id and confirm_sesskey()) { 88 | if (!empty($hide)) { 89 | $disabled = 1; 90 | } else { 91 | $disabled = 0; 92 | } 93 | $DB->set_field('local_ltiprovider', 'disabled', $disabled, array('id' => $tool->id)); 94 | redirect($returnurl); 95 | } 96 | 97 | $PAGE->navbar->add(get_string('pluginname', 'local_ltiprovider'), new moodle_url('/local/ltiprovider/index.php', array('courseid'=>$course->id))); 98 | $PAGE->navbar->add($strheading); 99 | $PAGE->set_title($strheading); 100 | $PAGE->set_heading($course->fullname . ': '.$strheading); 101 | 102 | $editform = new edit_form(null, compact('context', 'courseid', 'tool')); 103 | 104 | $userprofileupdate = get_config('local_ltiprovider', 'userprofileupdate'); 105 | if ($userprofileupdate != -1) { 106 | $tool->userprofileupdate = $userprofileupdate; 107 | } 108 | 109 | $editform->set_data($tool); 110 | 111 | if ($editform->is_cancelled()) { 112 | redirect($returnurl); 113 | 114 | } else if ($data = $editform->get_data()) { 115 | 116 | if ($data->id > 0) { 117 | // Update 118 | local_ltiprovider_update_tool($data); 119 | } else { 120 | // Create new 121 | local_ltiprovider_add_tool($data); 122 | } 123 | redirect($returnurl); 124 | } 125 | 126 | echo $OUTPUT->header(); 127 | $editform->display(); 128 | echo $OUTPUT->footer(); -------------------------------------------------------------------------------- /edit_form.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Edit a tool provided in a course 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | require_once($CFG->dirroot.'/lib/formslib.php'); 29 | require_once($CFG->dirroot.'/course/lib.php'); 30 | 31 | /// get url variables 32 | class edit_form extends moodleform { 33 | 34 | // Define the form 35 | public function definition () { 36 | global $USER, $CFG, $COURSE; 37 | 38 | $mform =& $this->_form; 39 | $templateuser = $USER; 40 | $context = $this->_customdata['context']; 41 | $tool = $this->_customdata['tool']; 42 | 43 | $mform->addElement('header', 'settingsheader', get_string('toolsettings', 'local_ltiprovider')); 44 | 45 | $tools = array(); 46 | $tools[$context->id] = get_string('course'); 47 | 48 | $modinfo = get_fast_modinfo($this->_customdata['courseid']); 49 | $mods = $modinfo->get_cms(); 50 | 51 | foreach ($mods as $mod) { 52 | $tools[$mod->context->id] = format_string($mod->name); 53 | } 54 | 55 | $mform->addElement('select', 'contextid', get_string('tooltobeprovide', 'local_ltiprovider'), $tools); 56 | $mform->setDefault('contextid', $context->id); 57 | 58 | $mform->addElement('checkbox', 'forcenavigation', null, get_string('forcenavigation', 'local_ltiprovider')); 59 | $mform->setDefault('forcenavigation', 1); 60 | 61 | $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'local_ltiprovider'), array('optional' => true, 'defaultunit' => 86400)); 62 | $mform->setDefault('enrolperiod', 0); 63 | $mform->addHelpButton('enrolperiod', 'enrolperiod', 'local_ltiprovider'); 64 | 65 | $mform->addElement('date_selector', 'enrolstartdate', get_string('enrolstartdate', 'local_ltiprovider'), array('optional' => true)); 66 | $mform->setDefault('enrolstartdate', 0); 67 | $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'local_ltiprovider'); 68 | 69 | $mform->addElement('date_selector', 'enrolenddate', get_string('enrolenddate', 'local_ltiprovider'), array('optional' => true)); 70 | $mform->setDefault('enrolenddate', 0); 71 | $mform->addHelpButton('enrolenddate', 'enrolenddate', 'local_ltiprovider'); 72 | 73 | $mform->addElement('text', 'maxenrolled', get_string('maxenrolled', 'local_ltiprovider')); 74 | $mform->setDefault('maxenrolled', 0); 75 | $mform->addHelpButton('maxenrolled', 'maxenrolled', 'local_ltiprovider'); 76 | $mform->setType('maxenrolled', PARAM_INT); 77 | 78 | $mform->addElement('text', 'addtogroup', get_string('addtogroup', 'local_ltiprovider')); 79 | $mform->setDefault('addtogroup', ''); 80 | $mform->addHelpButton('addtogroup', 'addtogroup', 'local_ltiprovider'); 81 | $mform->setType('addtogroup', PARAM_NOTAGS); 82 | 83 | $assignableroles = get_assignable_roles($context); 84 | 85 | $mform->addElement('checkbox', 'enrolinst', null, get_string('enrolinst', 'local_ltiprovider')); 86 | $mform->setDefault('enrolinst', 1); 87 | $mform->addHelpButton('enrolinst', 'enrolinst', 'local_ltiprovider'); 88 | $mform->setAdvanced('enrolinst'); 89 | $mform->addElement('checkbox', 'enrollearn', null, get_string('enrollearn', 'local_ltiprovider')); 90 | $mform->setDefault('enrollearn', 1); 91 | $mform->addHelpButton('enrollearn', 'enrollearn', 'local_ltiprovider'); 92 | $mform->setAdvanced('enrollearn'); 93 | 94 | $mform->addElement('select', 'croleinst', get_string('courseroleinstructor', 'local_ltiprovider'), $assignableroles); 95 | $mform->setDefault('croleinst', '3'); 96 | $mform->setAdvanced('croleinst'); 97 | $mform->addElement('select', 'crolelearn', get_string('courserolelearner', 'local_ltiprovider'), $assignableroles); 98 | $mform->setDefault('crolelearn', '5'); 99 | $mform->setAdvanced('crolelearn'); 100 | 101 | $mform->addElement('select', 'aroleinst', get_string('activityroleinstructor', 'local_ltiprovider'), $assignableroles); 102 | $mform->disabledIf('aroleinst', 'contextid', 'eq', $context->id); 103 | $mform->setDefault('aroleinst', '3'); 104 | $mform->setAdvanced('aroleinst'); 105 | $mform->addElement('select', 'arolelearn', get_string('activityrolelearner', 'local_ltiprovider'), $assignableroles); 106 | $mform->disabledIf('arolelearn', 'contextid', 'eq', $context->id); 107 | $mform->setDefault('arolelearn', '5'); 108 | $mform->setAdvanced('arolelearn'); 109 | 110 | $mform->addElement('header', 'remotesystem', get_string('remotesystem', 'local_ltiprovider')); 111 | 112 | $mform->addElement('text', 'secret', get_string('secret', 'local_ltiprovider'), 'maxlength="64" size="25"'); 113 | $mform->setType('secret', PARAM_MULTILANG); 114 | $mform->setDefault('secret', md5(uniqid(rand(), 1))); 115 | $mform->addRule('secret', get_string('required'), 'required'); 116 | 117 | 118 | $choices = core_text::get_encodings(); 119 | $mform->addElement('select', 'encoding', get_string('remoteencoding', 'local_ltiprovider'), $choices); 120 | $mform->setDefault('encoding', 'UTF-8'); 121 | 122 | $mform->addElement('header', 'defaultheader', get_string('userdefaultvalues', 'local_ltiprovider')); 123 | 124 | $choices = array(0 => get_string('never'), 1 => get_string('always')); 125 | $mform->addElement('select', 'userprofileupdate', get_string('userprofileupdate', 'local_ltiprovider'), $choices); 126 | 127 | $userprofileupdate = get_config('local_ltiprovider', 'userprofileupdate'); 128 | if ($userprofileupdate != -1) { 129 | $mform->setDefault('userprofileupdate', $userprofileupdate); 130 | $mform->freeze('userprofileupdate'); 131 | } else { 132 | $mform->setDefault('userprofileupdate', 1); 133 | } 134 | 135 | $choices = array(0 => get_string('emaildisplayno'), 1 => get_string('emaildisplayyes'), 2 => get_string('emaildisplaycourse')); 136 | $mform->addElement('select', 'maildisplay', get_string('emaildisplay'), $choices); 137 | $mform->setDefault('maildisplay', 2); 138 | 139 | $mform->addElement('text', 'city', get_string('city'), 'maxlength="100" size="25"'); 140 | $mform->setType('city', PARAM_MULTILANG); 141 | if (empty($CFG->defaultcity)) { 142 | $mform->setDefault('city', $templateuser->city); 143 | } else { 144 | $mform->setDefault('city', $CFG->defaultcity); 145 | } 146 | 147 | $mform->addElement('select', 'country', get_string('selectacountry'), get_string_manager()->get_list_of_countries()); 148 | if (empty($CFG->country)) { 149 | $mform->setDefault('country', $templateuser->country); 150 | } else { 151 | $mform->setDefault('country', $CFG->country); 152 | } 153 | $mform->setAdvanced('country'); 154 | 155 | $choices = core_date::get_list_of_timezones(); 156 | $choices['99'] = get_string('serverlocaltime'); 157 | $mform->addElement('select', 'timezone', get_string('timezone'), $choices); 158 | $mform->setDefault('timezone', $templateuser->timezone); 159 | $mform->setAdvanced('timezone'); 160 | 161 | $mform->addElement('select', 'lang', get_string('preferredlanguage'), get_string_manager()->get_list_of_translations()); 162 | $mform->setDefault('lang', $templateuser->lang); 163 | $mform->setAdvanced('lang'); 164 | 165 | $mform->addElement('text', 'institution', get_string('institution'), 'maxlength="40" size="25"'); 166 | $mform->setType('institution', PARAM_MULTILANG); 167 | $mform->setDefault('institution', $templateuser->institution); 168 | $mform->setAdvanced('institution'); 169 | 170 | $mform->addElement('header', 'outcomes', get_string('outcomessettings', 'local_ltiprovider')); 171 | $mform->addElement('checkbox', 'sendgrades', null, get_string('sendgrades', 'local_ltiprovider')); 172 | $mform->setDefault('sendgrades', 1); 173 | 174 | $mform->addElement('advcheckbox', 'requirecompletion', null, get_string('requirecompletion', 'local_ltiprovider')); 175 | $mform->setDefault('requirecompletion', 0); 176 | $mform->disabledIf('requirecompletion', 'sendgrades'); 177 | 178 | $mform->addElement('advcheckbox', 'sendcompletion', null, get_string('sendcompletion', 'local_ltiprovider')); 179 | $mform->setDefault('sendcompletion', 0); 180 | $mform->disabledIf('sendcompletion', 'sendgrades'); 181 | $mform->addHelpButton('sendcompletion', 'sendcompletion', 'local_ltiprovider'); 182 | 183 | $mform->addElement('header', 'memberships', get_string('membershipsettings', 'local_ltiprovider')); 184 | $mform->addElement('checkbox', 'syncmembers', null, get_string('enablememberssync', 'local_ltiprovider')); 185 | $mform->disabledIf('syncmembers', 'contextid', 'neq', $context->id); 186 | 187 | $options = array(); 188 | $options[30*60] = '30 ' . get_string('minutes'); 189 | $options[60*60] = '1 ' . get_string('hour'); 190 | $options[2*60*60] = '2 ' . get_string('hours'); 191 | $options[6*60*60] = '6 ' . get_string('hours'); 192 | $options[12*60*60] = '12 ' . get_string('hours'); 193 | $options[24*60*60] = '24 ' . get_string('hours'); 194 | $mform->addElement('select', 'syncperiod', get_string('syncperiod', 'local_ltiprovider'), $options); 195 | $mform->setDefault('syncperiod', 30*60); 196 | $mform->disabledIf('syncperiod', 'contextid', 'neq', $context->id); 197 | 198 | $options = array(); 199 | $options[1] = get_string('enrolandunenrol' , 'local_ltiprovider'); 200 | $options[2] = get_string('enrolnew' , 'local_ltiprovider'); 201 | $options[3] = get_string('unenrolmissing' , 'local_ltiprovider'); 202 | $mform->addElement('select', 'syncmode', get_string('syncmode', 'local_ltiprovider'), $options); 203 | $mform->setDefault('syncmode', 1); 204 | $mform->disabledIf('syncmode', 'contextid', 'neq', $context->id); 205 | 206 | 207 | $mform->addElement('header', 'layoutandcss', get_string('layoutandcss', 'local_ltiprovider')); 208 | 209 | $mform->addElement('checkbox', 'hidepageheader', null, get_string('hidepageheader', 'local_ltiprovider')); 210 | $mform->addElement('checkbox', 'hidepagefooter', null, get_string('hidepagefooter', 'local_ltiprovider')); 211 | $mform->addElement('checkbox', 'hideleftblocks', null, get_string('hideleftblocks', 'local_ltiprovider')); 212 | $mform->addElement('checkbox', 'hiderightblocks', null, get_string('hiderightblocks', 'local_ltiprovider')); 213 | $mform->setAdvanced('hideleftblocks'); 214 | $mform->setAdvanced('hiderightblocks'); 215 | 216 | $editoroptions = array(); 217 | $displayoptions = array('rows'=>'4', 'cols'=>''); 218 | $mform->addElement('textarea', 'customcss', get_string('customcss', 'local_ltiprovider'), $displayoptions, $editoroptions); 219 | $mform->setAdvanced('customcss'); 220 | 221 | $mform->addElement('hidden', 'id'); 222 | $mform->setType('id', PARAM_INT); 223 | 224 | $mform->addElement('hidden', 'courseid'); 225 | $mform->setType('courseid', PARAM_INT); 226 | 227 | local_ltiprovider_call_hook("add_settings", (object) array('mform' => $mform, 228 | 'customdata' => $this->_customdata, 229 | 'tool' => $tool)); 230 | 231 | $this->add_action_buttons(); 232 | } 233 | 234 | public function validation($data, $files) { 235 | global $COURSE, $DB, $CFG; 236 | 237 | $errors = parent::validation($data, $files); 238 | 239 | if (!empty($data['enrolenddate']) and $data['enrolenddate'] < $data['enrolstartdate']) { 240 | $errors['enrolenddate'] = get_string('enrolenddaterror', 'local_ltiprovider'); 241 | } 242 | 243 | if (!empty($data['requirecompletion']) || !empty($data['sendcompletion'])) { 244 | $completion = new completion_info($COURSE); 245 | $moodlecontext = $DB->get_record('context', array('id' => $data['contextid'])); 246 | if ($moodlecontext->contextlevel == CONTEXT_MODULE) { 247 | $cm = get_coursemodule_from_id(false, $moodlecontext->instanceid, 0, false, MUST_EXIST); 248 | } else { 249 | $cm = null; 250 | } 251 | 252 | if (! $completion->is_enabled($cm)) { 253 | if (!empty($data['requirecompletion'])) { 254 | $errors['requirecompletion'] = get_string('errorcompletionenabled', 'local_ltiprovider'); 255 | } 256 | if (!empty($data['sendcompletion'])) { 257 | $errors['sendcompletion'] = get_string('errorcompletionenabled', 'local_ltiprovider'); 258 | } 259 | } 260 | } 261 | 262 | return $errors; 263 | } 264 | 265 | } 266 | -------------------------------------------------------------------------------- /extension/scormbridge/lang/en/ltiproviderextension_scormbridge.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Strings for component ltiproviderextension_scormbridge. 19 | * 20 | * @package ltiproviderextension 21 | * @subpackage scormbridge 22 | * @copyright 2014 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | $string['pluginname'] = 'SCORM bridge'; -------------------------------------------------------------------------------- /extension/scormbridge/lib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Library functions. 19 | * 20 | * @package ltiproviderextension 21 | * @subpackage scormbridge 22 | * @copyright 2014 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | 29 | /** 30 | * SCORM Bridge, we detect changes in quizzes for submitting back to the parent Window that will process SCORM API messages 31 | * 32 | * @param object $nav Global navigation object 33 | */ 34 | function ltiproviderextension_scormbridge_navigation($nav) { 35 | global $DB, $SESSION, $USER, $PAGE; 36 | 37 | // First we need to check if we are in a LTI session and also if the module is a quiz. 38 | if (isset($SESSION->ltiprovider)) { 39 | $context = $SESSION->ltiprovider->context; 40 | if ($context->contextlevel == CONTEXT_MODULE) { 41 | $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); 42 | 43 | if ($cm->modname == "quiz") { 44 | $url = new moodle_url('/local/ltiprovider/extension/scormbridge/tracking.js.php', 45 | array('quizid' => $cm->instance, 'rand' => rand(0, 1000))); 46 | $PAGE->requires->js($url); 47 | 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /extension/scormbridge/logout.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Force uses logout 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '../../../../../config.php'); 27 | 28 | $toolid = required_param('toolid', PARAM_INT); 29 | 30 | if ($tool = $DB->get_record('local_ltiprovider', array('id' => $toolid))) { 31 | // Force logout. 32 | $authsequence = get_enabled_auth_plugins(); 33 | foreach ($authsequence as $authname) { 34 | $authplugin = get_auth_plugin($authname); 35 | $authplugin->logoutpage_hook(); 36 | } 37 | 38 | require_logout(); 39 | } 40 | -------------------------------------------------------------------------------- /extension/scormbridge/tracking.js.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Retrieve the quiz tracking: Attempts and grades 19 | * Information can be send using Push (postMessage) or retreived by pulling (jsonp) 20 | * 21 | * @package local 22 | * @subpackage ltiprovider 23 | * @copyright 2011 Juan Leyva 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | require_once(dirname(__FILE__) . '../../../../../config.php'); 28 | 29 | $quizid = optional_param('quizid', 0, PARAM_INT); 30 | $toolid = optional_param('toolid', 0, PARAM_INT); 31 | $jsonp = optional_param('jsonp', "", PARAM_RAW); // Whether to use a function padding for the response. 32 | 33 | if (!$quizid and $toolid) { 34 | if (isset($SESSION->ltiprovider)) { 35 | $context = $SESSION->ltiprovider->context; 36 | if ($context->contextlevel == CONTEXT_MODULE) { 37 | $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); 38 | 39 | if ($cm->modname == "quiz") { 40 | $quizid = $cm->instance; 41 | } 42 | } 43 | } 44 | } 45 | 46 | if (!$quizid) { 47 | die; 48 | } 49 | 50 | // Check that the user is logged and the LTI session launched. 51 | if (isset($SESSION->ltiprovider) and isloggedin()) { 52 | $conditions = array("userid" => $USER->id, "quiz" => $quizid); 53 | $attempts = $DB->get_records('quiz_attempts', $conditions, "id ASC"); 54 | $grades = $DB->get_records('quiz_grades', $conditions, "id ASC"); 55 | } else { 56 | $attempts = array(); 57 | $grades = array(); 58 | } 59 | 60 | $data = array( 61 | 'grades' => array_values($grades), 62 | 'attempts' => array_values($attempts) 63 | ); 64 | 65 | $data = json_encode($data); 66 | 67 | // Using JSONP, we use a padding function that must be implemented in the consumer side. 68 | if ($jsonp) { 69 | ?> 70 | 71 | (''); 72 | 73 | 76 | 77 | parent.postMessage('', "*"); 78 | 79 | . 16 | 17 | /** 18 | * Version details. 19 | * 20 | * @package ltiproviderextension 21 | * @subpackage scormbridge 22 | * @copyright 2014 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | $plugin->version = 2014080100; 29 | $plugin->requires = 2014051200; // Require Moodle version (2.7). 30 | $plugin->component = "ltiproviderextension_scormbridge"; 31 | -------------------------------------------------------------------------------- /ims-blti/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2007 Andy Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /ims-blti/OAuthBody.php: -------------------------------------------------------------------------------- 1 | add_consumer($oauth_consumer_key, $oauth_consumer_secret); 39 | 40 | $server = new OAuthServer($store); 41 | 42 | $method = new OAuthSignatureMethod_HMAC_SHA1(); 43 | $server->add_signature_method($method); 44 | $request = OAuthRequest::from_request(); 45 | 46 | global $LastOAuthBodyBaseString; 47 | $LastOAuthBodyBaseString = $request->get_signature_base_string(); 48 | // echo($LastOAuthBodyBaseString."\n"); 49 | 50 | try { 51 | $server->verify_request($request); 52 | } catch (Exception $e) { 53 | $message = $e->getMessage(); 54 | throw new Exception("OAuth signature failed: " . $message); 55 | } 56 | 57 | $postdata = file_get_contents('php://input'); 58 | // echo($postdata); 59 | 60 | $hash = base64_encode(sha1($postdata, TRUE)); 61 | 62 | if ( $hash != $oauth_body_hash ) { 63 | throw new Exception("OAuth oauth_body_hash mismatch"); 64 | } 65 | 66 | return $postdata; 67 | } 68 | 69 | function sendOAuthBodyPOST($method, $endpoint, $oauth_consumer_key, $oauth_consumer_secret, $content_type, $body) 70 | { 71 | $hash = base64_encode(sha1($body, TRUE)); 72 | 73 | $parms = array('oauth_body_hash' => $hash); 74 | 75 | $test_token = ''; 76 | $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); 77 | $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL); 78 | 79 | $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms); 80 | $acc_req->sign_request($hmac_method, $test_consumer, $test_token); 81 | 82 | // Pass this back up "out of band" for debugging 83 | global $LastOAuthBodyBaseString; 84 | $LastOAuthBodyBaseString = $acc_req->get_signature_base_string(); 85 | // echo($LastOAuthBodyBaseString."\m"); 86 | 87 | $headers = array(); 88 | $headers[] = $acc_req->to_header(); 89 | $headers[] = "Content-type: " . $content_type; 90 | 91 | $curl = new \curl(); 92 | $curl->setHeader($headers); 93 | $response = $curl->post($endpoint, $body); 94 | 95 | return $response; 96 | } 97 | 98 | function sendOAuthParamsPOST($method, $endpoint, $oauth_consumer_key, $oauth_consumer_secret, $content_type, $params) 99 | { 100 | 101 | if (is_array($params)) { 102 | $body = http_build_query($params, '', '&'); 103 | } else { 104 | $body = $params; 105 | } 106 | 107 | $hash = base64_encode(sha1($body, TRUE)); 108 | 109 | $parms = $params; 110 | $parms['oauth_body_hash'] = $hash; 111 | 112 | $test_token = ''; 113 | $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); 114 | $test_consumer = new OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL); 115 | 116 | $acc_req = OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms); 117 | $acc_req->sign_request($hmac_method, $test_consumer, $test_token); 118 | 119 | // Pass this back up "out of band" for debugging 120 | global $LastOAuthBodyBaseString; 121 | $LastOAuthBodyBaseString = $acc_req->get_signature_base_string(); 122 | // echo($LastOAuthBodyBaseString."\m"); 123 | 124 | $header = $acc_req->to_header(); 125 | $header = $header . "\r\nContent-type: " . $content_type . "\r\n"; 126 | 127 | $params = array('http' => array( 128 | 'method' => 'POST', 129 | 'content' => $body, 130 | 'header' => $header 131 | )); 132 | $ctx = stream_context_create($params); 133 | $fp = @fopen($endpoint, 'rb', false, $ctx); 134 | if (!$fp) { 135 | throw new \Exception("Problem with $endpoint, $php_errormsg"); 136 | } 137 | $response = @stream_get_contents($fp); 138 | if ($response === false) { 139 | throw new \Exception("Problem reading data from $endpoint, $php_errormsg"); 140 | } 141 | return $response; 142 | } 143 | 144 | ?> 145 | -------------------------------------------------------------------------------- /ims-blti/TrivialOAuthDataStore.php: -------------------------------------------------------------------------------- 1 | consumers[$consumer_key] = $consumer_secret; 13 | } 14 | 15 | function lookup_consumer($consumer_key) { 16 | if ( strpos($consumer_key, "http://" ) === 0 ) { 17 | $consumer = new OAuthConsumer($consumer_key,"secret", NULL); 18 | return $consumer; 19 | } 20 | if ( $this->consumers[$consumer_key] ) { 21 | $consumer = new OAuthConsumer($consumer_key,$this->consumers[$consumer_key], NULL); 22 | return $consumer; 23 | } 24 | return NULL; 25 | } 26 | 27 | function lookup_token($consumer, $token_type, $token) { 28 | return new OAuthToken($consumer, ""); 29 | } 30 | 31 | // Return NULL if the nonce has not been used 32 | // Return $nonce if the nonce was previously used 33 | function lookup_nonce($consumer, $token, $nonce, $timestamp) { 34 | // Should add some clever logic to keep nonces from 35 | // being reused - for no we are really trusting 36 | // that the timestamp will save us 37 | return NULL; 38 | } 39 | 40 | function new_request_token($consumer) { 41 | return NULL; 42 | } 43 | 44 | function new_access_token($token, $consumer) { 45 | return NULL; 46 | } 47 | } 48 | ?> -------------------------------------------------------------------------------- /ims-blti/blti.php: -------------------------------------------------------------------------------- 1 | 0 ) { 40 | $row = $_SESSION['_basiclti_lti_row']; 41 | if ( isset($row) ) $this->row = $row; 42 | $context_id = $_SESSION['_basiclti_lti_context_id']; 43 | if ( isset($context_id) ) $this->context_id = $context_id; 44 | $info = $_SESSION['_basic_lti_context']; 45 | if ( isset($info) ) { 46 | $this->info = $info; 47 | $this->valid = true; 48 | return; 49 | } 50 | $this->message = "Could not find context in session"; 51 | return; 52 | } 53 | $this->message = "Session not available"; 54 | return; 55 | } 56 | 57 | // Insure we have a valid launch 58 | if ( empty($_REQUEST["oauth_consumer_key"]) ) { 59 | $this->message = "Missing oauth_consumer_key in request"; 60 | return; 61 | } 62 | $oauth_consumer_key = $_REQUEST["oauth_consumer_key"]; 63 | 64 | // Find the secret - either form the parameter as a string or 65 | // look it up in a database from parameters we are given 66 | $secret = false; 67 | $row = false; 68 | if ( is_string($parm) ) { 69 | $secret = $parm; 70 | } else if ( ! is_array($parm) ) { 71 | $this->message = "Constructor requires a secret or database information."; 72 | return; 73 | } 74 | 75 | // Verify the message signature 76 | $store = new ltiprovider\TrivialOAuthDataStore(); 77 | $store->add_consumer($oauth_consumer_key, $secret); 78 | 79 | $server = new ltiprovider\OAuthServer($store); 80 | 81 | $method = new ltiprovider\OAuthSignatureMethod_HMAC_SHA1(); 82 | $server->add_signature_method($method); 83 | $request = ltiprovider\OAuthRequest::from_request(); 84 | 85 | $this->basestring = $request->get_signature_base_string(); 86 | 87 | try { 88 | $server->verify_request($request); 89 | $this->valid = true; 90 | } catch (Exception $e) { 91 | $this->message = $e->getMessage(); 92 | return; 93 | } 94 | 95 | // Store the launch information in the session for later 96 | $newinfo = array(); 97 | foreach($_POST as $key => $value ) { 98 | if ( $key == "basiclti_submit" ) continue; 99 | if ( strpos($key, "oauth_") === false ) { 100 | $newinfo[$key] = $value; 101 | continue; 102 | } 103 | if ( $key == "oauth_consumer_key" ) { 104 | $newinfo[$key] = $value; 105 | continue; 106 | } 107 | } 108 | 109 | //Added abertranb to decode base 64 20120801 110 | if (isset($newinfo['custom_lti_message_encoded_base64']) && $newinfo['custom_lti_message_encoded_base64']==1){ 111 | $newinfo = $this->decodeBase64($newinfo); 112 | } 113 | 114 | $this->info = $newinfo; 115 | 116 | if ( $usesession == true and strlen(session_id()) > 0 ) { 117 | $_SESSION['_basic_lti_context'] = $this->info; 118 | unset($_SESSION['_basiclti_lti_row']); 119 | unset($_SESSION['_basiclti_lti_context_id']); 120 | if ( $this->row ) $_SESSION['_basiclti_lti_row'] = $this->row; 121 | if ( $this->context_id ) $_SESSION['_basiclti_lti_context_id'] = $this->context_id; 122 | } 123 | 124 | if ( $this->valid && $doredirect ) { 125 | $this->redirect(); 126 | $this->complete = true; 127 | } 128 | } 129 | 130 | function addSession($location) { 131 | if ( ini_get('session.use_cookies') == 0 ) { 132 | if ( strpos($location,'?') > 0 ) { 133 | $location = $location . '&'; 134 | } else { 135 | $location = $location . '?'; 136 | } 137 | $location = $location . session_name() . '=' . session_id(); 138 | } 139 | return $location; 140 | } 141 | 142 | function isInstructor() { 143 | $roles = $this->info['roles']; 144 | $roles = strtolower($roles); 145 | if ( ! ( strpos($roles,"instructor") === false ) ) return true; 146 | if ( ! ( strpos($roles,"administrator") === false ) ) return true; 147 | return false; 148 | } 149 | 150 | function getUserEmail() { 151 | # set default email in the event privacy settings don't pass in email. 152 | $email = md5($this->info['user_id']) . "@ltiuser.com"; 153 | if ( !empty($this->info['lis_person_contact_email_primary']) ) $email = $this->info['lis_person_contact_email_primary']; 154 | # Sakai Hack 155 | if ( !empty($this->info['lis_person_contact_emailprimary']) ) $email = $this->info['lis_person_contact_emailprimary']; 156 | return $email; 157 | } 158 | 159 | function getUserShortName() { 160 | $email = $this->getUserEmail(); 161 | $givenname = $this->info['lis_person_name_given']; 162 | $familyname = $this->info['lis_person_name_family']; 163 | $fullname = $this->info['lis_person_name_full']; 164 | if ( strlen($email) > 0 ) return $email; 165 | if ( strlen($givenname) > 0 ) return $givenname; 166 | if ( strlen($familyname) > 0 ) return $familyname; 167 | return $this->getUserName(); 168 | } 169 | 170 | function getUserName() { 171 | $givenname = $this->info['lis_person_name_given']; 172 | $familyname = $this->info['lis_person_name_family']; 173 | $fullname = $this->info['lis_person_name_full']; 174 | if ( strlen($fullname) > 0 ) return $fullname; 175 | if ( strlen($familyname) > 0 and strlen($givenname) > 0 ) return $givenname + $familyname; 176 | if ( strlen($givenname) > 0 ) return $givenname; 177 | if ( strlen($familyname) > 0 ) return $familyname; 178 | return $this->getUserEmail(); 179 | } 180 | 181 | function getUserKey() { 182 | $oauth = $this->info['oauth_consumer_key']; 183 | $id = $this->info['user_id']; 184 | if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id; 185 | return false; 186 | } 187 | 188 | function getUserImage() { 189 | $image = $this->info['user_image']; 190 | if ( strlen($image) > 0 ) return $image; 191 | $email = $this->getUserEmail(); 192 | if ( $email === false ) return false; 193 | $size = 40; 194 | $grav_url = $_SERVER['HTTPS'] ? 'https://' : 'http://'; 195 | $grav_url = $grav_url . "www.gravatar.com/avatar.php?gravatar_id=".md5( strtolower($email) )."&size=".$size; 196 | return $grav_url; 197 | } 198 | 199 | function getResourceKey() { 200 | $oauth = $this->info['oauth_consumer_key']; 201 | $id = $this->info['resource_link_id']; 202 | if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id; 203 | return false; 204 | } 205 | 206 | function getResourceTitle() { 207 | $title = $this->info['resource_link_title']; 208 | if ( strlen($title) > 0 ) return $title; 209 | return false; 210 | } 211 | 212 | function getConsumerKey() { 213 | $oauth = $this->info['oauth_consumer_key']; 214 | return $oauth; 215 | } 216 | 217 | function getCourseKey() { 218 | if ( $this->context_id ) return $this->context_id; 219 | $oauth = $this->info['oauth_consumer_key']; 220 | $id = $this->info['context_id']; 221 | if ( strlen($id) > 0 and strlen($oauth) > 0 ) return $oauth . ':' . $id; 222 | return false; 223 | } 224 | 225 | function getCourseName() { 226 | $label = $this->info['context_label']; 227 | $title = $this->info['context_title']; 228 | $id = $this->info['context_id']; 229 | if ( strlen($label) > 0 ) return $label; 230 | if ( strlen($title) > 0 ) return $title; 231 | if ( strlen($id) > 0 ) return $id; 232 | return false; 233 | } 234 | 235 | // TODO: Add javasript version if headers are already sent 236 | function redirect() { 237 | $host = $_SERVER['HTTP_HOST']; 238 | $uri = $_SERVER['PHP_SELF']; 239 | $location = $_SERVER['HTTPS'] ? 'https://' : 'http://'; 240 | $location = $location . $host . $uri; 241 | $location = $this->addSession($location); 242 | header("Location: $location"); 243 | } 244 | 245 | function dump() { 246 | if ( ! $this->valid or $this->info == false ) return "Context not valid\n"; 247 | $ret = ""; 248 | if ( $this->isInstructor() ) { 249 | $ret .= "isInstructor() = true\n"; 250 | } else { 251 | $ret .= "isInstructor() = false\n"; 252 | } 253 | $ret .= "getUserKey() = ".$this->getUserKey()."\n"; 254 | $ret .= "getUserEmail() = ".$this->getUserEmail()."\n"; 255 | $ret .= "getUserShortName() = ".$this->getUserShortName()."\n"; 256 | $ret .= "getUserName() = ".$this->getUserName()."\n"; 257 | $ret .= "getUserImage() = ".$this->getUserImage()."\n"; 258 | $ret .= "getResourceKey() = ".$this->getResourceKey()."\n"; 259 | $ret .= "getResourceTitle() = ".$this->getResourceTitle()."\n"; 260 | $ret .= "getCourseName() = ".$this->getCourseName()."\n"; 261 | $ret .= "getCourseKey() = ".$this->getCourseKey()."\n"; 262 | $ret .= "getConsumerKey() = ".$this->getConsumerKey()."\n"; 263 | return $ret; 264 | } 265 | 266 | /** 267 | * Data submitter are in base64 then we have to decode 268 | * @author Antoni Bertran (antoni@tresipunt.com) 269 | * @param $info array 270 | * @date 20120801 271 | */ 272 | function decodeBase64($info) { 273 | $keysNoEncode = array("lti_version", "lti_message_type", "tool_consumer_instance_description", "tool_consumer_instance_guid", "oauth_consumer_key", "custom_lti_message_encoded_base64", "oauth_nonce", "oauth_version", "oauth_callback", "oauth_timestamp", "basiclti_submit", "oauth_signature_method", "ext_ims_lis_memberships_id", "ext_ims_lis_memberships_url"); 274 | foreach ($info as $key => $item){ 275 | if (!in_array($key, $keysNoEncode)) 276 | $info[$key] = base64_decode($item); 277 | } 278 | return $info; 279 | } 280 | 281 | } 282 | 283 | ?> 284 | -------------------------------------------------------------------------------- /ims-blti/blti_util.php: -------------------------------------------------------------------------------- 1 | "120988f929-274612", 11 | "resource_link_title" => "Weekly Blog", 12 | "resource_link_description" => "Each student needs to reflect on the weekly reading. These should be one paragraph long.", 13 | "user_id" => "292832126", 14 | "roles" => "Instructor", // or Learner 15 | "lis_person_name_full" => 'Jane Q. Public', 16 | "lis_person_contact_email_primary" => "user@school.edu", 17 | "lis_person_sourcedid" => "school.edu:user", 18 | "context_id" => "456434513", 19 | "context_title" => "Design of Personal Environments", 20 | "context_label" => "SI182", 21 | ); 22 | 23 | return $parms; 24 | } 25 | 26 | function validateDescriptor($descriptor) 27 | { 28 | $xml = new SimpleXMLElement($xmldata); 29 | if ( ! $xml ) { 30 | echo("Error parsing Descriptor XML\n"); 31 | return; 32 | } 33 | $launch_url = $xml->secure_launch_url[0]; 34 | if ( ! $launch_url ) $launch_url = $xml->launch_url[0]; 35 | if ( $launch_url ) $launch_url = (string) $launch_url; 36 | return $launch_url; 37 | } 38 | 39 | // Parse a descriptor 40 | function launchInfo($xmldata) { 41 | $xml = new SimpleXMLElement($xmldata); 42 | if ( ! $xml ) { 43 | echo("Error parsing Descriptor XML\n"); 44 | return; 45 | } 46 | $launch_url = $xml->secure_launch_url[0]; 47 | if ( ! $launch_url ) $launch_url = $xml->launch_url[0]; 48 | if ( $launch_url ) $launch_url = (string) $launch_url; 49 | $custom = array(); 50 | if ( $xml->custom[0]->parameter ) 51 | foreach ( $xml->custom[0]->parameter as $resource) { 52 | $key = (string) $resource['key']; 53 | $key = strtolower($key); 54 | $nk = ""; 55 | for($i=0; $i < strlen($key); $i++) { 56 | $ch = substr($key,$i,1); 57 | if ( $ch >= "a" && $ch <= "z" ) $nk .= $ch; 58 | else if ( $ch >= "0" && $ch <= "9" ) $nk .= $ch; 59 | else $nk .= "_"; 60 | } 61 | $value = (string) $resource; 62 | $custom["custom_".$nk] = $value; 63 | } 64 | return array("launch_url" => $launch_url, "custom" => $custom ) ; 65 | } 66 | 67 | function local_ltiprovider_split_custom_parameters($customstr) { 68 | $lines = preg_split("/[\n;]/",$customstr); 69 | $retval = array(); 70 | foreach ($lines as $line){ 71 | $pos = strpos($line,"="); 72 | if ( $pos === false || $pos < 1 ) continue; 73 | $key = trim(substr($line, 0, $pos)); 74 | $val = trim(substr($line, $pos+1)); 75 | $key = local_ltiprovider_map_keyname($key); 76 | $retval['custom_'.$key] = $val; 77 | } 78 | return $retval; 79 | } 80 | 81 | function local_ltiprovider_map_keyname($key) { 82 | $newkey = ""; 83 | $key = strtolower(trim($key)); 84 | foreach (str_split($key) as $ch) { 85 | if ( ($ch >= 'a' && $ch <= 'z') || ($ch >= '0' && $ch <= '9') ) { 86 | $newkey .= $ch; 87 | } else { 88 | $newkey .= '_'; 89 | } 90 | } 91 | return $newkey; 92 | } 93 | 94 | function signParameters($oldparms, $endpoint, $method, $oauth_consumer_key, $oauth_consumer_secret, 95 | $submit_text = false, $org_id = false, $org_desc = false) 96 | { 97 | global $last_base_string; 98 | $parms = $oldparms; 99 | if ( ! isset($parms["lti_version"]) ) $parms["lti_version"] = "LTI-1p0"; 100 | if ( ! isset($parms["lti_message_type"]) ) $parms["lti_message_type"] = "basic-lti-launch-request"; 101 | if ( ! isset($parms["oauth_callback"]) ) $parms["oauth_callback"] = "about:blank"; 102 | if ( $org_id ) $parms["tool_consumer_instance_guid"] = $org_id; 103 | if ( $org_desc ) $parms["tool_consumer_instance_description"] = $org_desc; 104 | if ( $submit_text ) $parms["ext_submit"] = $submit_text; 105 | 106 | $test_token = ''; 107 | 108 | $hmac_method = new ltiprovider\OAuthSignatureMethod_HMAC_SHA1(); 109 | $test_consumer = new ltiprovider\OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL); 110 | 111 | $acc_req = ltiprovider\OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms); 112 | 113 | $acc_req->sign_request($hmac_method, $test_consumer, $test_token); 114 | 115 | // Pass this back up "out of band" for debugging 116 | $last_base_string = $acc_req->get_signature_base_string(); 117 | 118 | $newparms = $acc_req->get_parameters(); 119 | 120 | return $newparms; 121 | } 122 | 123 | function signOnly($oldparms, $endpoint, $method, $oauth_consumer_key, $oauth_consumer_secret) 124 | { 125 | global $last_base_string; 126 | $parms = $oldparms; 127 | 128 | $test_token = ''; 129 | 130 | $hmac_method = new ltiprovider\OAuthSignatureMethod_HMAC_SHA1(); 131 | $test_consumer = new ltiprovider\OAuthConsumer($oauth_consumer_key, $oauth_consumer_secret, NULL); 132 | 133 | $acc_req = ltiprovider\OAuthRequest::from_consumer_and_token($test_consumer, $test_token, $method, $endpoint, $parms); 134 | $acc_req->sign_request($hmac_method, $test_consumer, $test_token); 135 | 136 | // Pass this back up "out of band" for debugging 137 | $last_base_string = $acc_req->get_signature_base_string(); 138 | 139 | $newparms = $acc_req->get_parameters(); 140 | 141 | return $newparms; 142 | } 143 | 144 | function postLaunchHTML($newparms, $endpoint, $debug=false, $iframeattr=false) { 145 | global $last_base_string; 146 | $r = "
\n"; 147 | if ( $iframeattr ) { 148 | $r = "
\n" ; 149 | } else { 150 | $r = "\n" ; 151 | } 152 | $submit_text = $newparms['ext_submit']; 153 | foreach($newparms as $key => $value ) { 154 | $key = htmlspecialchars($key); 155 | $value = htmlspecialchars($value); 156 | if ( $key == "ext_submit" ) { 157 | $r .= "\n"; 165 | } 166 | if ( $debug ) { 167 | $r .= "\n"; 180 | $r .= ""; 181 | $r .= get_stringIMS("toggle_debug_data","basiclti")."\n"; 182 | $r .= "
\n"; 183 | $r .= "".get_stringIMS("basiclti_endpoint","basiclti")."
\n"; 184 | $r .= $endpoint . "
\n 
\n"; 185 | $r .= "".get_stringIMS("basiclti_parameters","basiclti")."
\n"; 186 | foreach($newparms as $key => $value ) { 187 | $key = htmlspecialchars($key); 188 | $value = htmlspecialchars($value); 189 | $r .= "$key = $value
\n"; 190 | } 191 | $r .= " 
\n"; 192 | $r .= "

".get_stringIMS("basiclti_base_string","basiclti")."
\n".$last_base_string."

\n"; 193 | $r .= "
\n"; 194 | } 195 | $r .= "
\n"; 196 | if ( $iframeattr ) { 197 | $r .= "\n"; 199 | } 200 | if ( ! $debug ) { 201 | $ext_submit = "ext_submit"; 202 | $ext_submit_text = $submit_text; 203 | $r .= " \n"; 214 | } 215 | $r .= "
\n"; 216 | return $r; 217 | } 218 | 219 | /* This is a bit of homage to Moodle's pattern of internationalisation */ 220 | function get_stringIMS($key,$bundle) { 221 | return $key; 222 | } 223 | 224 | function do_post_request($url, $data, $optional_headers = null) 225 | { 226 | $params = array('http' => array( 227 | 'method' => 'POST', 228 | 'content' => $data 229 | )); 230 | 231 | if ($optional_headers !== null) { 232 | $header = $optional_headers . "\r\n"; 233 | } 234 | // $header = $header . "Content-type: application/x-www-form-urlencoded\r\n"; 235 | $params['http']['header'] = $header; 236 | $ctx = stream_context_create($params); 237 | $fp = @fopen($url, 'rb', false, $ctx); 238 | if (!$fp) { 239 | echo @stream_get_contents($fp); 240 | throw new Exception("Problem with $url, $php_errormsg"); 241 | } 242 | $response = @stream_get_contents($fp); 243 | if ($response === false) { 244 | throw new Exception("Problem reading data from $url, $php_errormsg"); 245 | } 246 | return $response; 247 | } 248 | 249 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * List the tool provided in a course 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | require_once($CFG->dirroot.'/local/ltiprovider/lib.php'); 28 | 29 | $courseid = required_param('courseid', PARAM_INT); 30 | 31 | if (! ($course = $DB->get_record('course', array('id'=>$courseid)))) { 32 | print_error('invalidcourseid', 'error'); 33 | } 34 | 35 | $PAGE->set_url('/local/ltiprovider/index.php', array('courseid' => $courseid)); 36 | 37 | context_helper::preload_course($course->id); 38 | if (!$context = context_course::instance($course->id)) { 39 | print_error('nocontext'); 40 | } 41 | 42 | require_login($course); 43 | require_capability('local/ltiprovider:view', $context); 44 | 45 | // $PAGE->navbar->add(get_string('toolsprovided', 'local_ltiprovider')); 46 | echo $OUTPUT->header(); 47 | 48 | echo $OUTPUT->heading(get_string('toolsprovided', 'local_ltiprovider')); 49 | 50 | $tools = $DB->get_records('local_ltiprovider', array('courseid' => $course->id)); 51 | 52 | $data = array(); 53 | foreach ($tools as $tool) { 54 | if (!$toolcontext = context::instance_by_id($tool->contextid, IGNORE_MISSING)) { 55 | local_ltiprovider_delete_tool($tool); 56 | continue; 57 | } 58 | $line = array(); 59 | $line[] = $toolcontext->get_context_name(); 60 | $line[] = $tool->secret; 61 | $line[] = new moodle_url('/local/ltiprovider/tool.php', array('id' => $tool->id)); 62 | 63 | if (has_capability('local/ltiprovider:manage', $context)) { 64 | $buttons = array(); 65 | 66 | $buttons[] = html_writer::link(new moodle_url('/local/ltiprovider/edit.php', array('id'=>$tool->id, 'delete'=>1, 'sesskey'=>sesskey())), $OUTPUT->pix_icon('t/delete', get_string('delete'))); 67 | $buttons[] = html_writer::link(new moodle_url('/local/ltiprovider/edit.php', array('id'=>$tool->id, 'sesskey'=>sesskey())), $OUTPUT->pix_icon('t/edit', get_string('edit'))); 68 | 69 | if ($tool->disabled) { 70 | $buttons[] = html_writer::link(new moodle_url('/local/ltiprovider/edit.php', array('id'=>$tool->id, 'show'=>1, 'sesskey'=>sesskey())), $OUTPUT->pix_icon('t/show', get_string('show'))); 71 | } else { 72 | $buttons[] = html_writer::link(new moodle_url('/local/ltiprovider/edit.php', array('id'=>$tool->id, 'hide'=>1, 'sesskey'=>sesskey())), $OUTPUT->pix_icon('t/hide', get_string('hide'))); 73 | } 74 | 75 | if ($tool->sendgrades) { 76 | $buttons[] = html_writer::link(new moodle_url('/local/ltiprovider/syncreport.php', array('id'=>$tool->id)), $OUTPUT->pix_icon('i/grades', get_string('gradessyncreport', 'local_ltiprovider'))); 77 | } 78 | 79 | if ($tool->syncmembers) { 80 | $buttons[] = html_writer::link(new moodle_url('/local/ltiprovider/syncmembers.php', array('id'=>$tool->id)), $OUTPUT->pix_icon('i/users', get_string('forcesyncmembers', 'local_ltiprovider'))); 81 | } 82 | 83 | $line[] = implode(' ', $buttons); 84 | } else { 85 | $line[] = ''; 86 | } 87 | $data[] = $line; 88 | } 89 | 90 | // Moodle 2.2 and onwards 91 | if (isset($CFG->allowframembedding) and !$CFG->allowframembedding) { 92 | echo $OUTPUT->box(get_string('allowframembedding', 'local_ltiprovider')); 93 | } 94 | 95 | $table = new html_table(); 96 | $table->head = array( 97 | get_string('name', 'local_ltiprovider'), 98 | get_string('secret', 'local_ltiprovider'), 99 | get_string('url', 'local_ltiprovider'), 100 | get_string('edit')); 101 | $table->size = array('20%', '20%', '50%', '10%'); 102 | $table->align = array('left', 'left', 'left', 'center'); 103 | $table->width = '99%'; 104 | $table->data = $data; 105 | echo html_writer::table($table); 106 | 107 | echo $OUTPUT->single_button(new moodle_url('/local/ltiprovider/edit.php', array('id' => -1, 'courseid' => $course->id)), get_string('add')); 108 | 109 | echo $OUTPUT->footer(); 110 | -------------------------------------------------------------------------------- /js/syncreport.js: -------------------------------------------------------------------------------- 1 | // This file is part of Moodle - http://moodle.org/ 2 | // 3 | // Moodle is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // Moodle is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with Moodle. If not, see . 15 | 16 | /** 17 | * Javascript helper function for Sync Report plugin 18 | * 19 | * @package local-ltiprovider 20 | * @author Antoni Bertran antoni@tresipunt.com 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | require(['jquery'], function($) { 25 | var lastChecked = null; 26 | 27 | $(document).ready(function() { 28 | var $chkboxes = $('.usercheckbox'); 29 | $chkboxes.click(function(e) { 30 | if(!lastChecked) { 31 | lastChecked = this; 32 | return; 33 | } 34 | 35 | if(e.shiftKey) { 36 | var start = $chkboxes.index(this); 37 | var end = $chkboxes.index(lastChecked); 38 | 39 | $chkboxes.slice(Math.min(start,end), Math.max(start,end)+ 1).prop('checked', lastChecked.checked); 40 | 41 | } 42 | 43 | lastChecked = this; 44 | }); 45 | }); 46 | }); 47 | 48 | function ltiprovider_send_syncreport() { 49 | event.preventDefault(); 50 | var checkboxes = document.querySelectorAll('input[name="user_force_grade_checkbox"]:checked'), checks = ''; 51 | Array.prototype.forEach.call(checkboxes, function(el) { 52 | checks += (checks.length>0?',':'') + el.value; 53 | }); 54 | if (checks.length>0) { 55 | var x = document.getElementsByName("user_force_grade"); 56 | var i; 57 | //should be only one 58 | for (i = 0; i < x.length; i++) { 59 | x[i].value = checks; 60 | } 61 | document.getElementById('ltiprovider_send_syncreport_form').submit(); 62 | } else { 63 | alert(M.util.get_string('youhavetoselectauser', 'local_ltiprovider')); 64 | } 65 | return false; 66 | 67 | } 68 | -------------------------------------------------------------------------------- /lang/en/local_ltiprovider.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Language strings 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | $string['pluginname'] = 'LTI Provider'; 27 | $string['providetool'] = 'Provide a tool for an external system'; 28 | 29 | $string['remotesystem'] = 'Remote system'; 30 | $string['userdefaultvalues'] = 'User default values'; 31 | $string['remoteencoding'] = 'Remote system encoding'; 32 | $string['secret'] = 'Shared secret'; 33 | $string['toolsettings'] = 'Tool settings'; 34 | 35 | $string['enrolperiod'] = 'Enrolment duration'; 36 | $string['enrolperiod_desc'] = 'Default length of time that the enrolment is valid (in seconds). If set to zero, the enrolment duration will be unlimited by default.'; 37 | $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user enrols themselves from the remote system. If disabled, the enrolment duration will be unlimited.'; 38 | $string['enrolstartdate'] = 'Start date'; 39 | $string['enrolstartdate_help'] = 'If enabled, users can access from this date onward only.'; 40 | $string['enrolenddate'] = 'End date'; 41 | $string['enrolenddate_help'] = 'If enabled, users can access until this date only.'; 42 | $string['enrolenddaterror'] = 'Enrolment end date cannot be earlier than start date'; 43 | 44 | $string['maxenrolled'] = 'Max enrolled users'; 45 | $string['maxenrolled_help'] = 'Specifies the maximum number of users that can access from the remote system. 0 means no limit.'; 46 | $string['maxenrolledreached'] = 'Maximum number of users allowed to access was already reached.'; 47 | 48 | $string['courseroleinstructor'] = 'Course role for Instructor'; 49 | $string['courserolelearner'] = 'Course role for Learner'; 50 | $string['activityroleinstructor'] = 'Activity role for Instructor'; 51 | $string['activityrolelearner'] = 'Activity role for Learner'; 52 | 53 | $string['tooldisabled'] = 'Access to the tool is disabled'; 54 | $string['tooltobeprovide'] = 'Tool to be provided'; 55 | $string['delconfirm'] = 'Are you sure you want to delete this tool?'; 56 | $string['deletetool'] = 'Delete a tool'; 57 | $string['toolsprovided'] = 'List of tools provided'; 58 | $string['name'] = 'Tool name'; 59 | $string['url'] = 'Launch URL'; 60 | $string['layoutandcss'] = 'Layout and CSS'; 61 | $string['hidepageheader'] = 'Hide page header'; 62 | $string['hidepagefooter'] = 'Hide page footer'; 63 | $string['hideleftblocks'] = 'Hide left blocks'; 64 | $string['hiderightblocks'] = 'Hide right blocks'; 65 | $string['customcss'] = 'Custom CSS'; 66 | $string['sendgrades'] = 'Send grades back'; 67 | $string['forcenavigation'] = 'Force course or activity navigation'; 68 | 69 | $string['invalidcredentials'] = 'Invalid credentials'; 70 | $string['allowframembedding'] = 'In order to avoid problems embedding this site, please enable the allowframembedding setting in Admin -> Security -> HTTP security'; 71 | $string['newpopupnotice'] = 'The tool will be opened in a new Window. Please, check that popups for this site are enabled in your browser. You can use the link displayed bellow for opening the tool.'; 72 | $string['opentool'] = 'Open tool in a new window'; 73 | 74 | $string['enrolmentnotstarted'] = 'The enrolment period has not started'; 75 | $string['enrolmentfinished'] = 'The enrolment period has finished'; 76 | $string['ltiprovider:manage'] = 'Manage tools (provide)'; 77 | $string['ltiprovider:view'] = 'View tools provided'; 78 | 79 | $string['globalsharedsecret'] = 'Global Shared Secret'; 80 | $string['defaultauthmethod'] = 'Default auth method'; 81 | $string['defaultauthmethodhelp'] = 'This is the auth method assigned a new users created by the plugin'; 82 | $string['delegate'] = 'Delegate'; 83 | $string['userprofileupdate'] = 'User profile update'; 84 | $string['userprofileupdatehelp'] = 'Never for not update the user profile on every remote access, Delegate to be configured at tool level'; 85 | $string['rolesallowedcreateresources'] = 'Roles allowed to create resources (from the remote site)'; 86 | $string['rolesallowedcreatecontexts'] = 'Roles allowed to create contexts (from the remote site)'; 87 | $string['cantdeterminecontext'] = 'Can\' determine the context, it seems that there are more than one tool provided for this context_id'; 88 | 89 | $string['invalidtplcourse'] = 'Invalid course template id'; 90 | $string['missingrequiredtool'] = 'For duplicating a resource, you must point the request to an existing resource type course'; 91 | $string['invalidtypetool'] = 'For duplicating a resource, you must point the request to a resource type course'; 92 | $string['invalidresourcecopyid'] = 'Invalid resource to be copied identifier'; 93 | 94 | $string['coursebeingrestored'] = 'This course is being restored, it can take some minutes to finish'; 95 | 96 | $string['membershipsettings'] = 'Memberships service settings'; 97 | $string['enablememberssync'] = 'Enable members synchronization'; 98 | $string['syncperiod'] = 'Synchronization period'; 99 | $string['syncmode'] = 'Synchronization mode'; 100 | $string['enrolandunenrol'] = 'Enrol new and unenrol missing members'; 101 | $string['enrolnew'] = 'Enrol new members'; 102 | $string['unenrolmissing'] = 'Unenrol missing members'; 103 | 104 | $string['idnumberformat'] = 'Idnumber format for new created courses'; 105 | $string['shortnameformat'] = 'Shortname format for new created courses'; 106 | $string['fullnameformat'] = 'Fullname format for new created courses'; 107 | $string['genericformathelp'] = 'For remotely new create courses you can select the remote parameters for creating the name'; 108 | 109 | $string['duplicatecourseswithoutusers'] = 'Duplicate courses without users'; 110 | $string['duplicatecourseswithoutusershelp'] = 'When creating a new course, do not import the users from the template course'; 111 | 112 | $string['subplugintype_ltiproviderextension'] = 'LTI extension'; 113 | $string['subplugintype_ltiproviderextension_plural'] = 'LTI extensions'; 114 | 115 | $string['requirecompletion'] = 'Require course or activity completed before sending the grades'; 116 | $string['errorcompletionenabled'] = 'Completion should be enabled for the course or the activity'; 117 | 118 | $string['enrolinst'] = 'Automatically enrol Instructors'; 119 | $string['enrolinst_help'] = 'Uncheck this box to redirect instructors to the course page so they can select a self-enrolment option'; 120 | $string['enrollearn'] = 'Automatically enrol Learners'; 121 | $string['enrollearn_help'] = 'Uncheck this box to redirect learners to the course page so they can select a self-enrolment option'; 122 | 123 | $string['forcesendgrades'] = 'Force send grades'; 124 | $string['forcesendgradesallusers'] = 'Force send grades for all users'; 125 | $string['forcesendgradesallusersomittingcompletion'] = 'Force send grades for all users omitting completion status'; 126 | $string['gradessent'] = 'Grades sent'; 127 | $string['gradesserviceurl'] = 'Grades service URL'; 128 | $string['gradessourceid'] = 'Grades source Id'; 129 | $string['gradessyncreport'] = 'Grades synchronization report'; 130 | $string['notifycompletion'] = 'This tool is configured to require completion, if the user has not completed the course or activity he won\'t have grade.'; 131 | 132 | $string['addtogroup'] = 'Add users to group (idnumber or request based)'; 133 | $string['addtogroup_help'] = 'Add new users to the indicated group (use the group idnumber). If you want to use or create groups based on a parameter from the request, you must add "request:" before parameter name in the setting field, for example, request:myrememotegroup'; 134 | $string['forcesendgradesselectedusers'] = 'Force send grades for selected users'; 135 | $string['youhavetoselectauser'] = 'Must select at least one user'; 136 | 137 | $string['outcomessettings'] = 'Outcomes service settings'; 138 | $string['sendcompletion'] = 'Send completion status instead grades'; 139 | $string['sendcompletion_help'] = 'If this setting is checked, instead the course grade the completion status will be returned:
140 | 1: When the course or activity is completed
141 | 0: When the course or activity is not completed yet'; 142 | 143 | $string['forcesyncmembers'] = 'Force sync members'; 144 | 145 | $string['nomanualenrol'] = 'Manual enrol method is not enabled'; 146 | 147 | -------------------------------------------------------------------------------- /lib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * General plugin functions. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') or die; 27 | require_once($CFG->dirroot.'/local/ltiprovider/ims-blti/blti_util.php'); 28 | require_once($CFG->dirroot.'/local/ltiprovider/locallib.php'); 29 | 30 | use moodle\local\ltiprovider as ltiprovider; 31 | 32 | /** 33 | * Display the LTI settings in the course settings block 34 | * For 2.3 and onwards 35 | * 36 | * @param settings_navigation $nav The settings navigatin object 37 | * @param stdclass $context Course context 38 | */ 39 | function local_ltiprovider_extend_settings_navigation(settings_navigation $nav, $context) { 40 | if ($context->contextlevel >= CONTEXT_COURSE and ($branch = $nav->get('courseadmin')) 41 | and has_capability('local/ltiprovider:view', $context)) { 42 | $ltiurl = new moodle_url('/local/ltiprovider/index.php', array('courseid' => $context->instanceid)); 43 | $branch->add(get_string('pluginname', 'local_ltiprovider'), $ltiurl, $nav::TYPE_CONTAINER, null, 'ltiprovider'.$context->instanceid); 44 | } 45 | } 46 | 47 | /** 48 | * Change the navigation block and bar only for external users 49 | * Force course or activity navigation and modify CSS also 50 | * Please note that this function is only called in pages where the navigation block is present 51 | * 52 | * @global moodle_user $USER 53 | * @global moodle_database $DB 54 | * @param navigation_node $nav Current navigation object 55 | */ 56 | function local_ltiprovider_extend_navigation ($nav) { 57 | global $CFG, $USER, $PAGE, $SESSION, $ME; 58 | 59 | if (isset($USER) and isset($USER->auth) and strpos($USER->username, 'ltiprovider') === 0) { 60 | // Force course or activity navigation. 61 | if (isset($SESSION->ltiprovider) and $SESSION->ltiprovider->forcenavigation) { 62 | $context = $SESSION->ltiprovider->context; 63 | $urltogo = ''; 64 | if ($context->contextlevel == CONTEXT_COURSE and $PAGE->course->id != $SESSION->ltiprovider->courseid) { 65 | $urltogo = new moodle_url('/course/view.php', array('id' => $SESSION->ltiprovider->courseid)); 66 | } else if ($context->contextlevel == CONTEXT_MODULE and $PAGE->context->id != $context->id) { 67 | $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); 68 | $urltogo = new moodle_url('/mod/'.$cm->modname.'/view.php', array('id' => $cm->id)); 69 | } 70 | 71 | // Special case, user policy, we don't have to do nothing to avoid infinites loops. 72 | if (strpos($ME, 'user/policy.php')) { 73 | return; 74 | } 75 | 76 | if ($urltogo) { 77 | local_ltiprovider_call_hook("navigation", $nav); 78 | if (!$PAGE->requires->is_head_done()) { 79 | $PAGE->set_state($PAGE::STATE_IN_BODY); 80 | } 81 | redirect($urltogo); 82 | } 83 | } 84 | 85 | // Delete all the navigation nodes except the course one. 86 | if ($coursenode = $nav->find($PAGE->course->id, $nav::TYPE_COURSE)) { 87 | foreach (array('myprofile', 'users', 'site', 'home', 'myhome', 'mycourses', 'courses', '1') as $nodekey) { 88 | if ($node = $nav->get($nodekey)) { 89 | $node->remove(); 90 | } 91 | } 92 | $nav->children->add($coursenode); 93 | } 94 | 95 | // Custom CSS. 96 | if (isset($SESSION->ltiprovider) and !$PAGE->requires->is_head_done()) { 97 | $PAGE->requires->css(new moodle_url('/local/ltiprovider/styles.php', array('id' => $SESSION->ltiprovider->id))); 98 | } elseif (isset($SESSION->ltiprovider) && isset($SESSION->ltiprovider->id)) { 99 | $url = new moodle_url('/local/ltiprovider/styles.js.php', 100 | array('id' => $SESSION->ltiprovider->id, 'rand' => rand(0, 1000))); 101 | $PAGE->requires->js($url); 102 | } 103 | 104 | local_ltiprovider_call_hook("navigation", $nav); 105 | } 106 | } 107 | 108 | /** 109 | * Add new tool. 110 | * 111 | * @param object $tool 112 | * @return int 113 | */ 114 | function local_ltiprovider_add_tool($tool) { 115 | global $DB; 116 | 117 | if (!isset($tool->disabled)) { 118 | $tool->disabled = 0; 119 | } 120 | if (!isset($tool->timecreated)) { 121 | $tool->timecreated = time(); 122 | } 123 | if (!isset($tool->timemodified)) { 124 | $tool->timemodified = $tool->timecreated; 125 | } 126 | 127 | if (!isset($tool->sendgrades)) { 128 | $tool->sendgrades = 0; 129 | } 130 | if (!isset($tool->forcenavigation)) { 131 | $tool->forcenavigation = 0; 132 | } 133 | if (!isset($tool->enrolinst)) { 134 | $tool->enrolinst = 0; 135 | } 136 | if (!isset($tool->enrollearn)) { 137 | $tool->enrollearn = 0; 138 | } 139 | if (!isset($tool->hidepageheader)) { 140 | $tool->hidepageheader = 0; 141 | } 142 | if (!isset($tool->hidepagefooter)) { 143 | $tool->hidepagefooter = 0; 144 | } 145 | if (!isset($tool->hideleftblocks)) { 146 | $tool->hideleftblocks = 0; 147 | } 148 | if (!isset($tool->hiderightblocks)) { 149 | $tool->hiderightblocks = 0; 150 | } 151 | if (!isset($tool->syncmembers)) { 152 | $tool->syncmembers = 0; 153 | } 154 | 155 | $tool->id = $DB->insert_record('local_ltiprovider', $tool); 156 | local_ltiprovider_call_hook('save_settings', $tool); 157 | 158 | return $tool->id; 159 | } 160 | 161 | /** 162 | * Update existing tool. 163 | * @param object $tool 164 | * @return void 165 | */ 166 | function local_ltiprovider_update_tool($tool) { 167 | global $DB; 168 | 169 | $tool->timemodified = time(); 170 | 171 | if (!isset($tool->sendgrades)) { 172 | $tool->sendgrades = 0; 173 | } 174 | if (!isset($tool->forcenavigation)) { 175 | $tool->forcenavigation = 0; 176 | } 177 | if (!isset($tool->enrolinst)) { 178 | $tool->enrolinst = 0; 179 | } 180 | if (!isset($tool->enrollearn)) { 181 | $tool->enrollearn = 0; 182 | } 183 | if (!isset($tool->hidepageheader)) { 184 | $tool->hidepageheader = 0; 185 | } 186 | if (!isset($tool->hidepagefooter)) { 187 | $tool->hidepagefooter = 0; 188 | } 189 | if (!isset($tool->hideleftblocks)) { 190 | $tool->hideleftblocks = 0; 191 | } 192 | if (!isset($tool->hiderightblocks)) { 193 | $tool->hiderightblocks = 0; 194 | } 195 | if (!isset($tool->syncmembers)) { 196 | $tool->syncmembers = 0; 197 | } 198 | 199 | local_ltiprovider_call_hook('save_settings', $tool); 200 | $DB->update_record('local_ltiprovider', $tool); 201 | } 202 | 203 | /** 204 | * Delete tool. 205 | * @param object $tool 206 | * @return void 207 | */ 208 | function local_ltiprovider_delete_tool($tool) { 209 | global $DB; 210 | $DB->delete_records('local_ltiprovider_user', array('toolid' => $tool->id)); 211 | $DB->delete_records('local_ltiprovider', array('id' => $tool->id)); 212 | } 213 | 214 | /** 215 | * Checks if a course linked to a tool is missing, is so, delete the lti entries 216 | * @param stdclass $tool Tool record 217 | * @return bool True if the course was missing 218 | */ 219 | function local_ltiprovider_check_missing_course($tool) { 220 | global $DB; 221 | 222 | if (! $course = $DB->get_record('course', array('id' => $tool->courseid))) { 223 | $DB->delete_records('local_ltiprovider', array('courseid' => $tool->courseid)); 224 | $DB->delete_records('local_ltiprovider_user', array('toolid' => $tool->id)); 225 | mtrace("Tool: $tool->id deleted (courseid: $tool->courseid missing)"); 226 | return true; 227 | } 228 | return false; 229 | } 230 | 231 | /** 232 | * Cron function for sync grades 233 | * @return void 234 | */ 235 | function local_ltiprovider_cron() { 236 | global $DB, $CFG; 237 | require_once($CFG->dirroot."/local/ltiprovider/locallib.php"); 238 | require_once($CFG->dirroot."/local/ltiprovider/ims-blti/OAuth.php"); 239 | require_once($CFG->dirroot."/local/ltiprovider/ims-blti/OAuthBody.php"); 240 | require_once($CFG->libdir.'/gradelib.php'); 241 | require_once($CFG->dirroot.'/grade/querylib.php'); 242 | 243 | // TODO - Add a global setting for this 244 | $synctime = 60*60; // Every 1 hour grades are sync 245 | $timenow = time(); 246 | 247 | mtrace('Running cron for ltiprovider'); 248 | 249 | mtrace('Deleting LTI tools assigned to deleted courses'); 250 | if ($tools = $DB->get_records('local_ltiprovider')) { 251 | foreach ($tools as $tool) { 252 | local_ltiprovider_check_missing_course($tool); 253 | } 254 | } 255 | 256 | // Grades service. 257 | if ($tools = $DB->get_records_select('local_ltiprovider', 'disabled = ? AND sendgrades = ?', array(0, 1))) { 258 | foreach ($tools as $tool) { 259 | if ($tool->lastsync + $synctime < $timenow) { 260 | mtrace(" Starting sync tool for grades id $tool->id course id $tool->courseid"); 261 | if ($tool->requirecompletion) { 262 | mtrace(" Grades require activity or course completion"); 263 | } 264 | $user_count = 0; 265 | $send_count = 0; 266 | $error_count = 0; 267 | 268 | $completion = new completion_info(get_course($tool->courseid)); 269 | 270 | if ($users = $DB->get_records('local_ltiprovider_user', array('toolid' => $tool->id))) { 271 | foreach ($users as $user) { 272 | 273 | $data = array( 274 | 'tool' => $tool, 275 | 'user' => $user, 276 | ); 277 | local_ltiprovider_call_hook('grades', (object) $data); 278 | 279 | $user_count = $user_count + 1; 280 | // This can happen is the sync process has an unexpected error 281 | if ( strlen($user->serviceurl) < 1 ) { 282 | mtrace(" Empty serviceurl"); 283 | continue; 284 | } 285 | if ( strlen($user->sourceid) < 1 ) { 286 | mtrace(" Empty sourceid"); 287 | continue; 288 | } 289 | 290 | if ($user->lastsync > $tool->lastsync) { 291 | mtrace(" Skipping user {$user->id} due to recent sync"); 292 | continue; 293 | } 294 | 295 | $grade = false; 296 | if ($context = $DB->get_record('context', array('id' => $tool->contextid))) { 297 | if ($context->contextlevel == CONTEXT_COURSE) { 298 | 299 | if ($tool->requirecompletion and !$completion->is_course_complete($user->userid)) { 300 | mtrace(" Skipping user $user->userid since he didn't complete the course"); 301 | continue; 302 | } 303 | 304 | if ($tool->sendcompletion) { 305 | $grade = $completion->is_course_complete($user->userid) ? 1 : 0; 306 | $grademax = 1; 307 | } else if ($grade = grade_get_course_grade($user->userid, $tool->courseid)) { 308 | $grademax = floatval($grade->item->grademax); 309 | $grade = $grade->grade; 310 | } 311 | } else if ($context->contextlevel == CONTEXT_MODULE) { 312 | $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); 313 | 314 | if ($tool->requirecompletion) { 315 | $data = $completion->get_data($cm, false, $user->userid); 316 | if ($data->completionstate != COMPLETION_COMPLETE_PASS and $data->completionstate != COMPLETION_COMPLETE) { 317 | mtrace(" Skipping user $user->userid since he didn't complete the activity"); 318 | continue; 319 | } 320 | } 321 | 322 | if ($tool->sendcompletion) { 323 | $data = $completion->get_data($cm, false, $user->userid); 324 | if ($data->completionstate == COMPLETION_COMPLETE_PASS || 325 | $data->completionstate == COMPLETION_COMPLETE || 326 | $data->completionstate == COMPLETION_COMPLETE_FAIL) { 327 | $grade = 1; 328 | } else { 329 | $grade = 0; 330 | } 331 | $grademax = 1; 332 | } else { 333 | $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, $user->userid); 334 | if (empty($grades->items[0]->grades)) { 335 | $grade = false; 336 | } else { 337 | $grade = reset($grades->items[0]->grades); 338 | if (!empty($grade->item)) { 339 | $grademax = floatval($grade->item->grademax); 340 | } else { 341 | $grademax = floatval($grades->items[0]->grademax); 342 | } 343 | $grade = $grade->grade; 344 | } 345 | } 346 | } 347 | 348 | if ( $grade === false || $grade === NULL || strlen($grade) < 1) { 349 | mtrace(" Invalid grade $grade"); 350 | continue; 351 | } 352 | 353 | // No need to be dividing by zero 354 | if ( $grademax == 0.0 ) $grademax = 100.0; 355 | 356 | // TODO: Make lastgrade should be float or string - but it is integer so we truncate 357 | // TODO: Then remove those intval() calls 358 | 359 | // Don't double send 360 | if ( intval($grade) == $user->lastgrade ) { 361 | mtrace(" Skipping, last grade send is equal to current grade"); 362 | continue; 363 | } 364 | 365 | // We sync with the external system only when the new grade differs with the previous one 366 | // TODO - Global setting for check this 367 | if ($grade >= 0 and $grade <= $grademax) { 368 | $float_grade = $grade / $grademax; 369 | $body = local_ltiprovider_create_service_body($user->sourceid, $float_grade); 370 | 371 | try { 372 | $response = ltiprovider\sendOAuthBodyPOST('POST', $user->serviceurl, $user->consumerkey, $user->consumersecret, 'application/xml', $body); 373 | } catch (Exception $e) { 374 | mtrace(" ".$e->getMessage()); 375 | $error_count = $error_count + 1; 376 | continue; 377 | } 378 | 379 | // TODO - Check for errors in $retval in a correct way (parsing xml) 380 | if (strpos(strtolower($response), 'success') !== false) { 381 | 382 | $DB->set_field('local_ltiprovider_user', 'lastsync', $timenow, array('id' => $user->id)); 383 | $DB->set_field('local_ltiprovider_user', 'lastgrade', intval($grade), array('id' => $user->id)); 384 | mtrace(" User grade sent to remote system. userid: $user->userid grade: $float_grade"); 385 | $send_count = $send_count + 1; 386 | } else { 387 | mtrace(" User grade send failed. userid: $user->userid grade: $float_grade: " . $response); 388 | $error_count = $error_count + 1; 389 | } 390 | } else { 391 | mtrace(" User grade for user $user->userid out of range: grade = ".$grade); 392 | $error_count = $error_count + 1; 393 | } 394 | } else { 395 | mtrace(" Invalid context: contextid = ".$tool->contextid); 396 | } 397 | } 398 | } 399 | mtrace(" Completed sync tool id $tool->id course id $tool->courseid users=$user_count sent=$send_count errors=$error_count"); 400 | $DB->set_field('local_ltiprovider', 'lastsync', $timenow, array('id' => $tool->id)); 401 | } 402 | } 403 | } 404 | 405 | $timenow = time(); 406 | // Automatic course restaurations. 407 | if ($croncourses = get_config('local_ltiprovider', 'croncourses')) { 408 | $croncourses = unserialize($croncourses); 409 | if (is_array($croncourses)) { 410 | mtrace('Starting restauration of pending courses'); 411 | 412 | foreach ($croncourses as $key => $course) { 413 | mtrace('Starting restoration of ' . $key); 414 | 415 | // We limit the backups to 1 hour, then retry. 416 | if ($course->restorestart and ($timenow < $course->restorestart + 3600)) { 417 | mtrace('Skipping restoration in process for: ' . $key); 418 | continue; 419 | } 420 | 421 | $course->restorestart = time(); 422 | $croncourses[$key] = $course; 423 | $croncoursessafe = serialize($croncourses); 424 | set_config('croncourses', $croncoursessafe, 'local_ltiprovider'); 425 | 426 | if ($destinationcourse = $DB->get_record('course', array('id' => $course->destinationid))) { 427 | // Duplicate course + users. 428 | local_ltiprovider_duplicate_course($course->id, $destinationcourse, 1, 429 | $options = array(array('name' => 'users', 430 | 'value' => 1)), 431 | $course->userrestoringid, $course->context); 432 | mtrace('Restoration for ' .$key. ' finished'); 433 | } else { 434 | mtrace('Restoration for ' .$key. ' finished (destination course not exists)'); 435 | } 436 | 437 | unset($croncourses[$key]); 438 | $croncoursessafe = serialize($croncourses); 439 | set_config('croncourses', $croncoursessafe, 'local_ltiprovider'); 440 | } 441 | } 442 | } 443 | 444 | // Membership service. 445 | $timenow = time(); 446 | $userphotos = array(); 447 | 448 | if ($tools = $DB->get_records('local_ltiprovider', array('disabled' => 0, 'syncmembers' => 1))) { 449 | mtrace('Starting sync of member using the memberships service'); 450 | $consumers = array(); 451 | 452 | foreach ($tools as $tool) { 453 | $lastsync = get_config('local_ltiprovider', 'membershipslastsync-' . $tool->id); 454 | if (!$lastsync) { 455 | $lastsync = 0; 456 | } 457 | if ($lastsync + $tool->syncperiod < $timenow) { 458 | $ret = local_ltiprovider_membership_service($tool, $timenow, $userphotos, $consumers); 459 | $userphotos = $ret['userphotos']; 460 | $consumers = $ret['consumers']; 461 | } else { 462 | $last = format_time((time() - $lastsync)); 463 | mtrace("Tool $tool->id synchronized $last ago"); 464 | } 465 | mtrace('Finished sync of member using the memberships service'); 466 | } 467 | } 468 | 469 | local_ltiprovider_membership_service_update_userphotos($userphotos); 470 | 471 | } 472 | 473 | /** 474 | * Call a hook present in a subplugin 475 | * 476 | * @param string $hookname The hookname (function without franken style prefix) 477 | * @param object $data Object containing data to be used by the hook function 478 | * @return bool Allways false 479 | */ 480 | function local_ltiprovider_call_hook($hookname, $data) { 481 | $plugins = get_plugin_list_with_function('ltiproviderextension', $hookname); 482 | if (!empty($plugins)) { 483 | foreach ($plugins as $plugin) { 484 | call_user_func($plugin, $data); 485 | } 486 | } 487 | return false; 488 | } 489 | -------------------------------------------------------------------------------- /modinfo/chat.php: -------------------------------------------------------------------------------- 1 | time()); 5 | 6 | -------------------------------------------------------------------------------- /modinfo/forum.php: -------------------------------------------------------------------------------- 1 | 0, 5 | "availableuntil" => 0, 6 | "showavailability" => 0); 7 | 8 | -------------------------------------------------------------------------------- /modinfo/quiz.php: -------------------------------------------------------------------------------- 1 | ""); 5 | 6 | -------------------------------------------------------------------------------- /services.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Main entry for extra services request. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | require_once($CFG->dirroot.'/local/ltiprovider/locallib.php'); 28 | require_once($CFG->dirroot.'/local/ltiprovider/ims-blti/blti.php'); 29 | 30 | $service = required_param('custom_service', PARAM_RAW_TRIMMED); 31 | $toolid = optional_param('id', 0, PARAM_INT); 32 | $lticontextid = optional_param('context_id', false, PARAM_RAW); 33 | 34 | if (isset($newinfo['custom_lti_message_encoded_base64']) and $newinfo['custom_lti_message_encoded_base64'] == 1) { 35 | $lticontextid = base64_decode($lticontextid); 36 | $service = base64_decode($service); 37 | } 38 | 39 | if (!$toolid and $lticontextid) { 40 | // Check if there is more that one course for this LTI context id. 41 | if ($DB->count_records('course', array('idnumber' => $lticontextid)) > 1) { 42 | print_error('cantdeterminecontext', 'local_ltiprovider'); 43 | } 44 | 45 | if ($course = $DB->get_record('course', array('idnumber' => $lticontextid))) { 46 | // Look for a course created for this LTI context id. 47 | if ($coursecontext = context_course::instance($course->id)) { 48 | if ($DB->count_records('local_ltiprovider', array('contextid' => $coursecontext->id)) > 1) { 49 | print_error('cantdeterminecontext', 'local_ltiprovider'); 50 | } 51 | $toolid = $DB->get_field('local_ltiprovider', 'id', array('contextid' => $coursecontext->id)); 52 | } 53 | } 54 | } 55 | 56 | $secret = ''; 57 | // If we dont receive a request for a specific tool, we use the global shared secret. 58 | if ($tool = $DB->get_record('local_ltiprovider', array('id' => $toolid))) { 59 | if ($tool->disabled) { 60 | print_error('tooldisabled', 'local_ltiprovider'); 61 | } 62 | $secret = $tool->secret; 63 | } else { 64 | $secret = get_config('local_ltiprovider', 'globalsharedsecret'); 65 | } 66 | 67 | if (!$secret) { 68 | print_error('invalidtoolid', 'local_ltiprovider'); 69 | } 70 | 71 | // Do not set session, do not redirect. 72 | $context = new BLTI($secret, false, false); 73 | 74 | // Correct launch request. 75 | if ($context->valid) { 76 | 77 | set_time_limit(0); 78 | raise_memory_limit(MEMORY_EXTRA); 79 | 80 | // Are we creating a new context (that means a new course tool)? 81 | if ($service == 'create_context') { 82 | $custom_context_template = $context->info['custom_context_template']; 83 | 84 | if (!$tplcourse = $DB->get_record('course', array('idnumber' => $custom_context_template), '*', IGNORE_MULTIPLE)) { 85 | print_error('invalidtplcourse', 'local_ltiprovider'); 86 | } 87 | 88 | require_once("$CFG->dirroot/course/lib.php"); 89 | $newcourse = new stdClass(); 90 | $newcourse->fullname = local_ltiprovider_get_new_course_info('fullname', $context); 91 | $newcourse->shortname = local_ltiprovider_get_new_course_info('shortname', $context); 92 | $newcourse->idnumber = local_ltiprovider_get_new_course_info('idnumber', $context); 93 | 94 | $categories = $DB->get_records('course_categories', null, '', 'id', 0, 1); 95 | $category = array_shift($categories); 96 | $newcourse->category = $category->id; 97 | 98 | $course = create_course($newcourse); 99 | 100 | $coursecontext = context_course::instance($course->id); 101 | 102 | // Create the tool that provide the full course. 103 | $tool = local_ltiprovider_create_tool($course->id, $coursecontext->id, $context); 104 | 105 | $username = local_ltiprovider_create_username($context->info['oauth_consumer_key'], $context->info['user_id']); 106 | $userrestoringid = $DB->get_field('user', 'id', array('username' => $username)); 107 | 108 | // Duplicate course + users. 109 | $course = local_ltiprovider_duplicate_course($tplcourse->id, $course, 1, 110 | $options = array(array('name' => 'users', 111 | 'value' => 1)), $userrestoringid, $context); 112 | echo json_encode($course); 113 | 114 | } else if ($service == 'duplicate_resource') { 115 | $idnumber = $context->info['custom_resource_link_copy_id']; 116 | $resource_link_id = $context->info['resource_link_id']; 117 | 118 | if (!$tool) { 119 | print_error('missingrequiredtool', 'local_ltiprovider'); 120 | } 121 | 122 | if (! $context = $DB->get_record('context', array('id' => $tool->contextid))) { 123 | print_error("invalidcontext"); 124 | } 125 | 126 | if ($context->contextlevel != CONTEXT_COURSE) { 127 | print_error('invalidtypetool', 'local_ltiprovider'); 128 | } 129 | 130 | if (!$cm = $DB->get_record('course_modules', array('idnumber' => $idnumber), '*', IGNORE_MULTIPLE)) { 131 | print_error('invalidresourcecopyid', 'local_ltiprovider'); 132 | } 133 | 134 | $courseid = $context->instanceid; 135 | 136 | $cmid = local_ltiprovider_duplicate_module($cm->id, $courseid, $resource_link_id, $context); 137 | if ($cm = get_coursemodule_from_id(false, $cmid)) { 138 | echo json_encode($cm); 139 | } 140 | } else if ($service == 'force_logout') { 141 | // Force logout. 142 | $authsequence = get_enabled_auth_plugins(); 143 | foreach ($authsequence as $authname) { 144 | $authplugin = get_auth_plugin($authname); 145 | $authplugin->logoutpage_hook(); 146 | } 147 | 148 | require_logout(); 149 | } 150 | 151 | } else { 152 | echo $context->message; 153 | } 154 | -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * General plugin functions. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | if ($hassiteconfig) { // needs this condition or there is error on login page 29 | 30 | $ADMIN->add('root', new admin_category('ltiprovider', get_string('pluginname', 'local_ltiprovider'))); 31 | $ADMIN->add('ltiprovider', new admin_externalpage('ltiprovidersettings', get_string('settings'), 32 | $CFG->wwwroot.'/admin/settings.php?section=local_ltiprovider', 'local/ltiprovider:manage')); 33 | 34 | $settings = new admin_settingpage('local_ltiprovider', 'LTI Provider'); 35 | $ADMIN->add('localplugins', $settings); 36 | 37 | $settings->add(new admin_setting_configtext('local_ltiprovider/globalsharedsecret', 38 | get_string('globalsharedsecret', 'local_ltiprovider'), '', '', PARAM_RAW_TRIMMED)); 39 | 40 | $options = array(-1 => get_string('delegate', 'local_ltiprovider'), 0 => get_string('never'), 1 => get_string('always')); 41 | $settings->add(new admin_setting_configselect('local_ltiprovider/userprofileupdate', get_string('userprofileupdate', 42 | 'local_ltiprovider'), get_string('userprofileupdatehelp', 'local_ltiprovider'), 1, $options)); 43 | 44 | $auths = get_plugin_list('auth'); 45 | $authmethods = array(); 46 | foreach ($auths as $auth => $unused) { 47 | if (is_enabled_auth($auth)) { 48 | $authmethods[$auth] = get_string('pluginname', "auth_{$auth}"); 49 | } 50 | } 51 | $settings->add(new admin_setting_configselect('local_ltiprovider/defaultauthmethod', get_string('defaultauthmethod', 52 | 'local_ltiprovider'), get_string('defaultauthmethodhelp', 'local_ltiprovider'), 'manual', $authmethods)); 53 | 54 | $options = array('context_id', 'context_title' , 'context_label', 'consumer_key : context_id', 'consumer_key : context_title' , 'consumer_key : context_label'); 55 | 56 | $settings->add(new admin_setting_configselect('local_ltiprovider/fullnameformat', get_string('fullnameformat', 57 | 'local_ltiprovider'), get_string('genericformathelp', 'local_ltiprovider'), 1, $options)); 58 | 59 | $settings->add(new admin_setting_configselect('local_ltiprovider/shortnameformat', get_string('shortnameformat', 60 | 'local_ltiprovider'), get_string('genericformathelp', 'local_ltiprovider'), 2, $options)); 61 | 62 | $settings->add(new admin_setting_configselect('local_ltiprovider/idnumberformat', get_string('idnumberformat', 63 | 'local_ltiprovider'), get_string('genericformathelp', 'local_ltiprovider'), 0, $options)); 64 | 65 | $settings->add(new admin_setting_configcheckbox('local_ltiprovider/duplicatecourseswithoutusers', get_string('duplicatecourseswithoutusers', 'local_ltiprovider'), 66 | get_string('duplicatecourseswithoutusershelp', 'local_ltiprovider'), 0)); 67 | 68 | $settings->add(new admin_setting_configmultiselect('local_ltiprovider/rolesallowedcreatecontexts', get_string('rolesallowedcreatecontexts', 'local_ltiprovider'), 69 | '', array('Administrator'), 70 | array( 71 | 'Student' => 'Student', 72 | 'Faculty' => 'Faculty', 73 | 'Member' => 'Member', 74 | 'Learner' => 'Learner', 75 | 'Instructor' => 'Instructor', 76 | 'Mentor' => 'Mentor', 77 | 'Staff' => 'Staff', 78 | 'Alumni' => 'Alumni', 79 | 'ProspectiveStudent' => 'ProspectiveStudent', 80 | 'Guest' => 'Guest', 81 | 'Other' => 'Other', 82 | 'Administrator' => 'Administrator', 83 | 'Observer' => 'Observer', 84 | 'None' => 'None' 85 | ))); 86 | 87 | $settings->add(new admin_setting_configmultiselect('local_ltiprovider/rolesallowedcreateresources', get_string('rolesallowedcreateresources', 'local_ltiprovider'), 88 | '', array('Administrator'), 89 | array( 90 | 'Student' => 'Student', 91 | 'Faculty' => 'Faculty', 92 | 'Member' => 'Member', 93 | 'Learner' => 'Learner', 94 | 'Instructor' => 'Instructor', 95 | 'Mentor' => 'Mentor', 96 | 'Staff' => 'Staff', 97 | 'Alumni' => 'Alumni', 98 | 'ProspectiveStudent' => 'ProspectiveStudent', 99 | 'Guest' => 'Guest', 100 | 'Other' => 'Other', 101 | 'Administrator' => 'Administrator', 102 | 'Observer' => 'Observer', 103 | 'None' => 'None' 104 | ))); 105 | } -------------------------------------------------------------------------------- /styles.js.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Custom styles for a tool. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | 28 | $toolid = required_param('id', PARAM_INT); 29 | 30 | $url = $CFG->wwwroot . "/local/ltiprovider/styles.php?id=$toolid"; 31 | ?> 32 | 33 | function local_ltiprovider_loadjscssfile(){ 34 | var url = ""; 35 | var fileref=document.createElement("link"); 36 | fileref.setAttribute("rel", "stylesheet"); 37 | fileref.setAttribute("type", "text/css"); 38 | fileref.setAttribute("href", url); 39 | var head = document.getElementsByTagName("head"); 40 | if (head) { 41 | head[0].appendChild(fileref) 42 | } 43 | } 44 | 45 | // Waiting DOM ready (hide effect). 46 | YUI().use('node', function(Y) { 47 | Y.on("domready", function(){ 48 | local_ltiprovider_loadjscssfile(); 49 | }); 50 | }); 51 | 52 | // Without waiting. 53 | local_ltiprovider_loadjscssfile(); -------------------------------------------------------------------------------- /styles.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Custom styles for a tool. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | 28 | $toolid = required_param('id', PARAM_INT); 29 | 30 | if (! ($tool = $DB->get_record('local_ltiprovider', array('id'=>$toolid)))) { 31 | print_error('invalidtoolid', 'local_ltiprovider'); 32 | } 33 | 34 | if ($tool->disabled) { 35 | print_error('tooldisabled', 'local_ltiprovider'); 36 | } 37 | 38 | $lifetime = 60*60*24*3; 39 | 40 | $css = ''; 41 | 42 | if ($tool->hidepageheader or $SESSION->ltiprovider->hidepageheader) { 43 | $css .= ' 44 | #page-header{ 45 | display: none; 46 | } 47 | 48 | header.navbar { 49 | display: none; 50 | } 51 | '; 52 | } 53 | if ($tool->hidepagefooter or $SESSION->ltiprovider->hidepagefooter) { 54 | $css .= ' 55 | #page-footer{ 56 | display: none; 57 | } 58 | '; 59 | } 60 | if ($tool->hideleftblocks or $SESSION->ltiprovider->hideleftblocks) { 61 | $css .= ' 62 | #region-pre .block, #block-region-side-pre .block{ 63 | display: none; 64 | } 65 | #mod_quiz_navblock { 66 | display: block !important; 67 | } 68 | .empty-region-side-post.used-region-side-pre #region-main.span8 { 69 | width: inherit; 70 | } 71 | '; 72 | } 73 | if ($tool->hiderightblocks or $SESSION->ltiprovider->hiderightblocks) { 74 | $css .= ' 75 | #region-post, #block-region-side-post { 76 | display: none; 77 | } 78 | '; 79 | } 80 | 81 | if ($tool->customcss or $SESSION->ltiprovider->customcss) { 82 | 83 | $css .= $tool->customcss; 84 | 85 | if ($SESSION->ltiprovider->customcss and $SESSION->ltiprovider->customcss != $tool->customcss) { 86 | $css .= $SESSION->ltiprovider->customcss; 87 | } 88 | } 89 | 90 | header('Content-Disposition: inline; filename="styles.php"'); 91 | header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT'); 92 | header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); 93 | header('Pragma: '); 94 | header('Accept-Ranges: none'); 95 | header('Content-Type: text/css; charset=utf-8'); 96 | header('Content-Length: '.strlen($css)); 97 | 98 | echo $css; 99 | die; 100 | -------------------------------------------------------------------------------- /syncmembers.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Force to sync members of current tool 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2017 Antoni Bertran Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | require_once(dirname(__FILE__) . '/lib.php'); 28 | 29 | $id = required_param('id', PARAM_INT); 30 | 31 | $tool = $DB->get_record('local_ltiprovider', array('id' => $id), '*', MUST_EXIST); 32 | $courseid = $tool->courseid; 33 | $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); 34 | 35 | $params_url = array('id' => $id); 36 | 37 | $PAGE->set_url('/local/ltiprovider/syncmembers.php', $params_url ); 38 | 39 | $context = context_course::instance($course->id); 40 | 41 | require_login($course); 42 | require_capability('local/ltiprovider:manage', $context); 43 | 44 | $returnurl = new moodle_url('/local/ltiprovider/index.php', array('courseid' => $courseid)); 45 | 46 | $strheading = get_string('forcesyncmembers', 'local_ltiprovider'); 47 | $PAGE->set_context($context); 48 | $PAGE->navbar->add(get_string('pluginname', 'local_ltiprovider'), new moodle_url('/local/ltiprovider/index.php', array('courseid'=>$course->id))); 49 | $PAGE->navbar->add($strheading); 50 | $PAGE->set_title($strheading); 51 | $PAGE->set_heading($course->fullname . ': '.$strheading); 52 | 53 | echo $OUTPUT->header(); 54 | 55 | $userphotos = array(); 56 | $consumers = array(); 57 | $timenow = time(); 58 | $ret = local_ltiprovider_membership_service($tool, $timenow, $userphotos, $consumers); 59 | echo '

Response =>

'.htmlentities($ret['response']).'

'; 60 | 61 | $userphotos = $ret['userphotos']; 62 | local_ltiprovider_membership_service_update_userphotos($userphotos); 63 | 64 | echo $OUTPUT->footer(); 65 | -------------------------------------------------------------------------------- /syncreport.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Edit a tool provided in a course 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2016 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | require_once($CFG->dirroot.'/local/ltiprovider/classes/table_syncreport.php'); 28 | 29 | $id = required_param('id', PARAM_INT); 30 | $search_firstname = optional_param('search_firstname', '', PARAM_TEXT); 31 | $search_lastname = optional_param('search_lastname', '', PARAM_TEXT); 32 | $sifirst = optional_param('sifirst', null, PARAM_NOTAGS); 33 | $silast = optional_param('silast', null, PARAM_NOTAGS); 34 | 35 | 36 | $tool = $DB->get_record('local_ltiprovider', array('id' => $id), '*', MUST_EXIST); 37 | $courseid = $tool->courseid; 38 | $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); 39 | 40 | $params_url = array('id' => $id); 41 | if (!empty($search_firstname)) { 42 | $params_url['search_firstname'] = $search_firstname; 43 | } 44 | if (!empty($search_surname)) { 45 | $params_url['search_surname'] = $search_surname; 46 | } 47 | if (!empty($sifirst)) { 48 | $params_url['sifirst'] = $sifirst; 49 | } 50 | if (!empty($silast)) { 51 | $params_url['silast'] = $silast; 52 | } 53 | 54 | $PAGE->set_url('/local/ltiprovider/syncreport.php', $params_url ); 55 | 56 | $context = context_course::instance($course->id); 57 | 58 | require_login($course); 59 | require_capability('local/ltiprovider:manage', $context); 60 | 61 | $returnurl = new moodle_url('/local/ltiprovider/index.php', array('courseid' => $courseid)); 62 | 63 | $strheading = get_string('gradessyncreport', 'local_ltiprovider'); 64 | $PAGE->set_context($context); 65 | $PAGE->navbar->add(get_string('pluginname', 'local_ltiprovider'), new moodle_url('/local/ltiprovider/index.php', array('courseid'=>$course->id))); 66 | $PAGE->navbar->add($strheading); 67 | $PAGE->set_title($strheading); 68 | $PAGE->set_heading($course->fullname . ': '.$strheading); 69 | $PAGE->requires->jquery(); 70 | $PAGE->requires->js('/local/ltiprovider/js/syncreport.js'); 71 | $PAGE->requires->strings_for_js(array('youhavetoselectauser'), 'local_ltiprovider'); 72 | 73 | echo $OUTPUT->header(); 74 | echo $OUTPUT->heading(get_string('gradessent', 'local_ltiprovider')); 75 | 76 | if ($tool->requirecompletion) { 77 | echo $OUTPUT->notification(get_string('notifycompletion', 'local_ltiprovider'), 'notifymessage'); 78 | } 79 | 80 | $filterparams = new stdClass(); 81 | $filterparams->firstname = $search_firstname; 82 | $filterparams->sifirst = $sifirst; 83 | $filterparams->lastname = $search_lastname; 84 | $filterparams->silast = $silast; 85 | $table = new local_ltiprovider_table_syncreport('local_ltiprovider_syncreport', $tool, $filterparams); 86 | echo $table->syncreport_search_form($id, $search_firstname, $search_lastname, $sifirst, $silast); 87 | 88 | $table->out(50, true); 89 | 90 | //Button to send grade to selected users 91 | $forcesendurl_participant = new \moodle_url('test/forcesendgrades.php', array('toolid' => $tool->id, 92 | 'printresponse' => 1, 'selected_users' => 1, 'user_force_grade' => '')); 93 | $action = new component_action('click', 'ltiprovider_send_syncreport'); 94 | echo $OUTPUT->single_button($forcesendurl_participant, get_string('forcesendgradesselectedusers', 'local_ltiprovider'), 95 | 'post', array('actions' => array($action), 'formid' => 'ltiprovider_send_syncreport_form')); 96 | 97 | //Button to send grade to all users 98 | $forcesendurl = new \moodle_url('test/forcesendgrades.php', array('toolid' => $tool->id, 'printresponse' => 1)); 99 | echo $OUTPUT->single_button($forcesendurl, get_string('forcesendgradesallusers', 'local_ltiprovider')); 100 | 101 | if ($tool->requirecompletion) { 102 | $forcesendurl = new \moodle_url('test/forcesendgrades.php', array('toolid' => $tool->id, 'printresponse' => 1, 'omitcompletion' => 1)); 103 | echo $OUTPUT->single_button($forcesendurl, get_string('forcesendgradesallusersomittingcompletion', 'local_ltiprovider')); 104 | } 105 | 106 | if (!$table->is_downloading()) { 107 | echo $OUTPUT->footer(); 108 | } 109 | -------------------------------------------------------------------------------- /test/cron.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Launch destination url. Main entry point for the external system. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../../config.php'); 27 | 28 | require_login(); 29 | require_capability('moodle/site:config', context_system::instance()); 30 | 31 | require_once('../lib.php'); 32 | 33 | if ($tools = $DB->get_records('local_ltiprovider', array('disabled' => 0))) { 34 | foreach ($tools as $tool) { 35 | set_config('membershipslastsync-' . $tool->id, 0, 'local_ltiprovider'); 36 | $tool->lastsync = 0; 37 | $DB->update_record('local_ltiprovider', $tool); 38 | } 39 | } 40 | 41 | if ($users = $DB->get_records('local_ltiprovider_user')) { 42 | foreach ($users as $user) { 43 | $user->lastsync = 0; 44 | $DB->update_record('local_ltiprovider_user', $user); 45 | } 46 | } 47 | 48 | @header('Content-Type: text/plain; charset=utf-8'); 49 | 50 | $start = time(); 51 | echo "Starting LTI provider cron"; 52 | local_ltiprovider_cron(); 53 | $end = time(); 54 | echo "LTI provider cron finished, duration: " . ($end - $start) . " secs"; 55 | -------------------------------------------------------------------------------- /test/forcesendgrades.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Force send grades back (overwritten). You can force by course, tool and userid (paremeters $courseid, $toolid, $userid) 19 | * Completion check can be omitted too ($omitcompletion) 20 | * 21 | * @package local 22 | * @subpackage ltiprovider 23 | * @copyright 2011 Juan Leyva 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | require_once(dirname(__FILE__) . '/../../../config.php'); 28 | 29 | require_once($CFG->dirroot . "/local/ltiprovider/lib.php"); 30 | require_once($CFG->dirroot . "/local/ltiprovider/locallib.php"); 31 | require_once($CFG->dirroot . "/local/ltiprovider/ims-blti/OAuth.php"); 32 | require_once($CFG->dirroot . "/local/ltiprovider/ims-blti/OAuthBody.php"); 33 | require_once($CFG->libdir . "/gradelib.php"); 34 | require_once($CFG->dirroot . "/grade/querylib.php"); 35 | 36 | use moodle\local\ltiprovider as ltiprovider; 37 | 38 | ini_set("display_erros", 1); 39 | error_reporting(E_ALL); 40 | 41 | $courseid = optional_param('courseid', 0, PARAM_INT); 42 | $toolid = optional_param('toolid', 0, PARAM_INT); 43 | $userid = optional_param('userid', 0, PARAM_INT); 44 | $omitcompletion = optional_param('omitcompletion', 0, PARAM_BOOL); 45 | $printresponse = optional_param('printresponse', 0, PARAM_BOOL); 46 | $sesskey = optional_param('sesskey', null, PARAM_RAW); 47 | $user_force_grade = optional_param('user_force_grade', null, PARAM_RAW); 48 | $selected_users = optional_param('selected_users', 0, PARAM_BOOL); 49 | 50 | if (!empty($sesskey)) { 51 | require_sesskey(); 52 | } 53 | 54 | if (empty($toolid)) { 55 | require_login(); 56 | require_capability('moodle/site:config', context_system::instance()); 57 | } else { 58 | $tool = $DB->get_record('local_ltiprovider', array('id' => $toolid), '*', MUST_EXIST); 59 | $course = $DB->get_record('course', array('id' => $tool->courseid), '*', MUST_EXIST); 60 | $coursecontext = context_course::instance($course->id); 61 | require_login($course); 62 | require_capability('local/ltiprovider:manage', $coursecontext); 63 | } 64 | 65 | $log = array(); 66 | 67 | if ($selected_users && strlen($user_force_grade)==0) { 68 | $log[] = s(" Error, there are not selected users"); 69 | } else { 70 | 71 | $select = 'disabled = ? AND sendgrades = ?'; 72 | $params_select = array(0, 1); 73 | if (!empty($courseid)) { 74 | $select .= ' AND courseid=?'; 75 | array_push($params_select, $courseid); 76 | } 77 | if (!empty($toolid)) { 78 | $select .= ' AND id=?'; 79 | array_push($params_select, $toolid); 80 | } 81 | if ($tools = $DB->get_records_select('local_ltiprovider', $select, $params_select)) { 82 | foreach ($tools as $tool) { 83 | 84 | $log[] = s(" Starting sync tool for grades id $tool->id course id $tool->courseid"); 85 | 86 | if ($omitcompletion) { 87 | $tool->requirecompletion = 0; 88 | } 89 | 90 | if ($tool->requirecompletion) { 91 | $log[] = s(" Grades require activity or course completion"); 92 | } 93 | $user_count = 0; 94 | $send_count = 0; 95 | $error_count = 0; 96 | 97 | $completion = new completion_info(get_course($tool->courseid)); 98 | 99 | $select_user = 'toolid = :toolid'; 100 | $params_user = array('toolid'=>$tool->id); 101 | if (!empty($userid)) { 102 | $select_user .= ' AND userid = :userid'; 103 | $params_user += array('userid' => $userid); 104 | } elseif ($selected_users) { 105 | list($sql_in, $params_in) = $DB->get_in_or_equal(explode(",", $user_force_grade), SQL_PARAMS_NAMED); 106 | $select_user .= ' AND userid '.$sql_in; 107 | $params_user += $params_in; 108 | } 109 | 110 | if ($users = $DB->get_records_select('local_ltiprovider_user', $select_user, $params_user)) { 111 | foreach ($users as $user) { 112 | 113 | $data = array( 114 | 'tool' => $tool, 115 | 'user' => $user, 116 | ); 117 | 118 | local_ltiprovider_call_hook('grades', (object)$data); 119 | 120 | $log[] = s(" Sending grades for user $user->userid"); 121 | $user_count = $user_count + 1; 122 | // This can happen is the sync process has an unexpected error 123 | if (strlen($user->serviceurl) < 1) { 124 | $log[] = s(" Empty serviceurl"); 125 | continue; 126 | } 127 | if (strlen($user->sourceid) < 1) { 128 | $log[] = s(" Empty sourceid"); 129 | continue; 130 | } 131 | 132 | $grade = false; 133 | if ($context = $DB->get_record('context', array('id' => $tool->contextid))) { 134 | if ($context->contextlevel == CONTEXT_COURSE) { 135 | 136 | if ($tool->requirecompletion and !$completion->is_course_complete($user->userid)) { 137 | $log[] = s(" Skipping user $user->userid since he didn't complete the course"); 138 | continue; 139 | } 140 | 141 | if ($grade = grade_get_course_grade($user->userid, $tool->courseid)) { 142 | $grademax = floatval($grade->item->grademax); 143 | $grade = $grade->grade; 144 | } 145 | } else { 146 | if ($context->contextlevel == CONTEXT_MODULE) { 147 | $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); 148 | 149 | if ($tool->requirecompletion) { 150 | $data = $completion->get_data($cm, false, $user->userid); 151 | if ($data->completionstate != COMPLETION_COMPLETE_PASS and $data->completionstate != COMPLETION_COMPLETE) { 152 | $log[] = s(" Skipping user $user->userid since he didn't complete the activity"); 153 | continue; 154 | } 155 | } 156 | 157 | $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, 158 | $user->userid); 159 | if (empty($grades->items[0]->grades)) { 160 | $grade = false; 161 | } else { 162 | $grade = reset($grades->items[0]->grades); 163 | if (!empty($grade->item)) { 164 | $grademax = floatval($grade->item->grademax); 165 | } else { 166 | $grademax = floatval($grades->items[0]->grademax); 167 | } 168 | $grade = $grade->grade; 169 | } 170 | } 171 | } 172 | 173 | if ($grade === false || $grade === null || strlen($grade) < 1) { 174 | $log[] = s(" Invalid grade $grade"); 175 | continue; 176 | } 177 | 178 | // No need to be dividing by zero 179 | if ($grademax == 0.0) { 180 | $grademax = 100.0; 181 | } 182 | 183 | // TODO: Make lastgrade should be float or string - but it is integer so we truncate 184 | // TODO: Then remove those intval() calls 185 | 186 | // Don't double send 187 | if (intval($grade) == $user->lastgrade) { 188 | $log[] = s(" Last grade send is equal to current grade"); 189 | } 190 | 191 | // We sync with the external system only when the new grade differs with the previous one 192 | // TODO - Global setting for check this 193 | if ($grade >= 0 and $grade <= $grademax) { 194 | $float_grade = $grade / $grademax; 195 | $body = local_ltiprovider_create_service_body($user->sourceid, $float_grade); 196 | 197 | try { 198 | $response = ltiprovider\sendOAuthBodyPOST('POST', $user->serviceurl, $user->consumerkey, 199 | $user->consumersecret, 'application/xml', $body); 200 | } catch (Exception $e) { 201 | $log[] = s(" Exception" . $e->getMessage()); 202 | $error_count = $error_count + 1; 203 | $log[] = s("Invalid $response " . $response); 204 | continue; 205 | } 206 | 207 | if ($printresponse) { 208 | $log[] = s(" Remote system response: \n $response\n"); 209 | } 210 | 211 | // TODO - Check for errors in $retval in a correct way (parsing xml) 212 | if (strpos(strtolower($response), 'success') !== false) { 213 | $DB->set_field('local_ltiprovider_user', 'lastsync', time(), array('id' => $user->id)); 214 | $DB->set_field('local_ltiprovider_user', 'lastgrade', intval($grade), 215 | array('id' => $user->id)); 216 | $log[] = s(" User grade sent to remote system. userid: $user->userid grade: $float_grade"); 217 | $send_count = $send_count + 1; 218 | } else { 219 | $log[] = s(" User grade send failed. userid: $user->userid grade: $float_grade: " . $response); 220 | $error_count = $error_count + 1; 221 | } 222 | } else { 223 | $log[] = s(" User grade for user $user->userid out of range: grade = " . $grade); 224 | $error_count = $error_count + 1; 225 | } 226 | } else { 227 | $log[] = s(" Invalid context: contextid = " . $tool->contextid); 228 | } 229 | } 230 | } 231 | $log[] = s(" Completed sync tool id $tool->id course id $tool->courseid users=$user_count sent=$send_count errors=$error_count"); 232 | } 233 | } 234 | 235 | } 236 | // Check if we requested this by URL or via the sync grades report. 237 | if ($toolid & !empty($sesskey)) { 238 | $link = new moodle_url('/local/ltiprovider/syncreport.php', array('id' => $toolid)); 239 | 240 | $PAGE->set_context($coursecontext); 241 | $PAGE->set_url($link); 242 | notice(implode("
", $log), $link); 243 | } else { 244 | @header('Content-Type: text/plain; charset=utf-8'); 245 | echo implode("\n", $log); 246 | } 247 | 248 | -------------------------------------------------------------------------------- /test/lms.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | IMS Basic Learning Tools Interoperability 9 | 10 | 22 | 23 | 24 | 25 |

IMS BasicLTI PHP Consumer

26 |

This is a very simple reference implementaton of the LMS side (i.e. consumer) for IMS BasicLTI.

27 | "120988f929-274612", 33 | "resource_link_title" => "Weekly Blog", 34 | "resource_link_description" => "A weekly blog.", 35 | "user_id" => "292832126", 36 | "roles" => "urn:lti:role:ims/lis/Instructor", // or urn:lti:instrole:ims/lis/Learner 37 | "lis_person_name_full" => 'Jane Q. Public', 38 | "lis_person_name_family" => 'Public', 39 | "lis_person_name_given" => 'Given', 40 | "lis_person_contact_email_primary" => "user@school.edu", 41 | "lis_person_sourcedid" => "school.edu:user", 42 | "context_id" => "456434513", 43 | "context_title" => "Design of Personal Environments", 44 | "context_label" => "SI182", 45 | "tool_consumer_instance_guid" => "lmsng.school.edu", 46 | "tool_consumer_instance_description" => "University of School (LMSng)", 47 | "custom_create_context" => "0", 48 | "custom_context_template" => "", 49 | "custom_resource_link_type" => "", 50 | "custom_resource_link_copy_id" => "", 51 | "custom_service" => "", 52 | "custom_force_navigation" => "", 53 | "custom_hide_left_blocks" => "", 54 | "custom_hide_right_blocks" => "", 55 | "custom_hide_page_header" => "", 56 | "custom_hide_page_footer" => "", 57 | "custom_custom_css" => "", 58 | "custom_show_blocks" => "", 59 | "ext_ims_lis_memberships_id" => "", 60 | "ext_ims_lis_memberships_url" => "", 61 | "user_image" => "" 62 | ); 63 | 64 | foreach ($lmsdata as $k => $val ) { 65 | if ( $_POST[$k] && strlen($_POST[$k]) > 0 ) { 66 | $lmsdata[$k] = $_POST[$k]; 67 | } 68 | } 69 | 70 | $cur_url = curPageURL(); 71 | $key = $_REQUEST["key"]; 72 | if ( ! $key ) $key = "12345"; 73 | $secret = $_REQUEST["secret"]; 74 | if ( ! $secret ) $secret = "secret"; 75 | $endpoint = $_REQUEST["endpoint"]; 76 | 77 | if ( ! $endpoint ) $endpoint = str_replace("test/lms.php","tool.php",$cur_url); 78 | 79 | $urlformat = $_REQUEST["format"]; 80 | $urlformat = ( $urlformat != 'XML' ); 81 | $tool_consumer_instance_guid = $lmsdata['tool_consumer_instance_guid']; 82 | $tool_consumer_instance_description = $lmsdata['tool_consumer_instance_description']; 83 | 84 | $xmldesc = str_replace("\\\"","\"",$_REQUEST["xmldesc"]); 85 | if ( ! $xmldesc ) $xmldesc = $default_desc; 86 | ?> 87 | 100 | Toggle Resource and Launch Data 101 | \n"); 103 | echo("\n"); 104 | echo(""); 112 | echo("(To set a value to 'empty' - set it to a blank)"); 113 | echo("
BasicLTI Resource\n"); 114 | if ( $urlformat ) { 115 | echo("Launch URL: \n"); 116 | } else { 117 | echo("XML BasicLTI Resource Descriptor:
\n"); 118 | } 119 | echo("
Key: \n"); 120 | echo("
Secret: \n"); 121 | echo("

"); 122 | echo("

Launch Data\n"); 123 | foreach ($lmsdata as $k => $val ) { 124 | echo($k.":
\n"); 127 | } 128 | echo("

"); 129 | echo(""); 130 | echo("


"); 131 | 132 | if ( $urlformat ) { 133 | $parms = $lmsdata; 134 | } else { 135 | $cx = launchInfo($xmldesc); 136 | $endpoint = $cx["launch_url"]; 137 | if ( ! $endpoint ) { 138 | echo("

Error, did not find a launch_url or secure_launch_url in the XML descriptor

\n"); 139 | exit(); 140 | } 141 | $custom = $cx["custom"]; 142 | $parms = array_merge($custom, $lmsdata); 143 | } 144 | 145 | // Cleanup parms before we sign 146 | foreach( $parms as $k => $val ) { 147 | if (strlen(trim($parms[$k]) ) < 1 ) { 148 | unset($parms[$k]); 149 | } 150 | } 151 | 152 | // Add oauth_callback to be compliant with the 1.0A spec 153 | $parms["oauth_callback"] = "about:blank"; 154 | 155 | $parms = signParameters($parms, $endpoint, "POST", $key, $secret, "Press to Launch", $tool_consumer_instance_guid, $tool_consumer_instance_description); 156 | 157 | $content = postLaunchHTML($parms, $endpoint, true, 158 | "width=\"100%\" height=\"900\" scrolling=\"auto\" frameborder=\"1\" transparency"); 159 | print($content); 160 | 161 | ?> 162 |
163 |

164 | Note: Unpublished drafts of IMS Specifications are only available to 165 | IMS members and any software based on an unpublished draft is subject to change. 166 | Sample code is provided to help developers understand the specification more quickly. 167 | Simply interoperating with this sample implementation code does not 168 | allow one to claim compliance with a specification. 169 |

170 | IMS Learning Tools Interoperability Working Group
171 | IMS Compliance Detail
172 | IMS Developer Community
173 | © 2009 IMS Global Learning Consortium, Inc. under the Apache 2 License.

174 | -------------------------------------------------------------------------------- /test/misc.php: -------------------------------------------------------------------------------- 1 | \n"); 16 | return; 17 | } 18 | 19 | $zip = zip_open($file_name); 20 | if (! is_resource($zip)) return; 21 | 22 | while ($zip_entry = zip_read($zip)) { 23 | if ( zip_entry_name($zip_entry) != $zip_file ) continue; 24 | if (zip_entry_open($zip, $zip_entry, "r")) { 25 | $buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry)); 26 | zip_entry_close($zip_entry); 27 | zip_close($zip); 28 | return $buf; 29 | } 30 | } 31 | zip_close($zip); 32 | } 33 | 34 | $default_desc = str_replace("CUR_URL", str_replace("lms.php", "tool.php", curPageURL()), 35 | ' 36 | 38 | A Simple Descriptor 39 | 40 | 120 41 | 42 | CUR_URL 43 | '); 44 | 45 | ?> 46 | 47 | -------------------------------------------------------------------------------- /tool.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Launch destination url. Main entry point for the external system. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | require_once(dirname(__FILE__) . '/../../config.php'); 27 | require_once($CFG->dirroot.'/local/ltiprovider/lib.php'); 28 | require_once($CFG->dirroot.'/local/ltiprovider/locallib.php'); 29 | require_once($CFG->dirroot.'/local/ltiprovider/ims-blti/blti.php'); 30 | 31 | $toolid = optional_param('id', 0, PARAM_INT); 32 | $lticontextid = optional_param('context_id', false, PARAM_RAW); 33 | $custom_create_context = optional_param('custom_create_context', false, PARAM_RAW); 34 | 35 | // Temporary context. 36 | $mycontext = new stdClass(); 37 | $mycontext->info = array(); 38 | $mycontext->info['context_id'] = optional_param('context_id', false, PARAM_RAW); 39 | $mycontext->info['context_title'] = optional_param('context_title', false, PARAM_RAW); 40 | $mycontext->info['context_label'] = optional_param('context_label', false, PARAM_RAW); 41 | $mycontext->info['oauth_consumer_key'] = optional_param('oauth_consumer_key', false, PARAM_RAW); 42 | $mycontext->info['resource_link_id'] = optional_param('resource_link_id', false, PARAM_RAW); 43 | 44 | if (optional_param('custom_lti_message_encoded_base64', 0, PARAM_INT) == 1) { 45 | $lticontextid = base64_decode($lticontextid); 46 | $custom_create_context = base64_decode($custom_create_context); 47 | $blti = new BLTI(false, false, false); 48 | $mycontext->info = $blti->decodeBase64($mycontext->info); 49 | } 50 | 51 | if (!$toolid and !$lticontextid) { 52 | print_error('invalidtoolid', 'local_ltiprovider'); 53 | } 54 | 55 | if (!$toolid and $lticontextid) { 56 | // Check if there is more that one course for this LTI context id. 57 | $idnumber = local_ltiprovider_get_new_course_info('idnumber', $mycontext); 58 | if ($DB->count_records('course', array('idnumber' => $idnumber)) > 1) { 59 | print_error('cantdeterminecontext', 'local_ltiprovider'); 60 | } 61 | if ($course = $DB->get_record('course', array('idnumber' => $idnumber))) { 62 | // Look for a course created for this LTI context id. 63 | if ($coursecontext = context_course::instance($course->id)) { 64 | if ($DB->count_records('local_ltiprovider', array('contextid' => $coursecontext->id)) > 1) { 65 | print_error('cantdeterminecontext', 'local_ltiprovider'); 66 | } 67 | $toolid = $DB->get_field('local_ltiprovider', 'id', array('contextid' => $coursecontext->id)); 68 | 69 | // Now check if we are accessing a resource/activity instead a course. 70 | $resource_link_id = $mycontext->info['resource_link_id']; 71 | if ($resource_link_id) { 72 | if ($cm = $DB->get_record('course_modules', array('idnumber' => $resource_link_id, 'course' => $course->id), '*', IGNORE_MULTIPLE)) { 73 | $cmcontext = context_module::instance($cm->id); 74 | 75 | $toolinstances = $DB->count_records('local_ltiprovider', array('contextid' => $cmcontext->id)); 76 | // More than one tool for the same resource/activity. 77 | if ($toolinstances and $toolinstances > 1) { 78 | print_error('cantdeterminecontext', 'local_ltiprovider'); 79 | } 80 | if ($toolinstances) { 81 | $toolid = $DB->get_field('local_ltiprovider', 'id', array('contextid' => $cmcontext->id)); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | $secret = ''; 90 | // We may expect a valid tool / context id or custom parameters. 91 | if ($tool = $DB->get_record('local_ltiprovider', array('id'=>$toolid))) { 92 | if ($tool->disabled) { 93 | print_error('tooldisabled', 'local_ltiprovider'); 94 | } 95 | $secret = $tool->secret; 96 | } else if ($custom_create_context) { 97 | $secret = get_config('local_ltiprovider', 'globalsharedsecret'); 98 | } 99 | 100 | if (!$secret) { 101 | print_error('invalidtoolid', 'local_ltiprovider'); 102 | } 103 | 104 | // Do not set session, do not redirect 105 | $context = new BLTI($secret, false, false); 106 | 107 | $data = array( 108 | 'tool' => $tool, 109 | 'context' => $context, 110 | ); 111 | local_ltiprovider_call_hook('validate_request', (object) $data); 112 | 113 | // Correct launch request 114 | if ($context->valid) { 115 | 116 | // Are we creating a new context (that means a new course tool)? 117 | if ($custom_create_context) { 118 | 119 | // Check if the remote user can create contexts, checking the remote role. 120 | $cancreate = false; 121 | $rolesallowedcreatecontexts = get_config('local_ltiprovider', 'rolesallowedcreatecontexts'); 122 | if ($rolesallowedcreatecontexts) { 123 | $rolesallowedcreatecontexts = explode(',', strtolower($rolesallowedcreatecontexts)); 124 | $roles = explode(',', strtolower($context->info['roles'])); 125 | 126 | foreach ($roles as $rol) { 127 | if (in_array($rol, $rolesallowedcreatecontexts)) { 128 | $cancreate = true; 129 | break; 130 | } 131 | } 132 | 133 | } 134 | 135 | require_once("$CFG->dirroot/course/lib.php"); 136 | $newcourse = new stdClass(); 137 | $newcourse->fullname = local_ltiprovider_get_new_course_info('fullname', $context); 138 | $newcourse->shortname = local_ltiprovider_get_new_course_info('shortname', $context); 139 | $newcourse->idnumber = local_ltiprovider_get_new_course_info('idnumber', $context); 140 | 141 | //Gets the first visible category by sort order 142 | $categories = $DB->get_records('course_categories', array('visible'=>1), 'sortorder', 'id', 0, 1); 143 | if (count($categories)==0){ 144 | $categories = $DB->get_records('course_categories', null, '', 'id', 0, 1); 145 | } 146 | $category = array_shift($categories); 147 | 148 | $newcourse->category = $category->id; 149 | 150 | // Course exists?? First try idnumber. 151 | $course = $DB->get_record('course', array('idnumber' => $newcourse->idnumber)); 152 | 153 | // Then try shortname. 154 | if (!$course) { 155 | $course = $DB->get_record('course', array('shortname' => $newcourse->shortname)); 156 | } 157 | 158 | if (!$cancreate and !$course) { 159 | print_error('rolecannotcreatecontexts', 'local_ltiprovider'); 160 | } 161 | 162 | if (!$course) { 163 | $course = create_course($newcourse); 164 | 165 | $coursecontext = context_course::instance($course->id); 166 | 167 | // Create the tool that provide the full course. 168 | $tool = local_ltiprovider_create_tool($course->id, $coursecontext->id, $context); 169 | 170 | // Are we using another course as template? 171 | // We have a setting for storing courses to be restored when the cron job is executed. 172 | $custom_context_template = $context->info['custom_context_template']; 173 | $tplcourse = $DB->get_record('course', array('idnumber' => $custom_context_template), '*', IGNORE_MULTIPLE); 174 | 175 | if ($custom_context_template and $tplcourse) { 176 | 177 | $username = local_ltiprovider_create_username($context->info['oauth_consumer_key'], $context->info['user_id']); 178 | $userrestoringid = $DB->get_field('user', 'id', array('username' => $username));; 179 | 180 | $newcourse = new stdClass(); 181 | $newcourse->id = $tplcourse->id; 182 | $newcourse->destinationid = $course->id; 183 | $newcourse->userrestoringid = $userrestoringid; 184 | $newcourse->context = new stdClass; 185 | $newcourse->context->info['roles'] = $context->info['roles']; 186 | $newcourse->restorestart = 0; 187 | $aid = $newcourse->id . "-" . $newcourse->destinationid; 188 | 189 | if ($croncourses = get_config('local_ltiprovider', 'croncourses')) { 190 | $croncourses = unserialize($croncourses); 191 | if (is_array($croncourses)) { 192 | $croncourses[$aid] = $newcourse; 193 | } else { 194 | $croncourses = array($aid => $newcourse); 195 | } 196 | } else { 197 | $croncourses = array($aid => $newcourse); 198 | } 199 | 200 | $croncourses = serialize($croncourses); 201 | set_config('croncourses', $croncourses, 'local_ltiprovider'); 202 | 203 | // Add the waiting label. 204 | $section = new stdClass(); 205 | $section->course = $course->id; 206 | $section->section = 0; 207 | $section->name = ""; 208 | $section->summary = get_string("coursebeingrestored", "local_ltiprovider"); 209 | $section->summaryformat = 1; 210 | $section->sequence = 10; 211 | $section->visible = 1; 212 | $section->availablefrom = 0; 213 | $section->availableuntil = 0; 214 | $section->showavailability = 0; 215 | $section->groupingid = 0; 216 | if ($section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0))) { 217 | $section->summary = get_string("coursebeingrestored", "local_ltiprovider"); 218 | $DB->update_record('course_sections', $section); 219 | } else { 220 | $DB->insert_record('course_sections', $section); 221 | } 222 | rebuild_course_cache($course->id); 223 | } 224 | } else { 225 | $coursecontext = context_course::instance($course->id); 226 | 227 | if (!$tool = $DB->get_record('local_ltiprovider', array('contextid' => $coursecontext->id))) { 228 | print_error('cantdeterminecontext', 'local_ltiprovider'); 229 | } 230 | } 231 | } 232 | 233 | // Check that we can perform enrolments 234 | if (enrol_is_enabled('manual')) { 235 | $manual = enrol_get_plugin('manual'); 236 | } else { 237 | print_error('nomanualenrol', 'local_ltiprovider'); 238 | } 239 | 240 | // Transform to utf8 all the post and get data 241 | 242 | foreach ($_POST as $key => $value) { 243 | $_POST[$key] = core_text::convert($value, $tool->encoding); 244 | } 245 | foreach ($_GET as $key => $value) { 246 | $_GET[$key] = core_text::convert($value, $tool->encoding); 247 | } 248 | 249 | // We need an username without extended chars 250 | // Later accounts add the ConsumerKey - we silently upgrade old accounts 251 | // Might want a flag for this -- Chuck 252 | $username = local_ltiprovider_create_username($context->info['oauth_consumer_key'], $context->info['user_id']); 253 | $dbuser = $DB->get_record('user', array('username' => $username)); 254 | if ( ! $dbuser ) { 255 | $old_username = 'ltiprovider'.md5($context->getUserKey()); 256 | $dbuser = $DB->get_record('user', array('username' => $old_username)); 257 | if ( $dbuser ) { 258 | // Probably should log this 259 | $DB->set_field('user', 'username', $username, array('id' => $dbuser->id)); 260 | } 261 | $dbuser = $DB->get_record('user', array('username' => $username)); 262 | } 263 | 264 | 265 | 266 | // Check if the user exists 267 | $dbuser = $DB->get_record('user', array('username' => $username)); 268 | if (! $dbuser ) { 269 | $user = new stdClass(); 270 | 271 | // clean_param , email username text 272 | $auth = get_config('local_ltiprovider', 'defaultauthmethod'); 273 | if ($auth) { 274 | $user->auth = $auth; 275 | } else { 276 | $user->auth = 'nologin'; 277 | } 278 | 279 | $user->username = $username; 280 | $user->password = md5(uniqid(rand(), 1)); 281 | local_ltiprovider_populate($user, $context, $tool); 282 | $user->id = $DB->insert_record('user', $user); 283 | // Reload full user 284 | $user = $DB->get_record('user', array('id' => $user->id)); 285 | // Trigger event. 286 | $event = \core\event\user_created::create( 287 | array( 288 | 'objectid' => $user->id, 289 | 'relateduserid' => $user->id, 290 | 'context' => context_user::instance($user->id) 291 | ) 292 | ); 293 | $event->trigger(); 294 | } else { 295 | $user = new stdClass(); 296 | local_ltiprovider_populate($user, $context, $tool); 297 | if ( local_ltiprovider_user_match($user, $dbuser) ) { 298 | $user = $dbuser; 299 | } else { 300 | $user = $dbuser; 301 | $userprofileupdate = get_config('local_ltiprovider', 'userprofileupdate'); 302 | if ($userprofileupdate == -1) { 303 | // Check the tool setting. 304 | $userprofileupdate = $tool->userprofileupdate; 305 | } 306 | if ($userprofileupdate) { 307 | local_ltiprovider_populate($user, $context, $tool); 308 | $DB->update_record('user', $user); 309 | 310 | // Trigger event. 311 | $event = \core\event\user_updated::create( 312 | array( 313 | 'objectid' => $user->id, 314 | 'relateduserid' => $user->id, 315 | 'context' => context_user::instance($user->id) 316 | ) 317 | ); 318 | $event->trigger(); 319 | } 320 | } 321 | } 322 | 323 | // Update user image. 324 | if (!empty($context->info['user_image']) or !empty($context->info['custom_user_image'])) { 325 | $userimageurl = (!empty($context->info['user_image'])) ? $context->info['user_image'] : $context->info['custom_user_image']; 326 | local_ltiprovider_update_user_profile_image($user->id, $userimageurl); 327 | } 328 | 329 | // Enrol user in course and activity if needed 330 | if (! $moodlecontext = $DB->get_record('context', array('id' => $tool->contextid))) { 331 | print_error("invalidcontext"); 332 | } 333 | 334 | if ($moodlecontext->contextlevel == CONTEXT_COURSE) { 335 | $courseid = $moodlecontext->instanceid; 336 | $urltogo = $CFG->wwwroot.'/course/view.php?id='.$courseid; 337 | // Check if we have to redirect to a specific module in the course. 338 | $resource_link_id = $context->info['resource_link_id']; 339 | if ($resource_link_id) { 340 | if ($cm = $DB->get_record('course_modules', array('idnumber' => $resource_link_id, 'course' => $courseid), '*', IGNORE_MULTIPLE)) { 341 | if ($cm = get_coursemodule_from_id(false, $cm->id, $courseid)) { 342 | $urltogo = new moodle_url('/mod/' .$cm->modname. '/view.php', array('id' => $cm->id)); 343 | } 344 | } 345 | // Detect it we must create the resource. 346 | if (!$cm) { 347 | $resource_link_title = $context->info['resource_link_title']; 348 | $resource_link_description = (isset($context->info['resource_link_description'])) ? $context->info['resource_link_description'] : false; 349 | $resource_link_type = (isset($context->info['custom_resource_link_type'])) ? $context->info['custom_resource_link_type'] : false; 350 | if (!$resource_link_title) { 351 | $resource_link_title = $context->info['custom_resource_link_title']; 352 | } 353 | if (!$resource_link_description && isset($context->info['custom_resource_link_description'])) { 354 | $resource_link_description = $context->info['custom_resource_link_description']; 355 | } 356 | 357 | // Minimun for creating a module, title and type. 358 | if ($resource_link_title and $resource_link_type) { 359 | 360 | // Check if the remote user can create modules, checking the remote role. 361 | $rolesallowedcreateresources = get_config('local_ltiprovider', 'rolesallowedcreateresources'); 362 | if ($rolesallowedcreateresources) { 363 | $rolesallowedcreateresources = explode(',', strtolower($rolesallowedcreateresources)); 364 | $roles = explode(',', strtolower($context->info['roles'])); 365 | $cancreate = false; 366 | 367 | foreach ($roles as $rol) { 368 | if (in_array($rol, $rolesallowedcreateresources)) { 369 | $cancreate = true; 370 | break; 371 | } 372 | } 373 | 374 | if ($cancreate) { 375 | require_once($CFG->dirroot . '/course/lib.php'); 376 | $moduleinfo = new stdClass(); 377 | 378 | // Always mandatory generic values to any module 379 | // TODO, check for valid types. 380 | $moduleinfo->modulename = $resource_link_type; 381 | $moduleinfo->section = 1; 382 | $moduleinfo->course = $courseid; 383 | $moduleinfo->visible = true; 384 | $moduleinfo->cmidnumber = $resource_link_id; 385 | 386 | // Sometimes optional generic values for some modules 387 | $moduleinfo->name= $resource_link_title; 388 | 389 | // Optional intro editor (depends of module) 390 | if ($resource_link_description) { 391 | $draftid_editor = 0; 392 | $USER = $user; 393 | file_prepare_draft_area($draftid_editor, null, null, null, null); 394 | $moduleinfo->introeditor = array('text'=> $resource_link_description, 'format'=>FORMAT_HTML, 'itemid'=>$draftid_editor); 395 | } 396 | 397 | // Add extra module info. 398 | $modinfofile = $CFG->dirroot . '/local/ltiprovider/modinfo/' . $moduleinfo->modulename . '.php'; 399 | if (file_exists($modinfofile)) { 400 | require_once($modinfofile); 401 | foreach ($extramodinfo as $key => $val) { 402 | $moduleinfo->{$key} = $val; 403 | } 404 | } 405 | 406 | $modinfo = create_module($moduleinfo); 407 | 408 | if ($modinfo) { 409 | $urltogo = new moodle_url('/course/modedit.php', array('update' => $modinfo->coursemodule)); 410 | } 411 | } else { 412 | print_error('rolecannotcreateresources', 'local_ltiprovider'); 413 | } 414 | } 415 | } 416 | } 417 | } 418 | 419 | // Duplicate an existing resource on SSO. 420 | $custom_resource_link_copy_id = (!empty($context->info['custom_resource_link_copy_id'])) ? $context->info['custom_resource_link_copy_id'] : false; 421 | if ($custom_resource_link_copy_id) { 422 | if (!$cm = $DB->get_record('course_modules', array('idnumber' => $custom_resource_link_copy_id), '*', IGNORE_MULTIPLE)) { 423 | print_error('invalidresourcecopyid', 'local_ltiprovider'); 424 | } 425 | $newcmid = local_ltiprovider_duplicate_module($cm->id, $courseid, $resource_link_id, $context); 426 | if ($cm = get_coursemodule_from_id(false, $newcmid)) { 427 | $urltogo = new moodle_url('/mod/' .$cm->modname. '/view.php', array('id' => $cm->id)); 428 | } 429 | } 430 | 431 | } else if ($moodlecontext->contextlevel == CONTEXT_MODULE) { 432 | $cmid = $moodlecontext->instanceid; 433 | $cm = get_coursemodule_from_id(false, $moodlecontext->instanceid, 0, false, MUST_EXIST); 434 | $courseid = $cm->course; 435 | $urltogo = $CFG->wwwroot.'/mod/'.$cm->modname.'/view.php?id='.$cm->id; 436 | } else { 437 | print_error("invalidcontext"); 438 | } 439 | 440 | $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); 441 | 442 | // Enrol the user in the course 443 | $isinstructor = $context->isInstructor(); 444 | local_ltiprovider_enrol_user($tool, $user, $isinstructor); 445 | 446 | // Check if we have to add the user to a group, and if so, add it. 447 | local_ltiprovider_add_user_to_group($tool, $user); 448 | 449 | if ($moodlecontext->contextlevel == CONTEXT_MODULE) { 450 | $role = $isinstructor ? 'instructor' : 'learner'; 451 | 452 | // Enrol the user in the activity 453 | if (($tool->aroleinst and $isinstructor) or ($tool->arolelearn and !$isinstructor)) { 454 | $roleid = $isinstructor ? $tool->aroleinst : $tool->arolelearn; 455 | role_assign($roleid, $user->id, $tool->contextid); 456 | } 457 | } 458 | 459 | // Login user 460 | $sourceid = (!empty($context->info['lis_result_sourcedid'])) ? $context->info['lis_result_sourcedid'] : ''; 461 | $serviceurl = (!empty($context->info['lis_outcome_service_url'])) ? $context->info['lis_outcome_service_url'] : ''; 462 | 463 | if ($userlog = $DB->get_record('local_ltiprovider_user', array('toolid' => $tool->id, 'userid' => $user->id))) { 464 | if ( $userlog->sourceid != $sourceid ) { 465 | $DB->set_field('local_ltiprovider_user', 'sourceid', $sourceid, array('id' => $userlog->id)); 466 | } 467 | if ( $userlog->serviceurl != $serviceurl ) { 468 | $DB->set_field('local_ltiprovider_user', 'serviceurl', $serviceurl, array('id' => $userlog->id)); 469 | } 470 | $DB->set_field('local_ltiprovider_user', 'lastaccess', time(), array('id' => $userlog->id)); 471 | } else { 472 | // These data is needed for sending backup outcomes (aka grades) 473 | $userlog = new stdClass(); 474 | $userlog->userid = $user->id; 475 | $userlog->toolid = $tool->id; 476 | // TODO Improve these checks 477 | $userlog->serviceurl = $serviceurl; 478 | $userlog->sourceid = $sourceid; 479 | $userlog->consumerkey = $context->info['oauth_consumer_key']; 480 | // TODO Do not store secret here 481 | $userlog->consumersecret = $secret; 482 | $userlog->lastsync = 0; 483 | $userlog->lastgrade = 0; 484 | $userlog->lastaccess = time(); 485 | $userlog->membershipsurl = (!empty($context->info['ext_ims_lis_memberships_url'])) ? $context->info['ext_ims_lis_memberships_url']: ''; 486 | $userlog->membershipsid = (!empty($context->info['ext_ims_lis_memberships_id'])) ? $context->info['ext_ims_lis_memberships_id'] : ''; 487 | $DB->insert_record('local_ltiprovider_user', $userlog); 488 | } 489 | 490 | $tool->context = $moodlecontext; 491 | 492 | $indexes = array('custom_force_navigation', 'custom_hide_left_blocks', 'custom_hide_right_blocks', 493 | 'custom_hide_page_header', 'custom_hide_page_footer', 'custom_custom_css', 'custom_show_blocks' ); 494 | 495 | foreach ($indexes as $i) { 496 | if (empty($context->info[$i])) { 497 | $context->info[$i] = ''; 498 | } 499 | } 500 | 501 | // Override some settings. 502 | if ($custom_force_navigation = $context->info['custom_force_navigation']) { 503 | $tool->forcenavigation = 1; 504 | } 505 | if ($custom_hide_left_blocks = $context->info['custom_hide_left_blocks']) { 506 | $tool->hideleftblocks = 1; 507 | } 508 | if ($custom_hide_right_blocks = $context->info['custom_hide_right_blocks']) { 509 | $tool->hiderightblocks = 1; 510 | } 511 | if ($custom_hide_page_header = $context->info['custom_hide_page_header']) { 512 | $tool->hidepageheader = 1; 513 | } 514 | if ($custom_hide_page_footer = $context->info['custom_hide_page_footer']) { 515 | $tool->hidepagefooter = 1; 516 | } 517 | if ($custom_custom_css = $context->info['custom_custom_css']) { 518 | $tool->customcss = $custom_custom_css; 519 | } 520 | if ($custom_show_blocks = $context->info['custom_show_blocks']) { 521 | $tool->showblocks = $custom_show_blocks; 522 | } 523 | 524 | $SESSION->ltiprovider = $tool; 525 | 526 | complete_user_login($user); 527 | 528 | // Trigger login event. 529 | $event = \core\event\user_loggedin::create( 530 | array( 531 | 'userid' => $user->id, 532 | 'objectid' => $user->id, 533 | 'other' => array('username' => $user->username), 534 | )); 535 | $event->trigger(); 536 | 537 | // Moodle 2.2 and onwards. 538 | if (isset($CFG->allowframembedding) and !$CFG->allowframembedding) { 539 | echo ' 540 | 541 | 542 | '; 543 | echo get_string('newpopupnotice', 'local_ltiprovider'); 544 | $stropentool = get_string('opentool', 'local_ltiprovider'); 545 | echo "

$stropentool

"; 546 | echo "

".get_string('allowframembedding', 'local_ltiprovider')."

"; 547 | echo ''; 548 | } else { 549 | redirect($urltogo); 550 | } 551 | } else { 552 | // print_error('invalidcredentials', 'local_ltiprovider'); 553 | echo $context->message; 554 | } 555 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Version details. 19 | * 20 | * @package local 21 | * @subpackage ltiprovider 22 | * @copyright 2011 Juan Leyva 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | $plugin->version = 2016020104; 29 | $plugin->requires = 2015051100; // Require Moodle version (2.9). 30 | $plugin->maturity = MATURITY_STABLE; 31 | $plugin->release = '2.9.1'; 32 | $plugin->component = 'local_ltiprovider'; 33 | --------------------------------------------------------------------------------