ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç ToolProxy.php000064400000006705152267612520007254 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license GNU Lesser General Public License, version 3 () */ #[\AllowDynamicProperties] class ToolProxy { /** * Local id of tool consumer. * * @var string $id */ public $id = null; /** * Tool Consumer for this tool proxy. * * @var ToolConsumer $consumer */ private $consumer = null; /** * Tool Consumer ID for this tool proxy. * * @var int $consumerId */ private $consumerId = null; /** * Consumer ID value. * * @var int $id */ private $recordId = null; /** * Data connector object. * * @var DataConnector $dataConnector */ private $dataConnector = null; /** * Tool Proxy document. * * @var MediaType\ToolProxy $toolProxy */ private $toolProxy = null; /** * Class constructor. * * @param DataConnector $dataConnector Data connector * @param string $id Tool Proxy ID (optional, default is null) */ public function __construct($dataConnector, $id = null) { $this->initialize(); $this->dataConnector = $dataConnector; if (!empty($id)) { $this->load($id); } else { $this->recordId = DataConnector::getRandomString(32); } } /** * Initialise the tool proxy. */ public function initialize() { $this->id = null; $this->recordId = null; $this->toolProxy = null; $this->created = null; $this->updated = null; } /** * Initialise the tool proxy. * * Pseudonym for initialize(). */ public function initialise() { $this->initialize(); } /** * Get the tool proxy record ID. * * @return int Tool Proxy record ID value */ public function getRecordId() { return $this->recordId; } /** * Sets the tool proxy record ID. * * @param int $recordId Tool Proxy record ID value */ public function setRecordId($recordId) { $this->recordId = $recordId; } /** * Get tool consumer. * * @return ToolConsumer Tool consumer object for this context. */ public function getConsumer() { if (is_null($this->consumer)) { $this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector()); } return $this->consumer; } /** * Set tool consumer ID. * * @param int $consumerId Tool Consumer ID for this resource link. */ public function setConsumerId($consumerId) { $this->consumer = null; $this->consumerId = $consumerId; } /** * Get the data connector. * * @return DataConnector Data connector object */ public function getDataConnector() { return $this->dataConnector; } ### ### PRIVATE METHOD ### /** * Load the tool proxy from the database. * * @param string $id The tool proxy id value * * @return boolean True if the tool proxy was successfully loaded */ private function load($id) { $this->initialize(); $this->id = $id; $ok = $this->dataConnector->loadToolProxy($this); if (!$ok) { $this->enabled = $autoEnable; } return $ok; } } ToolConsumer.php000064400000037242152267612520007726 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ToolConsumer { /** * Local name of tool consumer. * * @var string $name */ public $name = null; /** * Shared secret. * * @var string $secret */ public $secret = null; /** * LTI version (as reported by last tool consumer connection). * * @var string $ltiVersion */ public $ltiVersion = null; /** * Name of tool consumer (as reported by last tool consumer connection). * * @var string $consumerName */ public $consumerName = null; /** * Tool consumer version (as reported by last tool consumer connection). * * @var string $consumerVersion */ public $consumerVersion = null; /** * Tool consumer GUID (as reported by first tool consumer connection). * * @var string $consumerGuid */ public $consumerGuid = null; /** * Optional CSS path (as reported by last tool consumer connection). * * @var string $cssPath */ public $cssPath = null; /** * Whether the tool consumer instance is protected by matching the consumer_guid value in incoming requests. * * @var boolean $protected */ public $protected = false; /** * Whether the tool consumer instance is enabled to accept incoming connection requests. * * @var boolean $enabled */ public $enabled = false; /** * Date/time from which the the tool consumer instance is enabled to accept incoming connection requests. * * @var int $enableFrom */ public $enableFrom = null; /** * Date/time until which the tool consumer instance is enabled to accept incoming connection requests. * * @var int $enableUntil */ public $enableUntil = null; /** * Date of last connection from this tool consumer. * * @var int $lastAccess */ public $lastAccess = null; /** * Default scope to use when generating an Id value for a user. * * @var int $idScope */ public $idScope = ToolProvider::ID_SCOPE_ID_ONLY; /** * Default email address (or email domain) to use when no email address is provided for a user. * * @var string $defaultEmail */ public $defaultEmail = ''; /** * Setting values (LTI parameters, custom parameters and local parameters). * * @var array $settings */ public $settings = null; /** * Date/time when the object was created. * * @var int $created */ public $created = null; /** * Date/time when the object was last updated. * * @var int $updated */ public $updated = null; /** * The consumer profile data. * * @var stdClass */ public $profile = null; /** * Consumer ID value. * * @var int $id */ private $id = null; /** * Consumer key value. * * @var string $key */ private $key = null; /** * Whether the settings value have changed since last saved. * * @var boolean $settingsChanged */ private $settingsChanged = false; /** * Data connector object or string. * * @var mixed $dataConnector */ private $dataConnector = null; /** * Class constructor. * * @param string $key Consumer key * @param DataConnector $dataConnector A data connector object * @param boolean $autoEnable true if the tool consumers is to be enabled automatically (optional, default is false) */ public function __construct($key = null, $dataConnector = null, $autoEnable = false) { $this->initialize(); if (empty($dataConnector)) { $dataConnector = DataConnector::getDataConnector(); } $this->dataConnector = $dataConnector; if (!empty($key)) { $this->load($key, $autoEnable); } else { $this->secret = DataConnector::getRandomString(32); } } /** * Initialise the tool consumer. */ public function initialize() { $this->id = null; $this->key = null; $this->name = null; $this->secret = null; $this->ltiVersion = null; $this->consumerName = null; $this->consumerVersion = null; $this->consumerGuid = null; $this->profile = null; $this->toolProxy = null; $this->settings = array(); $this->protected = false; $this->enabled = false; $this->enableFrom = null; $this->enableUntil = null; $this->lastAccess = null; $this->idScope = ToolProvider::ID_SCOPE_ID_ONLY; $this->defaultEmail = ''; $this->created = null; $this->updated = null; } /** * Initialise the tool consumer. * * Pseudonym for initialize(). */ public function initialise() { $this->initialize(); } /** * Save the tool consumer to the database. * * @return boolean True if the object was successfully saved */ public function save() { $ok = $this->dataConnector->saveToolConsumer($this); if ($ok) { $this->settingsChanged = false; } return $ok; } /** * Delete the tool consumer from the database. * * @return boolean True if the object was successfully deleted */ public function delete() { return $this->dataConnector->deleteToolConsumer($this); } /** * Get the tool consumer record ID. * * @return int Consumer record ID value */ public function getRecordId() { return $this->id; } /** * Sets the tool consumer record ID. * * @param int $id Consumer record ID value */ public function setRecordId($id) { $this->id = $id; } /** * Get the tool consumer key. * * @return string Consumer key value */ public function getKey() { return $this->key; } /** * Set the tool consumer key. * * @param string $key Consumer key value */ public function setKey($key) { $this->key = $key; } /** * Get the data connector. * * @return mixed Data connector object or string */ public function getDataConnector() { return $this->dataConnector; } /** * Is the consumer key available to accept launch requests? * * @return boolean True if the consumer key is enabled and within any date constraints */ public function getIsAvailable() { $ok = $this->enabled; $now = time(); if ($ok && !is_null($this->enableFrom)) { $ok = $this->enableFrom <= $now; } if ($ok && !is_null($this->enableUntil)) { $ok = $this->enableUntil > $now; } return $ok; } /** * Get a setting value. * * @param string $name Name of setting * @param string $default Value to return if the setting does not exist (optional, default is an empty string) * * @return string Setting value */ public function getSetting($name, $default = '') { if (array_key_exists($name, $this->settings)) { $value = $this->settings[$name]; } else { $value = $default; } return $value; } /** * Set a setting value. * * @param string $name Name of setting * @param string $value Value to set, use an empty value to delete a setting (optional, default is null) */ public function setSetting($name, $value = null) { $old_value = $this->getSetting($name); if ($value !== $old_value) { if (!empty($value)) { $this->settings[$name] = $value; } else { unset($this->settings[$name]); } $this->settingsChanged = true; } } /** * Get an array of all setting values. * * @return array Associative array of setting values */ public function getSettings() { return $this->settings; } /** * Set an array of all setting values. * * @param array $settings Associative array of setting values */ public function setSettings($settings) { $this->settings = $settings; } /** * Save setting values. * * @return boolean True if the settings were successfully saved */ public function saveSettings() { if ($this->settingsChanged) { $ok = $this->save(); } else { $ok = true; } return $ok; } /** * Check if the Tool Settings service is supported. * * @return boolean True if this tool consumer supports the Tool Settings service */ public function hasToolSettingsService() { $url = $this->getSetting('custom_system_setting_url'); return !empty($url); } /** * Get Tool Settings. * * @param boolean $simple True if all the simple media type is to be used (optional, default is true) * * @return mixed The array of settings if successful, otherwise false */ public function getToolSettings($simple = true) { $url = $this->getSetting('custom_system_setting_url'); $service = new Service\ToolSettings($this, $url, $simple); $response = $service->get(); return $response; } /** * Perform a Tool Settings service request. * * @param array $settings An associative array of settings (optional, default is none) * * @return boolean True if action was successful, otherwise false */ public function setToolSettings($settings = array()) { $url = $this->getSetting('custom_system_setting_url'); $service = new Service\ToolSettings($this, $url); $response = $service->set($settings); return $response; } /** * Add the OAuth signature to an LTI message. * * @param string $url URL for message request * @param string $type LTI message type * @param string $version LTI version * @param array $params Message parameters * * @return array Array of signed message parameters */ public function signParameters($url, $type, $version, $params) { if (!empty($url)) { // Check for query parameters which need to be included in the signature $queryParams = array(); $queryString = parse_url($url, PHP_URL_QUERY); if (!is_null($queryString)) { $queryItems = explode('&', $queryString); foreach ($queryItems as $item) { if (strpos($item, '=') !== false) { list($name, $value) = explode('=', $item); $queryParams[urldecode($name)] = urldecode($value); } else { $queryParams[urldecode($item)] = ''; } } } $params = $params + $queryParams; // Add standard parameters $params['lti_version'] = $version; $params['lti_message_type'] = $type; $params['oauth_callback'] = 'about:blank'; // Add OAuth signature $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); $consumer = new OAuth\OAuthConsumer($this->getKey(), $this->secret, null); $req = OAuth\OAuthRequest::from_consumer_and_token($consumer, null, 'POST', $url, $params); $req->sign_request($hmacMethod, $consumer, null); $params = $req->get_parameters(); // Remove parameters being passed on the query string foreach (array_keys($queryParams) as $name) { unset($params[$name]); } } return $params; } /** * Add the OAuth signature to an array of message parameters or to a header string. * * @return mixed Array of signed message parameters or header string */ public static function addSignature($endpoint, $consumerKey, $consumerSecret, $data, $method = 'POST', $type = null) { $params = array(); if (is_array($data)) { $params = $data; } // Check for query parameters which need to be included in the signature $queryParams = array(); $queryString = parse_url($endpoint, PHP_URL_QUERY); if (!is_null($queryString)) { $queryItems = explode('&', $queryString); foreach ($queryItems as $item) { if (strpos($item, '=') !== false) { list($name, $value) = explode('=', $item); $queryParams[urldecode($name)] = urldecode($value); } else { $queryParams[urldecode($item)] = ''; } } $params = $params + $queryParams; } if (!is_array($data)) { // Calculate body hash $hash = base64_encode(sha1($data, true)); $params['oauth_body_hash'] = $hash; } // Add OAuth signature $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); $oauthConsumer = new OAuth\OAuthConsumer($consumerKey, $consumerSecret, null); $oauthReq = OAuth\OAuthRequest::from_consumer_and_token($oauthConsumer, null, $method, $endpoint, $params); $oauthReq->sign_request($hmacMethod, $oauthConsumer, null); $params = $oauthReq->get_parameters(); // Remove parameters being passed on the query string foreach (array_keys($queryParams) as $name) { unset($params[$name]); } if (!is_array($data)) { $header = $oauthReq->to_header(); if (empty($data)) { if (!empty($type)) { $header .= "\nAccept: {$type}"; } } else if (isset($type)) { $header .= "\nContent-Type: {$type}"; $header .= "\nContent-Length: " . strlen($data); } return $header; } else { return $params; } } /** * Perform a service request * * @param object $service Service object to be executed * @param string $method HTTP action * @param string $format Media type * @param mixed $data Array of parameters or body string * * @return HTTPMessage HTTP object containing request and response details */ public function doServiceRequest($service, $method, $format, $data) { $header = ToolConsumer::addSignature($service->endpoint, $this->getKey(), $this->secret, $data, $method, $format); // Connect to tool consumer $http = new HTTPMessage($service->endpoint, $method, $data, $header); // Parse JSON response if ($http->send() && !empty($http->response)) { $http->responseJson = json_decode($http->response); $http->ok = !is_null($http->responseJson); } return $http; } /** * Load the tool consumer from the database by its record ID. * * @param string $id The consumer key record ID * @param DataConnector $dataConnector Database connection object * * @return object ToolConsumer The tool consumer object */ public static function fromRecordId($id, $dataConnector) { $toolConsumer = new ToolConsumer(null, $dataConnector); $toolConsumer->initialize(); $toolConsumer->setRecordId($id); if (!$dataConnector->loadToolConsumer($toolConsumer)) { $toolConsumer->initialize(); } return $toolConsumer; } ### ### PRIVATE METHOD ### /** * Load the tool consumer from the database. * * @param string $key The consumer key value * @param boolean $autoEnable True if the consumer should be enabled (optional, default if false) * * @return boolean True if the consumer was successfully loaded */ private function load($key, $autoEnable = false) { $this->key = $key; $ok = $this->dataConnector->loadToolConsumer($this); if (!$ok) { $this->enabled = $autoEnable; } return $ok; } } MediaType/ToolProxy.php000064400000002402152267612520011123 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license GNU Lesser General Public License, version 3 () */ #[\AllowDynamicProperties] class ToolProxy { /** * Class constructor. * * @param ToolProvider $toolProvider Tool Provider object * @param ServiceDefinition $toolProxyService Tool Proxy service * @param string $secret Shared secret */ function __construct($toolProvider, $toolProxyService, $secret) { $contexts = array(); $this->{'@context'} = array_merge(array('http://purl.imsglobal.org/ctx/lti/v2/ToolProxy'), $contexts); $this->{'@type'} = 'ToolProxy'; $this->{'@id'} = "{$toolProxyService->endpoint}"; $this->lti_version = 'LTI-2p0'; $this->tool_consumer_profile = $toolProvider->consumer->profile->{'@id'}; $this->tool_profile = new ToolProfile($toolProvider); $this->security_contract = new SecurityContract($toolProvider, $secret); } } MediaType/SecurityContract.php000064400000005752152267612520012464 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license GNU Lesser General Public License, version 3 () */ #[\AllowDynamicProperties] class SecurityContract { /** * Class constructor. * * @param ToolProvider $toolProvider Tool Provider instance * @param string $secret Shared secret */ function __construct($toolProvider, $secret) { $tcContexts = array(); foreach ($toolProvider->consumer->profile->{'@context'} as $context) { if (is_object($context)) { $tcContexts = array_merge(get_object_vars($context), $tcContexts); } } $this->shared_secret = $secret; $toolServices = array(); foreach ($toolProvider->requiredServices as $requiredService) { foreach ($requiredService->formats as $format) { $service = $toolProvider->findService($format, $requiredService->actions); if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) { $id = $service->{'@id'}; $parts = explode(':', $id, 2); if (count($parts) > 1) { if (array_key_exists($parts[0], $tcContexts)) { $id = "{$tcContexts[$parts[0]]}{$parts[1]}"; } } $toolService = new \stdClass; $toolService->{'@type'} = 'RestServiceProfile'; $toolService->service = $id; $toolService->action = $requiredService->actions; $toolServices[$service->{'@id'}] = $toolService; } } } foreach ($toolProvider->optionalServices as $optionalService) { foreach ($optionalService->formats as $format) { $service = $toolProvider->findService($format, $optionalService->actions); if (($service !== false) && !array_key_exists($service->{'@id'}, $toolServices)) { $id = $service->{'@id'}; $parts = explode(':', $id, 2); if (count($parts) > 1) { if (array_key_exists($parts[0], $tcContexts)) { $id = "{$tcContexts[$parts[0]]}{$parts[1]}"; } } $toolService = new \stdClass; $toolService->{'@type'} = 'RestServiceProfile'; $toolService->service = $id; $toolService->action = $optionalService->actions; $toolServices[$service->{'@id'}] = $toolService; } } } $this->tool_service = array_values($toolServices); } } MediaType/ResourceHandler.php000064400000004014152267612520012232 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license GNU Lesser General Public License, version 3 () */ #[\AllowDynamicProperties] class ResourceHandler { /** * Class constructor. * * @param ToolProvider $toolProvider Tool Provider object * @param ProfileResourceHandler $resourceHandler Resource handler object */ function __construct($toolProvider, $resourceHandler) { $this->resource_type = new \stdClass; $this->resource_type->code = $resourceHandler->item->id; $this->resource_name = new \stdClass; $this->resource_name->default_value = $resourceHandler->item->name; $this->resource_name->key = "{$resourceHandler->item->id}.resource.name"; $this->description = new \stdClass; $this->description->default_value = $resourceHandler->item->description; $this->description->key = "{$resourceHandler->item->id}.resource.description"; $this->icon_info = new \stdClass; $this->icon_info->default_location = new \stdClass; $this->icon_info->default_location->path = $resourceHandler->icon; $this->icon_info->key = "{$resourceHandler->item->id}.icon.path"; $this->message = array(); foreach ($resourceHandler->requiredMessages as $message) { $this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered); } foreach ($resourceHandler->optionalMessages as $message) { if (in_array($message->type, $toolProvider->consumer->profile->capability_offered)) { $this->message[] = new Message($message, $toolProvider->consumer->profile->capability_offered); } } } } MediaType/ToolProfile.php000064400000007721152267612520011413 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license GNU Lesser General Public License, version 3 () */ #[\AllowDynamicProperties] class ToolProfile { public $product_instance; /** * Class constructor. * * @param ToolProvider $toolProvider Tool Provider object */ function __construct($toolProvider) { $this->lti_version = 'LTI-2p0'; if (!empty($toolProvider->product)) { $this->product_instance = new \stdClass; } if (!empty($toolProvider->product->id)) { $this->product_instance->guid = $toolProvider->product->id; } if (!empty($toolProvider->product->name)) { $this->product_instance->product_info = new \stdClass; $this->product_instance->product_info->product_name = new \stdClass; $this->product_instance->product_info->product_name->default_value = $toolProvider->product->name; $this->product_instance->product_info->product_name->key = 'tool.name'; } if (!empty($toolProvider->product->description)) { $this->product_instance->product_info->description = new \stdClass; $this->product_instance->product_info->description->default_value = $toolProvider->product->description; $this->product_instance->product_info->description->key = 'tool.description'; } if (!empty($toolProvider->product->url)) { $this->product_instance->guid = $toolProvider->product->url; } if (!empty($toolProvider->product->version)) { $this->product_instance->product_info->product_version = $toolProvider->product->version; } if (!empty($toolProvider->vendor)) { $this->product_instance->product_info->product_family = new \stdClass; $this->product_instance->product_info->product_family->vendor = new \stdClass; } if (!empty($toolProvider->vendor->id)) { $this->product_instance->product_info->product_family->vendor->code = $toolProvider->vendor->id; } if (!empty($toolProvider->vendor->name)) { $this->product_instance->product_info->product_family->vendor->vendor_name = new \stdClass; $this->product_instance->product_info->product_family->vendor->vendor_name->default_value = $toolProvider->vendor->name; $this->product_instance->product_info->product_family->vendor->vendor_name->key = 'tool.vendor.name'; } if (!empty($toolProvider->vendor->description)) { $this->product_instance->product_info->product_family->vendor->description = new \stdClass; $this->product_instance->product_info->product_family->vendor->description->default_value = $toolProvider->vendor->description; $this->product_instance->product_info->product_family->vendor->description->key = 'tool.vendor.description'; } if (!empty($toolProvider->vendor->url)) { $this->product_instance->product_info->product_family->vendor->website = $toolProvider->vendor->url; } if (!empty($toolProvider->vendor->timestamp)) { $this->product_instance->product_info->product_family->vendor->timestamp = date('Y-m-d\TH:i:sP', $toolProvider->vendor->timestamp); } $this->resource_handler = array(); foreach ($toolProvider->resourceHandlers as $resourceHandler) { $this->resource_handler[] = new ResourceHandler($toolProvider, $resourceHandler); } if (!empty($toolProvider->baseUrl)) { $this->base_url_choice = array(); $this->base_url_choice[] = new \stdClass; $this->base_url_choice[0]->default_base_url = $toolProvider->baseUrl; } } } MediaType/Message.php000064400000003000152267612520010523 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license GNU Lesser General Public License, version 3 () */ #[\AllowDynamicProperties] class Message { /** * Class constructor. * * @param Message $message Message object * @param array $capabilitiesOffered Capabilities offered */ function __construct($message, $capabilitiesOffered) { $this->message_type = $message->type; $this->path = $message->path; $this->enabled_capability = array(); foreach ($message->capabilities as $capability) { if (in_array($capability, $capabilitiesOffered)) { $this->enabled_capability[] = $capability; } } $this->parameter = array(); foreach ($message->constants as $name => $value) { $parameter = new \stdClass; $parameter->name = $name; $parameter->fixed = $value; $this->parameter[] = $parameter; } foreach ($message->variables as $name => $value) { if (in_array($value, $capabilitiesOffered)) { $parameter = new \stdClass; $parameter->name = $name; $parameter->variable = $value; $this->parameter[] = $parameter; } } } } User.php000064400000024231152267612520006205 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class User { /** * User's first name. * * @var string $firstname */ public $firstname = ''; /** * User's last name (surname or family name). * * @var string $lastname */ public $lastname = ''; /** * User's fullname. * * @var string $fullname */ public $fullname = ''; /** * User's email address. * * @var string $email */ public $email = ''; /** * User's image URI. * * @var string $image */ public $image = ''; /** * Roles for user. * * @var array $roles */ public $roles = array(); /** * Groups for user. * * @var array $groups */ public $groups = array(); /** * User's result sourcedid. * * @var string $ltiResultSourcedId */ public $ltiResultSourcedId = null; /** * Date/time the record was created. * * @var object $created */ public $created = null; /** * Date/time the record was last updated. * * @var object $updated */ public $updated = null; /** * Resource link object. * * @var ResourceLink $resourceLink */ private $resourceLink = null; /** * Resource link record ID. * * @var int $resourceLinkId */ private $resourceLinkId = null; /** * User record ID value. * * @var string $id */ private $id = null; /** * user ID as supplied in the last connection request. * * @var string $ltiUserId */ public $ltiUserId = null; /** * Data connector object or string. * * @var mixed $dataConnector */ private $dataConnector = null; /** * Class constructor. */ public function __construct() { $this->initialize(); } /** * Initialise the user. */ public function initialize() { $this->firstname = ''; $this->lastname = ''; $this->fullname = ''; $this->email = ''; $this->image = ''; $this->roles = array(); $this->groups = array(); $this->ltiResultSourcedId = null; $this->created = null; $this->updated = null; } /** * Initialise the user. * * Pseudonym for initialize(). */ public function initialise() { $this->initialize(); } /** * Save the user to the database. * * @return boolean True if the user object was successfully saved */ public function save() { if (!empty($this->ltiResultSourcedId) && !is_null($this->resourceLinkId)) { $ok = $this->getDataConnector()->saveUser($this); } else { $ok = true; } return $ok; } /** * Delete the user from the database. * * @return boolean True if the user object was successfully deleted */ public function delete() { $ok = $this->getDataConnector()->deleteUser($this); return $ok; } /** * Get resource link. * * @return ResourceLink Resource link object */ public function getResourceLink() { if (is_null($this->resourceLink) && !is_null($this->resourceLinkId)) { $this->resourceLink = ResourceLink::fromRecordId($this->resourceLinkId, $this->getDataConnector()); } return $this->resourceLink; } /** * Get record ID of user. * * @return int Record ID of user */ public function getRecordId() { return $this->id; } /** * Set record ID of user. * * @param int $id Record ID of user */ public function setRecordId($id) { $this->id = $id; } /** * Set resource link ID of user. * * @param int $resourceLinkId Resource link ID of user */ public function setResourceLinkId($resourceLinkId) { $this->resourceLinkId = $resourceLinkId; } /** * Get the data connector. * * @return mixed Data connector object or string */ public function getDataConnector() { return $this->dataConnector; } /** * Get the user ID (which may be a compound of the tool consumer and resource link IDs). * * @param int $idScope Scope to use for user ID (optional, default is null for consumer default setting) * * @return string User ID value */ public function getId($idScope = null) { if (empty($idScope)) { if (!is_null($this->resourceLink)) { $idScope = $this->resourceLink->getConsumer()->idScope; } else { $idScope = ToolProvider::ID_SCOPE_ID_ONLY; } } switch ($idScope) { case ToolProvider::ID_SCOPE_GLOBAL: $id = $this->getResourceLink()->getKey() . ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId; break; case ToolProvider::ID_SCOPE_CONTEXT: $id = $this->getResourceLink()->getKey(); if ($this->resourceLink->ltiContextId) { $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->resourceLink->ltiContextId; } $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId; break; case ToolProvider::ID_SCOPE_RESOURCE: $id = $this->getResourceLink()->getKey(); if ($this->resourceLink->ltiResourceLinkId) { $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->resourceLink->ltiResourceLinkId; } $id .= ToolProvider::ID_SCOPE_SEPARATOR . $this->ltiUserId; break; default: $id = $this->ltiUserId; break; } return $id; } /** * Set the user's name. * * @param string $firstname User's first name. * @param string $lastname User's last name. * @param string $fullname User's full name. */ public function setNames($firstname, $lastname, $fullname) { $names = array(0 => '', 1 => ''); if (!empty($fullname)) { $this->fullname = trim($fullname); $names = preg_split("/[\s]+/", $this->fullname, 2); } if (!empty($firstname)) { $this->firstname = trim($firstname); $names[0] = $this->firstname; } else if (!empty($names[0])) { $this->firstname = $names[0]; } else { $this->firstname = 'User'; } if (!empty($lastname)) { $this->lastname = trim($lastname); $names[1] = $this->lastname; } else if (!empty($names[1])) { $this->lastname = $names[1]; } else { $this->lastname = $this->ltiUserId; } if (empty($this->fullname)) { $this->fullname = "{$this->firstname} {$this->lastname}"; } } /** * Set the user's email address. * * @param string $email Email address value * @param string $defaultEmail Value to use if no email is provided (optional, default is none) */ public function setEmail($email, $defaultEmail = null) { if (!empty($email)) { $this->email = $email; } else if (!empty($defaultEmail)) { $this->email = $defaultEmail; if (substr($this->email, 0, 1) === '@') { $this->email = $this->getId() . $this->email; } } else { $this->email = ''; } } /** * Check if the user is an administrator (at any of the system, institution or context levels). * * @return boolean True if the user has a role of administrator */ public function isAdmin() { return $this->hasRole('Administrator') || $this->hasRole('urn:lti:sysrole:ims/lis/SysAdmin') || $this->hasRole('urn:lti:sysrole:ims/lis/Administrator') || $this->hasRole('urn:lti:instrole:ims/lis/Administrator'); } /** * Check if the user is staff. * * @return boolean True if the user has a role of instructor, contentdeveloper or teachingassistant */ public function isStaff() { return ($this->hasRole('Instructor') || $this->hasRole('ContentDeveloper') || $this->hasRole('TeachingAssistant')); } /** * Check if the user is a learner. * * @return boolean True if the user has a role of learner */ public function isLearner() { return $this->hasRole('Learner'); } /** * Load the user from the database. * * @param int $id Record ID of user * @param DataConnector $dataConnector Database connection object * * @return User User object */ public static function fromRecordId($id, $dataConnector) { $user = new User(); $user->dataConnector = $dataConnector; $user->load($id); return $user; } /** * Class constructor from resource link. * * @param ResourceLink $resourceLink Resource_Link object * @param string $ltiUserId User ID value * @return User */ public static function fromResourceLink($resourceLink, $ltiUserId) { $user = new User(); $user->resourceLink = $resourceLink; if (!is_null($resourceLink)) { $user->resourceLinkId = $resourceLink->getRecordId(); $user->dataConnector = $resourceLink->getDataConnector(); } $user->ltiUserId = $ltiUserId; if (!empty($ltiUserId)) { $user->load(); } return $user; } ### ### PRIVATE METHODS ### /** * Check whether the user has a specified role name. * * @param string $role Name of role * * @return boolean True if the user has the specified role */ private function hasRole($role) { if (substr($role, 0, 4) !== 'urn:') { $role = 'urn:lti:role:ims/lis/' . $role; } return in_array($role, $this->roles); } /** * Load the user from the database. * * @param int $id Record ID of user (optional, default is null) * * @return boolean True if the user object was successfully loaded */ private function load($id = null) { $this->initialize(); $this->id = $id; $dataConnector = $this->getDataConnector(); if (!is_null($dataConnector)) { return $dataConnector->loadUser($this); } return false; } } ToolProvider.php000064400000141701152267612520007721 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license GNU Lesser General Public License, version 3 () */ #[\AllowDynamicProperties] class ToolProvider { /** * Default connection error message. */ const CONNECTION_ERROR_MESSAGE = 'Sorry, there was an error connecting you to the application.'; /** * LTI version 1 for messages. */ const LTI_VERSION1 = 'LTI-1p0'; /** * LTI version 2 for messages. */ const LTI_VERSION2 = 'LTI-2p0'; /** * Use ID value only. */ const ID_SCOPE_ID_ONLY = 0; /** * Prefix an ID with the consumer key. */ const ID_SCOPE_GLOBAL = 1; /** * Prefix the ID with the consumer key and context ID. */ const ID_SCOPE_CONTEXT = 2; /** * Prefix the ID with the consumer key and resource ID. */ const ID_SCOPE_RESOURCE = 3; /** * Character used to separate each element of an ID. */ const ID_SCOPE_SEPARATOR = ':'; /** * Permitted LTI versions for messages. */ private static $LTI_VERSIONS = array(self::LTI_VERSION1, self::LTI_VERSION2); /** * List of supported message types and associated class methods. */ private static $MESSAGE_TYPES = array('basic-lti-launch-request' => 'onLaunch', 'ContentItemSelectionRequest' => 'onContentItem', 'ToolProxyRegistrationRequest' => 'register'); /** * List of supported message types and associated class methods * * @var array $METHOD_NAMES */ private static $METHOD_NAMES = array('basic-lti-launch-request' => 'onLaunch', 'ContentItemSelectionRequest' => 'onContentItem', 'ToolProxyRegistrationRequest' => 'onRegister'); /** * Names of LTI parameters to be retained in the consumer settings property. * * @var array $LTI_CONSUMER_SETTING_NAMES */ private static $LTI_CONSUMER_SETTING_NAMES = array('custom_tc_profile_url', 'custom_system_setting_url'); /** * Names of LTI parameters to be retained in the context settings property. * * @var array $LTI_CONTEXT_SETTING_NAMES */ private static $LTI_CONTEXT_SETTING_NAMES = array('custom_context_setting_url', 'custom_lineitems_url', 'custom_results_url', 'custom_context_memberships_url'); /** * Names of LTI parameters to be retained in the resource link settings property. * * @var array $LTI_RESOURCE_LINK_SETTING_NAMES */ private static $LTI_RESOURCE_LINK_SETTING_NAMES = array('lis_course_section_sourcedid', 'lis_result_sourcedid', 'lis_outcome_service_url', 'ext_ims_lis_basic_outcome_url', 'ext_ims_lis_resultvalue_sourcedids', 'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url', 'ext_ims_lti_tool_setting', 'ext_ims_lti_tool_setting_id', 'ext_ims_lti_tool_setting_url', 'custom_link_setting_url', 'custom_lineitem_url', 'custom_result_url'); /** * Names of LTI custom parameter substitution variables (or capabilities) and their associated default message parameter names. * * @var array $CUSTOM_SUBSTITUTION_VARIABLES */ private static $CUSTOM_SUBSTITUTION_VARIABLES = array('User.id' => 'user_id', 'User.image' => 'user_image', 'User.username' => 'username', 'User.scope.mentor' => 'role_scope_mentor', 'Membership.role' => 'roles', 'Person.sourcedId' => 'lis_person_sourcedid', 'Person.name.full' => 'lis_person_name_full', 'Person.name.family' => 'lis_person_name_family', 'Person.name.given' => 'lis_person_name_given', 'Person.email.primary' => 'lis_person_contact_email_primary', 'Context.id' => 'context_id', 'Context.type' => 'context_type', 'Context.title' => 'context_title', 'Context.label' => 'context_label', 'CourseOffering.sourcedId' => 'lis_course_offering_sourcedid', 'CourseSection.sourcedId' => 'lis_course_section_sourcedid', 'CourseSection.label' => 'context_label', 'CourseSection.title' => 'context_title', 'ResourceLink.id' => 'resource_link_id', 'ResourceLink.title' => 'resource_link_title', 'ResourceLink.description' => 'resource_link_description', 'Result.sourcedId' => 'lis_result_sourcedid', 'BasicOutcome.url' => 'lis_outcome_service_url', 'ToolConsumerProfile.url' => 'custom_tc_profile_url', 'ToolProxy.url' => 'tool_proxy_url', 'ToolProxy.custom.url' => 'custom_system_setting_url', 'ToolProxyBinding.custom.url' => 'custom_context_setting_url', 'LtiLink.custom.url' => 'custom_link_setting_url', 'LineItems.url' => 'custom_lineitems_url', 'LineItem.url' => 'custom_lineitem_url', 'Results.url' => 'custom_results_url', 'Result.url' => 'custom_result_url', 'ToolProxyBinding.memberships.url' => 'custom_context_memberships_url'); /** * True if the last request was successful. * * @var boolean $ok */ public $ok = true; /** * Tool Consumer object. * * @var ToolConsumer $consumer */ public $consumer = null; /** * Return URL provided by tool consumer. * * @var string $returnUrl */ public $returnUrl = null; /** * User object. * * @var User $user */ public $user = null; /** * Resource link object. * * @var ResourceLink $resourceLink */ public $resourceLink = null; /** * Context object. * * @var Context $context */ public $context = null; /** * Data connector object. * * @var DataConnector $dataConnector */ public $dataConnector = null; /** * Default email domain. * * @var string $defaultEmail */ public $defaultEmail = ''; /** * Scope to use for user IDs. * * @var int $idScope */ public $idScope = self::ID_SCOPE_ID_ONLY; /** * Whether shared resource link arrangements are permitted. * * @var boolean $allowSharing */ public $allowSharing = false; /** * Message for last request processed * * @var string $message */ public $message = self::CONNECTION_ERROR_MESSAGE; /** * Error message for last request processed. * * @var string $reason */ public $reason = null; /** * Details for error message relating to last request processed. * * @var array $details */ public $details = array(); /** * Base URL for tool provider service * * @var string $baseUrl */ public $baseUrl = null; /** * Vendor details * * @var Item $vendor */ public $vendor = null; /** * Product details * * @var Item $product */ public $product = null; /** * Services required by Tool Provider * * @var array $requiredServices */ public $requiredServices = null; /** * Optional services used by Tool Provider * * @var array $optionalServices */ public $optionalServices = null; /** * Resource handlers for Tool Provider * * @var array $resourceHandlers */ public $resourceHandlers = null; /** * URL to redirect user to on successful completion of the request. * * @var string $redirectUrl */ protected $redirectUrl = null; /** * URL to redirect user to on successful completion of the request. * * @var string $mediaTypes */ protected $mediaTypes = null; /** * URL to redirect user to on successful completion of the request. * * @var string $documentTargets */ protected $documentTargets = null; /** * HTML to be displayed on a successful completion of the request. * * @var string $output */ protected $output = null; /** * HTML to be displayed on an unsuccessful completion of the request and no return URL is available. * * @var string $errorOutput */ protected $errorOutput = null; /** * Whether debug messages explaining the cause of errors are to be returned to the tool consumer. * * @var boolean $debugMode */ protected $debugMode = false; /** * Callback functions for handling requests. * * @var array $callbackHandler */ private $callbackHandler = null; /** * LTI parameter constraints for auto validation checks. * * @var array $constraints */ private $constraints = null; /** * Class constructor * * @param DataConnector $dataConnector Object containing a database connection object */ function __construct($dataConnector) { $this->constraints = array(); $this->dataConnector = $dataConnector; $this->ok = !is_null($this->dataConnector); // Set debug mode $this->debugMode = isset($_POST['custom_debug']) && (strtolower($_POST['custom_debug']) === 'true'); // Set return URL if available if (isset($_POST['launch_presentation_return_url'])) { $this->returnUrl = $_POST['launch_presentation_return_url']; } else if (isset($_POST['content_item_return_url'])) { $this->returnUrl = $_POST['content_item_return_url']; } $this->vendor = new Profile\Item(); $this->product = new Profile\Item(); $this->requiredServices = array(); $this->optionalServices = array(); $this->resourceHandlers = array(); } /** * Process an incoming request */ public function handleRequest() { if ($this->ok) { if ($this->authenticate()) { $this->doCallback(); } } $this->result(); } /** * Add a parameter constraint to be checked on launch * * @param string $name Name of parameter to be checked * @param boolean $required True if parameter is required (optional, default is true) * @param int $maxLength Maximum permitted length of parameter value (optional, default is null) * @param array $messageTypes Array of message types to which the constraint applies (optional, default is all) */ public function setParameterConstraint($name, $required = true, $maxLength = null, $messageTypes = null) { $name = trim($name); if (strlen($name) > 0) { $this->constraints[$name] = array('required' => $required, 'max_length' => $maxLength, 'messages' => $messageTypes); } } /** * Get an array of defined tool consumers * * @return array Array of ToolConsumer objects */ public function getConsumers() { return $this->dataConnector->getToolConsumers(); } /** * Find an offered service based on a media type and HTTP action(s) * * @param string $format Media type required * @param array $methods Array of HTTP actions required * * @return object The service object */ public function findService($format, $methods) { $found = false; $services = $this->consumer->profile->service_offered; if (is_array($services)) { $n = -1; foreach ($services as $service) { $n++; if (!is_array($service->format) || !in_array($format, $service->format)) { continue; } $missing = array(); foreach ($methods as $method) { if (!is_array($service->action) || !in_array($method, $service->action)) { $missing[] = $method; } } $methods = $missing; if (count($methods) <= 0) { $found = $service; break; } } } return $found; } /** * Send the tool proxy to the Tool Consumer * * @return boolean True if the tool proxy was accepted */ public function doToolProxyService() { // Create tool proxy $toolProxyService = $this->findService('application/vnd.ims.lti.v2.toolproxy+json', array('POST')); $secret = DataConnector::getRandomString(12); $toolProxy = new MediaType\ToolProxy($this, $toolProxyService, $secret); $http = $this->consumer->doServiceRequest($toolProxyService, 'POST', 'application/vnd.ims.lti.v2.toolproxy+json', json_encode($toolProxy)); $ok = $http->ok && ($http->status == 201) && isset($http->responseJson->tool_proxy_guid) && (strlen($http->responseJson->tool_proxy_guid) > 0); if ($ok) { $this->consumer->setKey($http->responseJson->tool_proxy_guid); $this->consumer->secret = $toolProxy->security_contract->shared_secret; $this->consumer->toolProxy = json_encode($toolProxy); $this->consumer->save(); } return $ok; } /** * Get an array of fully qualified user roles * * @param mixed $roles Comma-separated list of roles or array of roles * * @return array Array of roles */ public static function parseRoles($roles) { if (!is_array($roles)) { $roles = explode(',', $roles); } $parsedRoles = array(); foreach ($roles as $role) { $role = trim($role); if (!empty($role)) { if (substr($role, 0, 4) !== 'urn:') { $role = 'urn:lti:role:ims/lis/' . $role; } $parsedRoles[] = $role; } } return $parsedRoles; } /** * Generate a web page containing an auto-submitted form of parameters. * * @param string $url URL to which the form should be submitted * @param array $params Array of form parameters * @param string $target Name of target (optional) * @return string */ public static function sendForm($url, $params, $target = '') { $page = <<< EOD IMS LTI message
EOD; foreach($params as $key => $value ) { $key = htmlentities($key, ENT_COMPAT | ENT_HTML401, 'UTF-8'); $value = htmlentities($value, ENT_COMPAT | ENT_HTML401, 'UTF-8'); $page .= <<< EOD EOD; } $page .= <<< EOD
EOD; return $page; } ### ### PROTECTED METHODS ### /** * Process a valid launch request * * @return boolean True if no error */ protected function onLaunch() { $this->onError(); } /** * Process a valid content-item request * * @return boolean True if no error */ protected function onContentItem() { $this->onError(); } /** * Process a valid tool proxy registration request * * @return boolean True if no error */ protected function onRegister() { $this->onError(); } /** * Process a response to an invalid request * * @return boolean True if no further error processing required */ protected function onError() { $this->doCallback('onError'); } ### ### PRIVATE METHODS ### /** * Call any callback function for the requested action. * * This function may set the redirect_url and output properties. * * @return boolean True if no error reported */ private function doCallback($method = null) { $callback = $method; if (is_null($callback)) { $callback = self::$METHOD_NAMES[$_POST['lti_message_type']]; } if (method_exists($this, $callback)) { $result = $this->$callback(); } else if (is_null($method) && $this->ok) { $this->ok = false; $this->reason = "Message type not supported: {$_POST['lti_message_type']}"; } if ($this->ok && ($_POST['lti_message_type'] == 'ToolProxyRegistrationRequest')) { $this->consumer->save(); } } /** * Perform the result of an action. * * This function may redirect the user to another URL rather than returning a value. * * @return string Output to be displayed (redirection, or display HTML or message) */ private function result() { $ok = false; if (!$this->ok) { $ok = $this->onError(); } if (!$ok) { if (!$this->ok) { // If not valid, return an error message to the tool consumer if a return URL is provided if (!empty($this->returnUrl)) { $errorUrl = $this->returnUrl; if (strpos($errorUrl, '?') === false) { $errorUrl .= '?'; } else { $errorUrl .= '&'; } if ($this->debugMode && !is_null($this->reason)) { $errorUrl .= 'lti_errormsg=' . urlencode("Debug error: $this->reason"); } else { $errorUrl .= 'lti_errormsg=' . urlencode($this->message); if (!is_null($this->reason)) { $errorUrl .= '<i_errorlog=' . urlencode("Debug error: $this->reason"); } } if (!is_null($this->consumer) && isset($_POST['lti_message_type']) && ($_POST['lti_message_type'] === 'ContentItemSelectionRequest')) { $formParams = array(); if (isset($_POST['data'])) { $formParams['data'] = $_POST['data']; } $version = (isset($_POST['lti_version'])) ? $_POST['lti_version'] : self::LTI_VERSION1; $formParams = $this->consumer->signParameters($errorUrl, 'ContentItemSelection', $version, $formParams); $page = self::sendForm($errorUrl, $formParams); echo $page; } else { header("Location: {$errorUrl}"); } exit; } else { if (!is_null($this->errorOutput)) { echo $this->errorOutput; } else if ($this->debugMode && !empty($this->reason)) { echo "Debug error: {$this->reason}"; } else { echo "Error: {$this->message}"; } } } else if (!is_null($this->redirectUrl)) { header("Location: {$this->redirectUrl}"); exit; } else if (!is_null($this->output)) { echo $this->output; } } } /** * Check the authenticity of the LTI launch request. * * The consumer, resource link and user objects will be initialised if the request is valid. * * @return boolean True if the request has been successfully validated. */ private function authenticate() { // Get the consumer $doSaveConsumer = false; // Check all required launch parameters $this->ok = isset($_POST['lti_message_type']) && array_key_exists($_POST['lti_message_type'], self::$MESSAGE_TYPES); if (!$this->ok) { $this->reason = 'Invalid or missing lti_message_type parameter.'; } if ($this->ok) { $this->ok = isset($_POST['lti_version']) && in_array($_POST['lti_version'], self::$LTI_VERSIONS); if (!$this->ok) { $this->reason = 'Invalid or missing lti_version parameter.'; } } if ($this->ok) { if ($_POST['lti_message_type'] === 'basic-lti-launch-request') { $this->ok = isset($_POST['resource_link_id']) && (strlen(trim($_POST['resource_link_id'])) > 0); if (!$this->ok) { $this->reason = 'Missing resource link ID.'; } } else if ($_POST['lti_message_type'] === 'ContentItemSelectionRequest') { if (isset($_POST['accept_media_types']) && (strlen(trim($_POST['accept_media_types'])) > 0)) { $mediaTypes = array_filter(explode(',', str_replace(' ', '', $_POST['accept_media_types'])), 'strlen'); $mediaTypes = array_unique($mediaTypes); $this->ok = count($mediaTypes) > 0; if (!$this->ok) { $this->reason = 'No accept_media_types found.'; } else { $this->mediaTypes = $mediaTypes; } } else { $this->ok = false; } if ($this->ok && isset($_POST['accept_presentation_document_targets']) && (strlen(trim($_POST['accept_presentation_document_targets'])) > 0)) { $documentTargets = array_filter(explode(',', str_replace(' ', '', $_POST['accept_presentation_document_targets'])), 'strlen'); $documentTargets = array_unique($documentTargets); $this->ok = count($documentTargets) > 0; if (!$this->ok) { $this->reason = 'Missing or empty accept_presentation_document_targets parameter.'; } else { foreach ($documentTargets as $documentTarget) { $this->ok = $this->checkValue($documentTarget, array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay', 'none'), 'Invalid value in accept_presentation_document_targets parameter: %s.'); if (!$this->ok) { break; } } if ($this->ok) { $this->documentTargets = $documentTargets; } } } else { $this->ok = false; } if ($this->ok) { $this->ok = isset($_POST['content_item_return_url']) && (strlen(trim($_POST['content_item_return_url'])) > 0); if (!$this->ok) { $this->reason = 'Missing content_item_return_url parameter.'; } } } else if ($_POST['lti_message_type'] == 'ToolProxyRegistrationRequest') { $this->ok = ((isset($_POST['reg_key']) && (strlen(trim($_POST['reg_key'])) > 0)) && (isset($_POST['reg_password']) && (strlen(trim($_POST['reg_password'])) > 0)) && (isset($_POST['tc_profile_url']) && (strlen(trim($_POST['tc_profile_url'])) > 0)) && (isset($_POST['launch_presentation_return_url']) && (strlen(trim($_POST['launch_presentation_return_url'])) > 0))); if ($this->debugMode && !$this->ok) { $this->reason = 'Missing message parameters.'; } } } $now = time(); // Check consumer key if ($this->ok && ($_POST['lti_message_type'] != 'ToolProxyRegistrationRequest')) { $this->ok = isset($_POST['oauth_consumer_key']); if (!$this->ok) { $this->reason = 'Missing consumer key.'; } if ($this->ok) { $this->consumer = new ToolConsumer($_POST['oauth_consumer_key'], $this->dataConnector); $this->ok = !is_null($this->consumer->created); if (!$this->ok) { $this->reason = 'Invalid consumer key.'; } } if ($this->ok) { $today = date('Y-m-d', $now); if (is_null($this->consumer->lastAccess)) { $doSaveConsumer = true; } else { $last = date('Y-m-d', $this->consumer->lastAccess); $doSaveConsumer = $doSaveConsumer || ($last !== $today); } $this->consumer->last_access = $now; try { $store = new OAuthDataStore($this); $server = new OAuth\OAuthServer($store); $method = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); $server->add_signature_method($method); $request = OAuth\OAuthRequest::from_request(); $res = $server->verify_request($request); } catch (\Exception $e) { $this->ok = false; if (empty($this->reason)) { if ($this->debugMode) { $consumer = new OAuth\OAuthConsumer($this->consumer->getKey(), $this->consumer->secret); $signature = $request->build_signature($method, $consumer, false); $this->reason = $e->getMessage(); if (empty($this->reason)) { $this->reason = 'OAuth exception'; } $this->details[] = 'Timestamp: ' . time(); $this->details[] = "Signature: {$signature}"; $this->details[] = "Base string: {$request->base_string}]"; } else { $this->reason = 'OAuth signature check failed - perhaps an incorrect secret or timestamp.'; } } } } if ($this->ok) { $today = date('Y-m-d', $now); if (is_null($this->consumer->lastAccess)) { $doSaveConsumer = true; } else { $last = date('Y-m-d', $this->consumer->lastAccess); $doSaveConsumer = $doSaveConsumer || ($last !== $today); } $this->consumer->last_access = $now; if ($this->consumer->protected) { if (!is_null($this->consumer->consumerGuid)) { $this->ok = empty($_POST['tool_consumer_instance_guid']) || ($this->consumer->consumerGuid === $_POST['tool_consumer_instance_guid']); if (!$this->ok) { $this->reason = 'Request is from an invalid tool consumer.'; } } } if ($this->ok) { $this->ok = $this->consumer->enabled; if (!$this->ok) { $this->reason = 'Tool consumer has not been enabled by the tool provider.'; } } if ($this->ok) { $this->ok = is_null($this->consumer->enableFrom) || ($this->consumer->enableFrom <= $now); if ($this->ok) { $this->ok = is_null($this->consumer->enableUntil) || ($this->consumer->enableUntil > $now); if (!$this->ok) { $this->reason = 'Tool consumer access has expired.'; } } else { $this->reason = 'Tool consumer access is not yet available.'; } } } // Validate other message parameter values if ($this->ok) { if ($_POST['lti_message_type'] === 'ContentItemSelectionRequest') { if (isset($_POST['accept_unsigned'])) { $this->ok = $this->checkValue($_POST['accept_unsigned'], array('true', 'false'), 'Invalid value for accept_unsigned parameter: %s.'); } if ($this->ok && isset($_POST['accept_multiple'])) { $this->ok = $this->checkValue($_POST['accept_multiple'], array('true', 'false'), 'Invalid value for accept_multiple parameter: %s.'); } if ($this->ok && isset($_POST['accept_copy_advice'])) { $this->ok = $this->checkValue($_POST['accept_copy_advice'], array('true', 'false'), 'Invalid value for accept_copy_advice parameter: %s.'); } if ($this->ok && isset($_POST['auto_create'])) { $this->ok = $this->checkValue($_POST['auto_create'], array('true', 'false'), 'Invalid value for auto_create parameter: %s.'); } if ($this->ok && isset($_POST['can_confirm'])) { $this->ok = $this->checkValue($_POST['can_confirm'], array('true', 'false'), 'Invalid value for can_confirm parameter: %s.'); } } else if (isset($_POST['launch_presentation_document_target'])) { $this->ok = $this->checkValue($_POST['launch_presentation_document_target'], array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay'), 'Invalid value for launch_presentation_document_target parameter: %s.'); } } } if ($this->ok && ($_POST['lti_message_type'] === 'ToolProxyRegistrationRequest')) { $this->ok = $_POST['lti_version'] == self::LTI_VERSION2; if (!$this->ok) { $this->reason = 'Invalid lti_version parameter'; } if ($this->ok) { $http = new HTTPMessage($_POST['tc_profile_url'], 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json'); $this->ok = $http->send(); if (!$this->ok) { $this->reason = 'Tool consumer profile not accessible.'; } else { $tcProfile = json_decode($http->response); $this->ok = !is_null($tcProfile); if (!$this->ok) { $this->reason = 'Invalid JSON in tool consumer profile.'; } } } // Check for required capabilities if ($this->ok) { $this->consumer = new ToolConsumer($_POST['reg_key'], $this->dataConnector); $this->consumer->profile = $tcProfile; $capabilities = $this->consumer->profile->capability_offered; $missing = array(); foreach ($this->resourceHandlers as $resourceHandler) { foreach ($resourceHandler->requiredMessages as $message) { if (!in_array($message->type, $capabilities)) { $missing[$message->type] = true; } } } foreach ($this->constraints as $name => $constraint) { if ($constraint['required']) { if (!in_array($name, $capabilities) && !in_array($name, array_flip($capabilities))) { $missing[$name] = true; } } } if (!empty($missing)) { ksort($missing); $this->reason = 'Required capability not offered - \'' . implode('\', \'', array_keys($missing)) . '\''; $this->ok = false; } } // Check for required services if ($this->ok) { foreach ($this->requiredServices as $service) { foreach ($service->formats as $format) { if (!$this->findService($format, $service->actions)) { if ($this->ok) { $this->reason = 'Required service(s) not offered - '; $this->ok = false; } else { $this->reason .= ', '; } $this->reason .= "'{$format}' [" . implode(', ', $service->actions) . ']'; } } } } if ($this->ok) { if ($_POST['lti_message_type'] === 'ToolProxyRegistrationRequest') { $this->consumer->profile = $tcProfile; $this->consumer->secret = $_POST['reg_password']; $this->consumer->ltiVersion = $_POST['lti_version']; $this->consumer->name = $tcProfile->product_instance->service_owner->service_owner_name->default_value; $this->consumer->consumerName = $this->consumer->name; $this->consumer->consumerVersion = "{$tcProfile->product_instance->product_info->product_family->code}-{$tcProfile->product_instance->product_info->product_version}"; $this->consumer->consumerGuid = $tcProfile->product_instance->guid; $this->consumer->enabled = true; $this->consumer->protected = true; $doSaveConsumer = true; } } } else if ($this->ok && !empty($_POST['custom_tc_profile_url']) && empty($this->consumer->profile)) { $http = new HTTPMessage($_POST['custom_tc_profile_url'], 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json'); if ($http->send()) { $tcProfile = json_decode($http->response); if (!is_null($tcProfile)) { $this->consumer->profile = $tcProfile; $doSaveConsumer = true; } } } // Validate message parameter constraints if ($this->ok) { $invalidParameters = array(); foreach ($this->constraints as $name => $constraint) { if (empty($constraint['messages']) || in_array($_POST['lti_message_type'], $constraint['messages'])) { $ok = true; if ($constraint['required']) { if (!isset($_POST[$name]) || (strlen(trim($_POST[$name])) <= 0)) { $invalidParameters[] = "{$name} (missing)"; $ok = false; } } if ($ok && !is_null($constraint['max_length']) && isset($_POST[$name])) { if (strlen(trim($_POST[$name])) > $constraint['max_length']) { $invalidParameters[] = "{$name} (too long)"; } } } } if (count($invalidParameters) > 0) { $this->ok = false; if (empty($this->reason)) { $this->reason = 'Invalid parameter(s): ' . implode(', ', $invalidParameters) . '.'; } } } if ($this->ok) { // Set the request context if (isset($_POST['context_id'])) { $this->context = Context::fromConsumer($this->consumer, trim($_POST['context_id'])); $title = ''; if (isset($_POST['context_title'])) { $title = trim($_POST['context_title']); } if (empty($title)) { $title = "Course {$this->context->getId()}"; } if (isset($_POST['context_type'])) { $this->context->type = trim($_POST['context_type']); } $this->context->title = $title; } // Set the request resource link if (isset($_POST['resource_link_id'])) { $contentItemId = ''; if (isset($_POST['custom_content_item_id'])) { $contentItemId = $_POST['custom_content_item_id']; } $this->resourceLink = ResourceLink::fromConsumer($this->consumer, trim($_POST['resource_link_id']), $contentItemId); if (!empty($this->context)) { $this->resourceLink->setContextId($this->context->getRecordId()); } $title = ''; if (isset($_POST['resource_link_title'])) { $title = trim($_POST['resource_link_title']); } if (empty($title)) { $title = "Resource {$this->resourceLink->getId()}"; } $this->resourceLink->title = $title; // Delete any existing custom parameters foreach ($this->consumer->getSettings() as $name => $value) { if (strpos($name, 'custom_') === 0) { $this->consumer->setSetting($name); $doSaveConsumer = true; } } if (!empty($this->context)) { foreach ($this->context->getSettings() as $name => $value) { if (strpos($name, 'custom_') === 0) { $this->context->setSetting($name); } } } foreach ($this->resourceLink->getSettings() as $name => $value) { if (strpos($name, 'custom_') === 0) { $this->resourceLink->setSetting($name); } } // Save LTI parameters foreach (self::$LTI_CONSUMER_SETTING_NAMES as $name) { if (isset($_POST[$name])) { $this->consumer->setSetting($name, $_POST[$name]); } else { $this->consumer->setSetting($name); } } if (!empty($this->context)) { foreach (self::$LTI_CONTEXT_SETTING_NAMES as $name) { if (isset($_POST[$name])) { $this->context->setSetting($name, $_POST[$name]); } else { $this->context->setSetting($name); } } } foreach (self::$LTI_RESOURCE_LINK_SETTING_NAMES as $name) { if (isset($_POST[$name])) { $this->resourceLink->setSetting($name, $_POST[$name]); } else { $this->resourceLink->setSetting($name); } } // Save other custom parameters foreach ($_POST as $name => $value) { if ((strpos($name, 'custom_') === 0) && !in_array($name, array_merge(self::$LTI_CONSUMER_SETTING_NAMES, self::$LTI_CONTEXT_SETTING_NAMES, self::$LTI_RESOURCE_LINK_SETTING_NAMES))) { $this->resourceLink->setSetting($name, $value); } } } // Set the user instance $userId = ''; if (isset($_POST['user_id'])) { $userId = trim($_POST['user_id']); } $this->user = User::fromResourceLink($this->resourceLink, $userId); // Set the user name $firstname = (isset($_POST['lis_person_name_given'])) ? $_POST['lis_person_name_given'] : ''; $lastname = (isset($_POST['lis_person_name_family'])) ? $_POST['lis_person_name_family'] : ''; $fullname = (isset($_POST['lis_person_name_full'])) ? $_POST['lis_person_name_full'] : ''; $this->user->setNames($firstname, $lastname, $fullname); // Set the user email $email = (isset($_POST['lis_person_contact_email_primary'])) ? $_POST['lis_person_contact_email_primary'] : ''; $this->user->setEmail($email, $this->defaultEmail); // Set the user image URI if (isset($_POST['user_image'])) { $this->user->image = $_POST['user_image']; } // Set the user roles if (isset($_POST['roles'])) { $this->user->roles = self::parseRoles($_POST['roles']); } // Initialise the consumer and check for changes $this->consumer->defaultEmail = $this->defaultEmail; if ($this->consumer->ltiVersion !== $_POST['lti_version']) { $this->consumer->ltiVersion = $_POST['lti_version']; $doSaveConsumer = true; } if (isset($_POST['tool_consumer_instance_name'])) { if ($this->consumer->consumerName !== $_POST['tool_consumer_instance_name']) { $this->consumer->consumerName = $_POST['tool_consumer_instance_name']; $doSaveConsumer = true; } } if (isset($_POST['tool_consumer_info_product_family_code'])) { $version = $_POST['tool_consumer_info_product_family_code']; if (isset($_POST['tool_consumer_info_version'])) { $version .= "-{$_POST['tool_consumer_info_version']}"; } // do not delete any existing consumer version if none is passed if ($this->consumer->consumerVersion !== $version) { $this->consumer->consumerVersion = $version; $doSaveConsumer = true; } } else if (isset($_POST['ext_lms']) && ($this->consumer->consumerName !== $_POST['ext_lms'])) { $this->consumer->consumerVersion = $_POST['ext_lms']; $doSaveConsumer = true; } if (isset($_POST['tool_consumer_instance_guid'])) { if (is_null($this->consumer->consumerGuid)) { $this->consumer->consumerGuid = $_POST['tool_consumer_instance_guid']; $doSaveConsumer = true; } else if (!$this->consumer->protected) { $doSaveConsumer = ($this->consumer->consumerGuid !== $_POST['tool_consumer_instance_guid']); if ($doSaveConsumer) { $this->consumer->consumerGuid = $_POST['tool_consumer_instance_guid']; } } } if (isset($_POST['launch_presentation_css_url'])) { if ($this->consumer->cssPath !== $_POST['launch_presentation_css_url']) { $this->consumer->cssPath = $_POST['launch_presentation_css_url']; $doSaveConsumer = true; } } else if (isset($_POST['ext_launch_presentation_css_url']) && ($this->consumer->cssPath !== $_POST['ext_launch_presentation_css_url'])) { $this->consumer->cssPath = $_POST['ext_launch_presentation_css_url']; $doSaveConsumer = true; } else if (!empty($this->consumer->cssPath)) { $this->consumer->cssPath = null; $doSaveConsumer = true; } } // Persist changes to consumer if ($doSaveConsumer) { $this->consumer->save(); } if ($this->ok && isset($this->context)) { $this->context->save(); } if ($this->ok && isset($this->resourceLink)) { // Check if a share arrangement is in place for this resource link $this->ok = $this->checkForShare(); // Persist changes to resource link $this->resourceLink->save(); // Save the user instance if (isset($_POST['lis_result_sourcedid'])) { if ($this->user->ltiResultSourcedId !== $_POST['lis_result_sourcedid']) { $this->user->ltiResultSourcedId = $_POST['lis_result_sourcedid']; $this->user->save(); } } else if (!empty($this->user->ltiResultSourcedId)) { $this->user->ltiResultSourcedId = ''; $this->user->save(); } } return $this->ok; } /** * Check if a share arrangement is in place. * * @return boolean True if no error is reported */ private function checkForShare() { $ok = true; $doSaveResourceLink = true; $id = $this->resourceLink->primaryResourceLinkId; $shareRequest = isset($_POST['custom_share_key']) && !empty($_POST['custom_share_key']); if ($shareRequest) { if (!$this->allowSharing) { $ok = false; $this->reason = 'Your sharing request has been refused because sharing is not being permitted.'; } else { // Check if this is a new share key $shareKey = new ResourceLinkShareKey($this->resourceLink, $_POST['custom_share_key']); if (!is_null($shareKey->primaryConsumerKey) && !is_null($shareKey->primaryResourceLinkId)) { // Update resource link with sharing primary resource link details $key = $shareKey->primaryConsumerKey; $id = $shareKey->primaryResourceLinkId; $ok = ($key !== $this->consumer->getKey()) || ($id != $this->resourceLink->getId()); if ($ok) { $this->resourceLink->primaryConsumerKey = $key; $this->resourceLink->primaryResourceLinkId = $id; $this->resourceLink->shareApproved = $shareKey->autoApprove; $ok = $this->resourceLink->save(); if ($ok) { $doSaveResourceLink = false; $this->user->getResourceLink()->primaryConsumerKey = $key; $this->user->getResourceLink()->primaryResourceLinkId = $id; $this->user->getResourceLink()->shareApproved = $shareKey->autoApprove; $this->user->getResourceLink()->updated = time(); // Remove share key $shareKey->delete(); } else { $this->reason = 'An error occurred initialising your share arrangement.'; } } else { $this->reason = 'It is not possible to share your resource link with yourself.'; } } if ($ok) { $ok = !is_null($key); if (!$ok) { $this->reason = 'You have requested to share a resource link but none is available.'; } else { $ok = (!is_null($this->user->getResourceLink()->shareApproved) && $this->user->getResourceLink()->shareApproved); if (!$ok) { $this->reason = 'Your share request is waiting to be approved.'; } } } } } else { // Check no share is in place $ok = is_null($id); if (!$ok) { $this->reason = 'You have not requested to share a resource link but an arrangement is currently in place.'; } } // Look up primary resource link if ($ok && !is_null($id)) { $consumer = new ToolConsumer($key, $this->dataConnector); $ok = !is_null($consumer->created); if ($ok) { $resourceLink = ResourceLink::fromConsumer($consumer, $id); $ok = !is_null($resourceLink->created); } if ($ok) { if ($doSaveResourceLink) { $this->resourceLink->save(); } $this->resourceLink = $resourceLink; } else { $this->reason = 'Unable to load resource link being shared.'; } } return $ok; } /** * Validate a parameter value from an array of permitted values. * * @return boolean True if value is valid */ private function checkValue($value, $values, $reason) { $ok = in_array($value, $values); if (!$ok && !empty($reason)) { $this->reason = sprintf($reason, $value); } return $ok; } } ConsumerNonce.php000064400000003617152267612520010052 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ConsumerNonce { /** * Maximum age nonce values will be retained for (in minutes). */ const MAX_NONCE_AGE = 30; // in minutes /** * Date/time when the nonce value expires. * * @var int $expires */ public $expires = null; /** * Tool Consumer to which this nonce applies. * * @var ToolConsumer $consumer */ private $consumer = null; /** * Nonce value. * * @var string $value */ private $value = null; /** * Class constructor. * * @param ToolConsumer $consumer Consumer object * @param string $value Nonce value (optional, default is null) */ public function __construct($consumer, $value = null) { $this->consumer = $consumer; $this->value = $value; $this->expires = time() + (self::MAX_NONCE_AGE * 60); } /** * Load a nonce value from the database. * * @return boolean True if the nonce value was successfully loaded */ public function load() { return $this->consumer->getDataConnector()->loadConsumerNonce($this); } /** * Save a nonce value in the database. * * @return boolean True if the nonce value was successfully saved */ public function save() { return $this->consumer->getDataConnector()->saveConsumerNonce($this); } /** * Get tool consumer. * * @return ToolConsumer Consumer for this nonce */ public function getConsumer() { return $this->consumer; } /** * Get outcome value. * * @return string Outcome value */ public function getValue() { return $this->value; } } Outcome.php000064400000002721152267612520006702 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class Outcome { /** * Language value. * * @var string $language */ public $language = null; /** * Outcome status value. * * @var string $status */ public $status = null; /** * Outcome date value. * * @var string $date */ public $date = null; /** * Outcome type value. * * @var string $type */ public $type = null; /** * Outcome data source value. * * @var string $dataSource */ public $dataSource = null; /** * Outcome value. * * @var string $value */ private $value = null; /** * Class constructor. * * @param string $value Outcome value (optional, default is none) */ public function __construct($value = null) { $this->value = $value; $this->language = 'en-US'; $this->date = gmdate('Y-m-d\TH:i:s\Z', time()); $this->type = 'decimal'; } /** * Get the outcome value. * * @return string Outcome value */ public function getValue() { return $this->value; } /** * Set the outcome value. * * @param string $value Outcome value */ public function setValue($value) { $this->value = $value; } } ResourceLinkShare.php000064400000001577152267612520010667 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ResourceLinkShare { /** * Consumer key value. * * @var string $consumerKey */ public $consumerKey = null; /** * Resource link ID value. * * @var string $resourceLinkId */ public $resourceLinkId = null; /** * Title of sharing context. * * @var string $title */ public $title = null; /** * Whether sharing request is to be automatically approved on first use. * * @var boolean $approved */ public $approved = null; /** * Class constructor. */ public function __construct() { } } ContentItemImage.php000064400000001513152267612520010461 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ContentItemImage { /** * Class constructor. * * @param string $id URL of image * @param int $height Height of image in pixels (optional) * @param int $width Width of image in pixels (optional) */ function __construct($id, $height = null, $width = null) { $this->{'@id'} = $id; if (!is_null($height)) { $this->height = $height; } if (!is_null($width)) { $this->width = $width; } } } Service/Membership.php000064400000010525152267612520010763 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class Membership extends Service { /** * The object to which the settings apply (ResourceLink, Context or ToolConsumer). * * @var object $source */ private $source; /** * Class constructor. * * @param object $source The object to which the memberships apply (ResourceLink or Context) * @param string $endpoint Service endpoint */ public function __construct($source, $endpoint) { $consumer = $source->getConsumer(); parent::__construct($consumer, $endpoint, 'application/vnd.ims.lis.v2.membershipcontainer+json'); $this->source = $source; } /** * Get the memberships. * * @param string $role Role for which memberships are to be requested (optional, default is all roles) * @param int $limit Limit on the number of memberships to be returned (optional, default is all) * * @return mixed The array of User objects if successful, otherwise false */ public function get($role = null, $limit = 0) { $isLink = is_a($this->source, 'IMSGlobal\LTI\ToolProvider\ResourceLink'); $parameters = array(); if (!empty($role)) { $parameters['role'] = $role; } if ($limit > 0) { $parameters['limit'] = strval($limit); } if ($isLink) { $parameters['rlid'] = $this->source->getId(); } $http = $this->send('GET', $parameters); if (!$http->ok) { $users = false; } else { $users = array(); if ($isLink) { $oldUsers = $this->source->getUserResultSourcedIDs(true, ToolProvider\ToolProvider::ID_SCOPE_RESOURCE); } foreach ($http->responseJson->pageOf->membershipSubject->membership as $membership) { $member = $membership->member; if ($isLink) { $user = ToolProvider\User::fromResourceLink($this->source, $member->userId); } else { $user = new ToolProvider\User(); $user->ltiUserId = $member->userId; } // Set the user name $firstname = (isset($member->givenName)) ? $member->givenName : ''; $lastname = (isset($member->familyName)) ? $member->familyName : ''; $fullname = (isset($member->name)) ? $member->name : ''; $user->setNames($firstname, $lastname, $fullname); // Set the user email $email = (isset($member->email)) ? $member->email : ''; $user->setEmail($email, $this->source->getConsumer()->defaultEmail); // Set the user roles if (isset($membership->role)) { $user->roles = ToolProvider\ToolProvider::parseRoles($membership->role); } // If a result sourcedid is provided save the user if ($isLink) { if (isset($member->message)) { foreach ($member->message as $message) { if (isset($message->message_type) && ($message->message_type === 'basic-lti-launch-request')) { if (isset($message->lis_result_sourcedid)) { $user->ltiResultSourcedId = $message->lis_result_sourcedid; $user->save(); } break; } } } } $users[] = $user; // Remove old user (if it exists) if ($isLink) { unset($oldUsers[$user->getId(ToolProvider\ToolProvider::ID_SCOPE_RESOURCE)]); } } // Delete any old users which were not in the latest list from the tool consumer if ($isLink) { foreach ($oldUsers as $id => $user) { $user->delete(); } } } return $users; } } Service/ToolSettings.php000064400000010352152267612520011324 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ToolSettings extends Service { /** * Settings at current level mode. */ const MODE_CURRENT_LEVEL = 1; /** * Settings at all levels mode. */ const MODE_ALL_LEVELS = 2; /** * Settings with distinct names at all levels mode. */ const MODE_DISTINCT_NAMES = 3; /** * Names of LTI parameters to be retained in the consumer settings property. * * @var array $LEVEL_NAMES */ private static $LEVEL_NAMES = array('ToolProxy' => 'system', 'ToolProxyBinding' => 'context', 'LtiLink' => 'link'); /** * The object to which the settings apply (ResourceLink, Context or ToolConsumer). * * @var object $source */ private $source; /** * Whether to use the simple JSON format. * * @var boolean $simple */ private $simple; /** * Class constructor. * * @param object $source The object to which the settings apply (ResourceLink, Context or ToolConsumer) * @param string $endpoint Service endpoint * @param boolean $simple True if the simple media type is to be used (optional, default is true) */ public function __construct($source, $endpoint, $simple = true) { if (is_a($source, 'IMSGlobal\LTI\ToolProvider\ToolConsumer')) { $consumer = $source; } else { $consumer = $source->getConsumer(); } if ($simple) { $mediaType = 'application/vnd.ims.lti.v2.toolsettings.simple+json'; } else { $mediaType = 'application/vnd.ims.lti.v2.toolsettings+json'; } parent::__construct($consumer, $endpoint, $mediaType); $this->source = $source; $this->simple = $simple; } /** * Get the tool settings. * * @param int $mode Mode for request (optional, default is current level only) * * @return mixed The array of settings if successful, otherwise false */ public function get($mode = self::MODE_CURRENT_LEVEL) { $parameter = array(); if ($mode === self::MODE_ALL_LEVELS) { $parameter['bubble'] = 'all'; } else if ($mode === self::MODE_DISTINCT_NAMES) { $parameter['bubble'] = 'distinct'; } $http = $this->send('GET', $parameter); if (!$http->ok) { $response = false; } else if ($this->simple) { $response = json_decode($http->response, true); } else if (isset($http->responseJson->{'@graph'})) { $response = array(); foreach ($http->responseJson->{'@graph'} as $level) { $settings = json_decode(json_encode($level->custom), true); unset($settings['@id']); $response[self::$LEVEL_NAMES[$level->{'@type'}]] = $settings; } } return $response; } /** * Set the tool settings. * * @param array $settings An associative array of settings (optional, default is null) * * @return HTTPMessage HTTP object containing request and response details */ public function set($settings) { if (!$this->simple) { if (is_a($this->source, 'ToolConsumer')) { $type = 'ToolProxy'; } else if (is_a($this->source, 'ToolConsumer')) { $type = 'ToolProxyBinding'; } else { $type = 'LtiLink'; } $obj = new \stdClass(); $obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v2/ToolSettings'; $obj->{'@graph'} = array(); $level = new \stdClass(); $level->{'@type'} = $type; $level->{'@id'} = $this->endpoint; $level->{'custom'} = $settings; $obj->{'@graph'}[] = $level; $body = json_encode($obj); } else { $body = json_encode($settings); } $response = parent::send('PUT', null, $body); return $response->ok; } } Service/Service.php000064400000005145152267612520010272 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class Service { /** * Whether service request should be sent unsigned. * * @var boolean $unsigned */ public $unsigned = false; /** * Service endpoint. * * @var string $endpoint */ protected $endpoint; /** * Tool Consumer for this service request. * * @var ToolConsumer $consumer */ private $consumer; /** * Media type of message body. * * @var string $mediaType */ private $mediaType; /** * Class constructor. * * @param ToolConsumer $consumer Tool consumer object for this service request * @param string $endpoint Service endpoint * @param string $mediaType Media type of message body */ public function __construct($consumer, $endpoint, $mediaType) { $this->consumer = $consumer; $this->endpoint = $endpoint; $this->mediaType = $mediaType; } /** * Send a service request. * * @param string $method The action type constant (optional, default is GET) * @param array $parameters Query parameters to add to endpoint (optional, default is none) * @param string $body Body of request (optional, default is null) * * @return HTTPMessage HTTP object containing request and response details */ public function send($method, $parameters = array(), $body = null) { $url = $this->endpoint; if (!empty($parameters)) { if (strpos($url, '?') === false) { $sep = '?'; } else { $sep = '&'; } foreach ($parameters as $name => $value) { $url .= $sep . urlencode($name) . '=' . urlencode($value); $sep = '&'; } } if (!$this->unsigned) { $header = ToolProvider\ToolConsumer::addSignature($url, $this->consumer->getKey(), $this->consumer->secret, $body, $method, $this->mediaType); } else { $header = null; } // Connect to tool consumer $http = new HTTPMessage($url, $method, $body, $header); // Parse JSON response if ($http->send() && !empty($http->response)) { $http->responseJson = json_decode($http->response); $http->ok = !is_null($http->responseJson); } return $http; } } ResourceLinkShareKey.php000064400000010070152267612520011324 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ResourceLinkShareKey { /** * Maximum permitted life for a share key value. */ const MAX_SHARE_KEY_LIFE = 168; // in hours (1 week) /** * Default life for a share key value. */ const DEFAULT_SHARE_KEY_LIFE = 24; // in hours /** * Minimum length for a share key value. */ const MIN_SHARE_KEY_LENGTH = 5; /** * Maximum length for a share key value. */ const MAX_SHARE_KEY_LENGTH = 32; /** * ID for resource link being shared. * * @var string $resourceLinkId */ public $resourceLinkId = null; /** * Length of share key. * * @var int $length */ public $length = null; /** * Life of share key. * * @var int $life */ public $life = null; // in hours /** * Whether the sharing arrangement should be automatically approved when first used. * * @var boolean $autoApprove */ public $autoApprove = false; /** * Date/time when the share key expires. * * @var int $expires */ public $expires = null; /** * Share key value. * * @var string $id */ private $id = null; /** * Data connector. * * @var DataConnector $dataConnector */ private $dataConnector = null; /** * Class constructor. * * @param ResourceLink $resourceLink Resource_Link object * @param string $id Value of share key (optional, default is null) */ public function __construct($resourceLink, $id = null) { $this->initialize(); $this->dataConnector = $resourceLink->getDataConnector(); $this->resourceLinkId = $resourceLink->getRecordId(); $this->id = $id; if (!empty($id)) { $this->load(); } } /** * Initialise the resource link share key. */ public function initialize() { $this->length = null; $this->life = null; $this->autoApprove = false; $this->expires = null; } /** * Initialise the resource link share key. * * Pseudonym for initialize(). */ public function initialise() { $this->initialize(); } /** * Save the resource link share key to the database. * * @return boolean True if the share key was successfully saved */ public function save() { if (empty($this->life)) { $this->life = self::DEFAULT_SHARE_KEY_LIFE; } else { $this->life = max(min($this->life, self::MAX_SHARE_KEY_LIFE), 0); } $this->expires = time() + ($this->life * 60 * 60); if (empty($this->id)) { if (empty($this->length) || !is_numeric($this->length)) { $this->length = self::MAX_SHARE_KEY_LENGTH; } else { $this->length = max(min($this->length, self::MAX_SHARE_KEY_LENGTH), self::MIN_SHARE_KEY_LENGTH); } $this->id = DataConnector::getRandomString($this->length); } return $this->dataConnector->saveResourceLinkShareKey($this); } /** * Delete the resource link share key from the database. * * @return boolean True if the share key was successfully deleted */ public function delete() { return $this->dataConnector->deleteResourceLinkShareKey($this); } /** * Get share key value. * * @return string Share key value */ public function getId() { return $this->id; } ### ### PRIVATE METHOD ### /** * Load the resource link share key from the database. */ private function load() { $this->initialize(); $this->dataConnector->loadResourceLinkShareKey($this); if (!is_null($this->id)) { $this->length = strlen($this->id); } if (!is_null($this->expires)) { $this->life = ($this->expires - time()) / 60 / 60; } } } Context.php000064400000021655152267612520006722 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class Context { /** * Context ID as supplied in the last connection request. * * @var string $ltiContextId */ public $ltiContextId = null; /** * Context title. * * @var string $title */ public $title = null; /** * Setting values (LTI parameters, custom parameters and local parameters). * * @var array $settings */ public $settings = null; /** * Context type. * * @var string $type */ public $type = null; /** * Date/time when the object was created. * * @var int $created */ public $created = null; /** * Date/time when the object was last updated. * * @var int $updated */ public $updated = null; /** * Tool Consumer for this context. * * @var ToolConsumer $consumer */ private $consumer = null; /** * Tool Consumer ID for this context. * * @var int $consumerId */ private $consumerId = null; /** * ID for this context. * * @var int $id */ private $id = null; /** * Whether the settings value have changed since last saved. * * @var boolean $settingsChanged */ private $settingsChanged = false; /** * Data connector object or string. * * @var mixed $dataConnector */ private $dataConnector = null; /** * Class constructor. */ public function __construct() { $this->initialize(); } /** * Initialise the context. */ public function initialize() { $this->title = ''; $this->settings = array(); $this->created = null; $this->updated = null; } /** * Initialise the context. * * Pseudonym for initialize(). */ public function initialise() { $this->initialize(); } /** * Save the context to the database. * * @return boolean True if the context was successfully saved. */ public function save() { $ok = $this->getDataConnector()->saveContext($this); if ($ok) { $this->settingsChanged = false; } return $ok; } /** * Delete the context from the database. * * @return boolean True if the context was successfully deleted. */ public function delete() { return $this->getDataConnector()->deleteContext($this); } /** * Get tool consumer. * * @return ToolConsumer Tool consumer object for this context. */ public function getConsumer() { if (is_null($this->consumer)) { $this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector()); } return $this->consumer; } /** * Set tool consumer ID. * * @param int $consumerId Tool Consumer ID for this resource link. */ public function setConsumerId($consumerId) { $this->consumer = null; $this->consumerId = $consumerId; } /** * Get tool consumer key. * * @return string Consumer key value for this context. */ public function getKey() { return $this->getConsumer()->getKey(); } /** * Get context ID. * * @return string ID for this context. */ public function getId() { return $this->ltiContextId; } /** * Get the context record ID. * * @return int Context record ID value */ public function getRecordId() { return $this->id; } /** * Sets the context record ID. * * @return int $id Context record ID value */ public function setRecordId($id) { $this->id = $id; } /** * Get the data connector. * * @return mixed Data connector object or string */ public function getDataConnector() { return $this->dataConnector; } /** * Get a setting value. * * @param string $name Name of setting * @param string $default Value to return if the setting does not exist (optional, default is an empty string) * * @return string Setting value */ public function getSetting($name, $default = '') { if (array_key_exists($name, $this->settings)) { $value = $this->settings[$name]; } else { $value = $default; } return $value; } /** * Set a setting value. * * @param string $name Name of setting * @param string $value Value to set, use an empty value to delete a setting (optional, default is null) */ public function setSetting($name, $value = null) { $old_value = $this->getSetting($name); if ($value !== $old_value) { if (!empty($value)) { $this->settings[$name] = $value; } else { unset($this->settings[$name]); } $this->settingsChanged = true; } } /** * Get an array of all setting values. * * @return array Associative array of setting values */ public function getSettings() { return $this->settings; } /** * Set an array of all setting values. * * @param array $settings Associative array of setting values */ public function setSettings($settings) { $this->settings = $settings; } /** * Save setting values. * * @return boolean True if the settings were successfully saved */ public function saveSettings() { if ($this->settingsChanged) { $ok = $this->save(); } else { $ok = true; } return $ok; } /** * Check if the Tool Settings service is supported. * * @return boolean True if this context supports the Tool Settings service */ public function hasToolSettingsService() { $url = $this->getSetting('custom_context_setting_url'); return !empty($url); } /** * Get Tool Settings. * * @param int $mode Mode for request (optional, default is current level only) * @param boolean $simple True if all the simple media type is to be used (optional, default is true) * * @return mixed The array of settings if successful, otherwise false */ public function getToolSettings($mode = Service\ToolSettings::MODE_CURRENT_LEVEL, $simple = true) { $url = $this->getSetting('custom_context_setting_url'); $service = new Service\ToolSettings($this, $url, $simple); $response = $service->get($mode); return $response; } /** * Perform a Tool Settings service request. * * @param array $settings An associative array of settings (optional, default is none) * * @return boolean True if action was successful, otherwise false */ public function setToolSettings($settings = array()) { $url = $this->getSetting('custom_context_setting_url'); $service = new Service\ToolSettings($this, $url); $response = $service->set($settings); return $response; } /** * Check if the Membership service is supported. * * @return boolean True if this context supports the Membership service */ public function hasMembershipService() { $url = $this->getSetting('custom_context_memberships_url'); return !empty($url); } /** * Get Memberships. * * @return mixed The array of User objects if successful, otherwise false */ public function getMembership() { $url = $this->getSetting('custom_context_memberships_url'); $service = new Service\Membership($this, $url); $response = $service->get(); return $response; } /** * Load the context from the database. * * @param int $id Record ID of context * @param DataConnector $dataConnector Database connection object * * @return Context Context object */ public static function fromRecordId($id, $dataConnector) { $context = new Context(); $context->dataConnector = $dataConnector; $context->load($id); return $context; } /** * Class constructor from consumer. * * @param ToolConsumer $consumer Consumer instance * @param string $ltiContextId LTI Context ID value * @return Context */ public static function fromConsumer($consumer, $ltiContextId) { $context = new Context(); $context->consumer = $consumer; $context->dataConnector = $consumer->getDataConnector(); $context->ltiContextId = $ltiContextId; if (!empty($ltiContextId)) { $context->load(); } return $context; } ### ### PRIVATE METHODS ### /** * Load the context from the database. * * @param int $id Record ID of context (optional, default is null) * * @return boolean True if context was successfully loaded */ private function load($id = null) { $this->initialize(); $this->id = $id; return $this->getDataConnector()->loadContext($this); } } ResourceLink.php000064400000113070152267612520007674 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ResourceLink { /** * Read action. */ const EXT_READ = 1; /** * Write (create/update) action. */ const EXT_WRITE = 2; /** * Delete action. */ const EXT_DELETE = 3; /** * Create action. */ const EXT_CREATE = 4; /** * Update action. */ const EXT_UPDATE = 5; /** * Decimal outcome type. */ const EXT_TYPE_DECIMAL = 'decimal'; /** * Percentage outcome type. */ const EXT_TYPE_PERCENTAGE = 'percentage'; /** * Ratio outcome type. */ const EXT_TYPE_RATIO = 'ratio'; /** * Letter (A-F) outcome type. */ const EXT_TYPE_LETTER_AF = 'letteraf'; /** * Letter (A-F) with optional +/- outcome type. */ const EXT_TYPE_LETTER_AF_PLUS = 'letterafplus'; /** * Pass/fail outcome type. */ const EXT_TYPE_PASS_FAIL = 'passfail'; /** * Free text outcome type. */ const EXT_TYPE_TEXT = 'freetext'; /** * Context title. * * @var string $title */ public $title = null; /** * Resource link ID as supplied in the last connection request. * * @var string $ltiResourceLinkId */ public $ltiResourceLinkId = null; /** * User group sets (null if the consumer does not support the groups enhancement) * * @var array $groupSets */ public $groupSets = null; /** * User groups (null if the consumer does not support the groups enhancement) * * @var array $groups */ public $groups = null; /** * Request for last service request. * * @var string $extRequest */ public $extRequest = null; /** * Request headers for last service request. * * @var array $extRequestHeaders */ public $extRequestHeaders = null; /** * Response from last service request. * * @var string $extResponse */ public $extResponse = null; /** * Response header from last service request. * * @var array $extResponseHeaders */ public $extResponseHeaders = null; /** * Consumer key value for resource link being shared (if any). * * @var string $primaryResourceLinkId */ public $primaryResourceLinkId = null; /** * Whether the sharing request has been approved by the primary resource link. * * @var boolean $shareApproved */ public $shareApproved = null; /** * Date/time when the object was created. * * @var int $created */ public $created = null; /** * Date/time when the object was last updated. * * @var int $updated */ public $updated = null; /** * Record ID for this resource link. * * @var int $id */ private $id = null; /** * Tool Consumer for this resource link. * * @var ToolConsumer $consumer */ private $consumer = null; /** * Tool Consumer ID for this resource link. * * @var int $consumerId */ private $consumerId = null; /** * Context for this resource link. * * @var Context $context */ private $context = null; /** * Context ID for this resource link. * * @var int $contextId */ private $contextId = null; /** * Setting values (LTI parameters, custom parameters and local parameters). * * @var array $settings */ private $settings = null; /** * Whether the settings value have changed since last saved. * * @var boolean $settingsChanged */ private $settingsChanged = false; /** * XML document for the last extension service request. * * @var string $extDoc */ private $extDoc = null; /** * XML node array for the last extension service request. * * @var array $extNodes */ private $extNodes = null; /** * Data connector object or string. * * @var mixed $dataConnector */ private $dataConnector = null; /** * Class constructor. */ public function __construct() { $this->initialize(); } /** * Initialise the resource link. */ public function initialize() { $this->title = ''; $this->settings = array(); $this->groupSets = null; $this->groups = null; $this->primaryResourceLinkId = null; $this->shareApproved = null; $this->created = null; $this->updated = null; } /** * Initialise the resource link. * * Pseudonym for initialize(). */ public function initialise() { $this->initialize(); } /** * Save the resource link to the database. * * @return boolean True if the resource link was successfully saved. */ public function save() { $ok = $this->getDataConnector()->saveResourceLink($this); if ($ok) { $this->settingsChanged = false; } return $ok; } /** * Delete the resource link from the database. * * @return boolean True if the resource link was successfully deleted. */ public function delete() { return $this->getDataConnector()->deleteResourceLink($this); } /** * Get tool consumer. * * @return ToolConsumer Tool consumer object for this resource link. */ public function getConsumer() { if (is_null($this->consumer)) { if (!is_null($this->context) || !is_null($this->contextId)) { $this->consumer = $this->getContext()->getConsumer(); } else { $this->consumer = ToolConsumer::fromRecordId($this->consumerId, $this->getDataConnector()); } } return $this->consumer; } /** * Set tool consumer ID. * * @param int $consumerId Tool Consumer ID for this resource link. */ public function setConsumerId($consumerId) { $this->consumer = null; $this->consumerId = $consumerId; } /** * Get context. * * @return object LTIContext object for this resource link. */ public function getContext() { if (is_null($this->context) && !is_null($this->contextId)) { $this->context = Context::fromRecordId($this->contextId, $this->getDataConnector()); } return $this->context; } /** * Get context record ID. * * @return int Context record ID for this resource link. */ public function getContextId() { return $this->contextId; } /** * Set context ID. * * @param int $contextId Context ID for this resource link. */ public function setContextId($contextId) { $this->context = null; $this->contextId = $contextId; } /** * Get tool consumer key. * * @return string Consumer key value for this resource link. */ public function getKey() { return $this->getConsumer()->getKey(); } /** * Get resource link ID. * * @return string ID for this resource link. */ public function getId() { return $this->ltiResourceLinkId; } /** * Get resource link record ID. * * @return int Record ID for this resource link. */ public function getRecordId() { return $this->id; } /** * Set resource link record ID. * * @param int $id Record ID for this resource link. */ public function setRecordId($id) { $this->id = $id; } /** * Get the data connector. * * @return mixed Data connector object or string */ public function getDataConnector() { return $this->dataConnector; } /** * Get a setting value. * * @param string $name Name of setting * @param string $default Value to return if the setting does not exist (optional, default is an empty string) * * @return string Setting value */ public function getSetting($name, $default = '') { if (array_key_exists($name, $this->settings)) { $value = $this->settings[$name]; } else { $value = $default; } return $value; } /** * Set a setting value. * * @param string $name Name of setting * @param string $value Value to set, use an empty value to delete a setting (optional, default is null) */ public function setSetting($name, $value = null) { $old_value = $this->getSetting($name); if ($value !== $old_value) { if (!empty($value)) { $this->settings[$name] = $value; } else { unset($this->settings[$name]); } $this->settingsChanged = true; } } /** * Get an array of all setting values. * * @return array Associative array of setting values */ public function getSettings() { return $this->settings; } /** * Set an array of all setting values. * * @param array $settings Associative array of setting values */ public function setSettings($settings) { $this->settings = $settings; } /** * Save setting values. * * @return boolean True if the settings were successfully saved */ public function saveSettings() { if ($this->settingsChanged) { $ok = $this->save(); } else { $ok = true; } return $ok; } /** * Check if the Outcomes service is supported. * * @return boolean True if this resource link supports the Outcomes service (either the LTI 1.1 or extension service) */ public function hasOutcomesService() { $url = $this->getSetting('ext_ims_lis_basic_outcome_url') . $this->getSetting('lis_outcome_service_url'); return !empty($url); } /** * Check if the Memberships extension service is supported. * * @return boolean True if this resource link supports the Memberships extension service */ public function hasMembershipsService() { $url = $this->getSetting('ext_ims_lis_memberships_url'); return !empty($url); } /** * Check if the Setting extension service is supported. * * @return boolean True if this resource link supports the Setting extension service */ public function hasSettingService() { $url = $this->getSetting('ext_ims_lti_tool_setting_url'); return !empty($url); } /** * Perform an Outcomes service request. * * @param int $action The action type constant * @param Outcome $ltiOutcome Outcome object * @param User $user User object * * @return boolean True if the request was successfully processed */ public function doOutcomesService($action, $ltiOutcome, $user) { $response = false; $this->extResponse = null; // Lookup service details from the source resource link appropriate to the user (in case the destination is being shared) $sourceResourceLink = $user->getResourceLink(); $sourcedId = $user->ltiResultSourcedId; // Use LTI 1.1 service in preference to extension service if it is available $urlLTI11 = $sourceResourceLink->getSetting('lis_outcome_service_url'); $urlExt = $sourceResourceLink->getSetting('ext_ims_lis_basic_outcome_url'); if ($urlExt || $urlLTI11) { switch ($action) { case self::EXT_READ: if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) { $do = 'readResult'; } else if ($urlExt) { $urlLTI11 = null; $do = 'basic-lis-readresult'; } break; case self::EXT_WRITE: if ($urlLTI11 && $this->checkValueType($ltiOutcome, array(self::EXT_TYPE_DECIMAL))) { $do = 'replaceResult'; } else if ($this->checkValueType($ltiOutcome)) { $urlLTI11 = null; $do = 'basic-lis-updateresult'; } break; case self::EXT_DELETE: if ($urlLTI11 && ($ltiOutcome->type === self::EXT_TYPE_DECIMAL)) { $do = 'deleteResult'; } else if ($urlExt) { $urlLTI11 = null; $do = 'basic-lis-deleteresult'; } break; } } if (isset($do)) { $value = $ltiOutcome->getValue(); if (is_null($value)) { $value = ''; } if ($urlLTI11) { $xml = ''; if ($action === self::EXT_WRITE) { $xml = << {$ltiOutcome->language} {$value} EOF; } $sourcedId = htmlentities($sourcedId); $xml = << {$sourcedId} {$xml} EOF; if ($this->doLTI11Service($do, $urlLTI11, $xml)) { switch ($action) { case self::EXT_READ: if (!isset($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString'])) { break; } else { $ltiOutcome->setValue($this->extNodes['imsx_POXBody']["{$do}Response"]['result']['resultScore']['textString']); } case self::EXT_WRITE: case self::EXT_DELETE: $response = true; break; } } } else { $params = array(); $params['sourcedid'] = $sourcedId; $params['result_resultscore_textstring'] = $value; if (!empty($ltiOutcome->language)) { $params['result_resultscore_language'] = $ltiOutcome->language; } if (!empty($ltiOutcome->status)) { $params['result_statusofresult'] = $ltiOutcome->status; } if (!empty($ltiOutcome->date)) { $params['result_date'] = $ltiOutcome->date; } if (!empty($ltiOutcome->type)) { $params['result_resultvaluesourcedid'] = $ltiOutcome->type; } if (!empty($ltiOutcome->data_source)) { $params['result_datasource'] = $ltiOutcome->data_source; } if ($this->doService($do, $urlExt, $params)) { switch ($action) { case self::EXT_READ: if (isset($this->extNodes['result']['resultscore']['textstring'])) { $response = $this->extNodes['result']['resultscore']['textstring']; } break; case self::EXT_WRITE: case self::EXT_DELETE: $response = true; break; } } } if (is_array($response) && (count($response) <= 0)) { $response = ''; } } return $response; } /** * Perform a Memberships service request. * * The user table is updated with the new list of user objects. * * @param boolean $withGroups True is group information is to be requested as well * * @return mixed Array of User objects or False if the request was not successful */ public function doMembershipsService($withGroups = false) { $users = array(); $oldUsers = $this->getUserResultSourcedIDs(true, ToolProvider::ID_SCOPE_RESOURCE); $this->extResponse = null; $url = $this->getSetting('ext_ims_lis_memberships_url'); $params = array(); $params['id'] = $this->getSetting('ext_ims_lis_memberships_id'); $ok = false; if ($withGroups) { $ok = $this->doService('basic-lis-readmembershipsforcontextwithgroups', $url, $params); } if ($ok) { $this->groupSets = array(); $this->groups = array(); } else { $ok = $this->doService('basic-lis-readmembershipsforcontext', $url, $params); } if ($ok) { if (!isset($this->extNodes['memberships']['member'])) { $members = array(); } else if (!isset($this->extNodes['memberships']['member'][0])) { $members = array(); $members[0] = $this->extNodes['memberships']['member']; } else { $members = $this->extNodes['memberships']['member']; } for ($i = 0; $i < count($members); $i++) { $user = User::fromResourceLink($this, $members[$i]['user_id']); // Set the user name $firstname = (isset($members[$i]['person_name_given'])) ? $members[$i]['person_name_given'] : ''; $lastname = (isset($members[$i]['person_name_family'])) ? $members[$i]['person_name_family'] : ''; $fullname = (isset($members[$i]['person_name_full'])) ? $members[$i]['person_name_full'] : ''; $user->setNames($firstname, $lastname, $fullname); // Set the user email $email = (isset($members[$i]['person_contact_email_primary'])) ? $members[$i]['person_contact_email_primary'] : ''; $user->setEmail($email, $this->getConsumer()->defaultEmail); /// Set the user roles if (isset($members[$i]['roles'])) { $user->roles = ToolProvider::parseRoles($members[$i]['roles']); } // Set the user groups if (!isset($members[$i]['groups']['group'])) { $groups = array(); } else if (!isset($members[$i]['groups']['group'][0])) { $groups = array(); $groups[0] = $members[$i]['groups']['group']; } else { $groups = $members[$i]['groups']['group']; } for ($j = 0; $j < count($groups); $j++) { $group = $groups[$j]; if (isset($group['set'])) { $set_id = $group['set']['id']; if (!isset($this->groupSets[$set_id])) { $this->groupSets[$set_id] = array('title' => $group['set']['title'], 'groups' => array(), 'num_members' => 0, 'num_staff' => 0, 'num_learners' => 0); } $this->groupSets[$set_id]['num_members']++; if ($user->isStaff()) { $this->groupSets[$set_id]['num_staff']++; } if ($user->isLearner()) { $this->groupSets[$set_id]['num_learners']++; } if (!in_array($group['id'], $this->groupSets[$set_id]['groups'])) { $this->groupSets[$set_id]['groups'][] = $group['id']; } $this->groups[$group['id']] = array('title' => $group['title'], 'set' => $set_id); } else { $this->groups[$group['id']] = array('title' => $group['title']); } $user->groups[] = $group['id']; } // If a result sourcedid is provided save the user if (isset($members[$i]['lis_result_sourcedid'])) { $user->ltiResultSourcedId = $members[$i]['lis_result_sourcedid']; $user->save(); } $users[] = $user; // Remove old user (if it exists) unset($oldUsers[$user->getId(ToolProvider::ID_SCOPE_RESOURCE)]); } // Delete any old users which were not in the latest list from the tool consumer foreach ($oldUsers as $id => $user) { $user->delete(); } } else { $users = false; } return $users; } /** * Perform a Setting service request. * * @param int $action The action type constant * @param string $value The setting value (optional, default is null) * * @return mixed The setting value for a read action, true if a write or delete action was successful, otherwise false */ public function doSettingService($action, $value = null) { $response = false; $this->extResponse = null; switch ($action) { case self::EXT_READ: $do = 'basic-lti-loadsetting'; break; case self::EXT_WRITE: $do = 'basic-lti-savesetting'; break; case self::EXT_DELETE: $do = 'basic-lti-deletesetting'; break; } if (isset($do)) { $url = $this->getSetting('ext_ims_lti_tool_setting_url'); $params = array(); $params['id'] = $this->getSetting('ext_ims_lti_tool_setting_id'); if (is_null($value)) { $value = ''; } $params['setting'] = $value; if ($this->doService($do, $url, $params)) { switch ($action) { case self::EXT_READ: if (isset($this->extNodes['setting']['value'])) { $response = $this->extNodes['setting']['value']; if (is_array($response)) { $response = ''; } } break; case self::EXT_WRITE: $this->setSetting('ext_ims_lti_tool_setting', $value); $this->saveSettings(); $response = true; break; case self::EXT_DELETE: $response = true; break; } } } return $response; } /** * Check if the Tool Settings service is supported. * * @return boolean True if this resource link supports the Tool Settings service */ public function hasToolSettingsService() { $url = $this->getSetting('custom_link_setting_url'); return !empty($url); } /** * Get Tool Settings. * * @param int $mode Mode for request (optional, default is current level only) * @param boolean $simple True if all the simple media type is to be used (optional, default is true) * * @return mixed The array of settings if successful, otherwise false */ public function getToolSettings($mode = Service\ToolSettings::MODE_CURRENT_LEVEL, $simple = true) { $url = $this->getSetting('custom_link_setting_url'); $service = new Service\ToolSettings($this, $url, $simple); $response = $service->get($mode); return $response; } /** * Perform a Tool Settings service request. * * @param array $settings An associative array of settings (optional, default is none) * * @return boolean True if action was successful, otherwise false */ public function setToolSettings($settings = array()) { $url = $this->getSetting('custom_link_setting_url'); $service = new Service\ToolSettings($this, $url); $response = $service->set($settings); return $response; } /** * Check if the Membership service is supported. * * @return boolean True if this resource link supports the Membership service */ public function hasMembershipService() { $has = !empty($this->contextId); if ($has) { $has = !empty($this->getContext()->getSetting('custom_context_memberships_url')); } return $has; } /** * Get Memberships. * * @return mixed The array of User objects if successful, otherwise false */ public function getMembership() { $response = false; if (!empty($this->contextId)) { $url = $this->getContext()->getSetting('custom_context_memberships_url'); if (!empty($url)) { $service = new Service\Membership($this, $url); $response = $service->get(); } } return $response; } /** * Obtain an array of User objects for users with a result sourcedId. * * The array may include users from other resource links which are sharing this resource link. * It may also be optionally indexed by the user ID of a specified scope. * * @param boolean $localOnly True if only users from this resource link are to be returned, not users from shared resource links (optional, default is false) * @param int $idScope Scope to use for ID values (optional, default is null for consumer default) * * @return array Array of User objects */ public function getUserResultSourcedIDs($localOnly = false, $idScope = null) { return $this->getDataConnector()->getUserResultSourcedIDsResourceLink($this, $localOnly, $idScope); } /** * Get an array of ResourceLinkShare objects for each resource link which is sharing this context. * * @return array Array of ResourceLinkShare objects */ public function getShares() { return $this->getDataConnector()->getSharesResourceLink($this); } /** * Class constructor from consumer. * * @param ToolConsumer $consumer Consumer object * @param string $ltiResourceLinkId Resource link ID value * @param string $tempId Temporary Resource link ID value (optional, default is null) * @return ResourceLink */ public static function fromConsumer($consumer, $ltiResourceLinkId, $tempId = null) { $resourceLink = new ResourceLink(); $resourceLink->consumer = $consumer; $resourceLink->dataConnector = $consumer->getDataConnector(); $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; if (!empty($ltiResourceLinkId)) { $resourceLink->load(); if (is_null($resourceLink->id) && !empty($tempId)) { $resourceLink->ltiResourceLinkId = $tempId; $resourceLink->load(); $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; } } return $resourceLink; } /** * Class constructor from context. * * @param Context $context Context object * @param string $ltiResourceLinkId Resource link ID value * @param string $tempId Temporary Resource link ID value (optional, default is null) * @return ResourceLink */ public static function fromContext($context, $ltiResourceLinkId, $tempId = null) { $resourceLink = new ResourceLink(); $resourceLink->setContextId($context->getRecordId()); $resourceLink->context = $context; $resourceLink->dataConnector = $context->getDataConnector(); $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; if (!empty($ltiResourceLinkId)) { $resourceLink->load(); if (is_null($resourceLink->id) && !empty($tempId)) { $resourceLink->ltiResourceLinkId = $tempId; $resourceLink->load(); $resourceLink->ltiResourceLinkId = $ltiResourceLinkId; } } return $resourceLink; } /** * Load the resource link from the database. * * @param int $id Record ID of resource link * @param DataConnector $dataConnector Database connection object * * @return ResourceLink ResourceLink object */ public static function fromRecordId($id, $dataConnector) { $resourceLink = new ResourceLink(); $resourceLink->dataConnector = $dataConnector; $resourceLink->load($id); return $resourceLink; } ### ### PRIVATE METHODS ### /** * Load the resource link from the database. * * @param int $id Record ID of resource link (optional, default is null) * * @return boolean True if resource link was successfully loaded */ private function load($id = null) { $this->initialize(); $this->id = $id; return $this->getDataConnector()->loadResourceLink($this); } /** * Convert data type of value to a supported type if possible. * * @param Outcome $ltiOutcome Outcome object * @param string[] $supportedTypes Array of outcome types to be supported (optional, default is null to use supported types reported in the last launch for this resource link) * * @return boolean True if the type/value are valid and supported */ private function checkValueType($ltiOutcome, $supportedTypes = null) { if (empty($supportedTypes)) { $supportedTypes = explode(',', str_replace(' ', '', strtolower($this->getSetting('ext_ims_lis_resultvalue_sourcedids', self::EXT_TYPE_DECIMAL)))); } $type = $ltiOutcome->type; $value = $ltiOutcome->getValue(); // Check whether the type is supported or there is no value $ok = in_array($type, $supportedTypes) || (strlen($value) <= 0); if (!$ok) { // Convert numeric values to decimal if ($type === self::EXT_TYPE_PERCENTAGE) { if (substr($value, -1) === '%') { $value = substr($value, 0, -1); } $ok = is_numeric($value) && ($value >= 0) && ($value <= 100); if ($ok) { $ltiOutcome->setValue($value / 100); $ltiOutcome->type = self::EXT_TYPE_DECIMAL; } } else if ($type === self::EXT_TYPE_RATIO) { $parts = explode('/', $value, 2); $ok = (count($parts) === 2) && is_numeric($parts[0]) && is_numeric($parts[1]) && ($parts[0] >= 0) && ($parts[1] > 0); if ($ok) { $ltiOutcome->setValue($parts[0] / $parts[1]); $ltiOutcome->type = self::EXT_TYPE_DECIMAL; } // Convert letter_af to letter_af_plus or text } else if ($type === self::EXT_TYPE_LETTER_AF) { if (in_array(self::EXT_TYPE_LETTER_AF_PLUS, $supportedTypes)) { $ok = true; $ltiOutcome->type = self::EXT_TYPE_LETTER_AF_PLUS; } else if (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) { $ok = true; $ltiOutcome->type = self::EXT_TYPE_TEXT; } // Convert letter_af_plus to letter_af or text } else if ($type === self::EXT_TYPE_LETTER_AF_PLUS) { if (in_array(self::EXT_TYPE_LETTER_AF, $supportedTypes) && (strlen($value) === 1)) { $ok = true; $ltiOutcome->type = self::EXT_TYPE_LETTER_AF; } else if (in_array(self::EXT_TYPE_TEXT, $supportedTypes)) { $ok = true; $ltiOutcome->type = self::EXT_TYPE_TEXT; } // Convert text to decimal } else if ($type === self::EXT_TYPE_TEXT) { $ok = is_numeric($value) && ($value >= 0) && ($value <=1); if ($ok) { $ltiOutcome->type = self::EXT_TYPE_DECIMAL; } else if (substr($value, -1) === '%') { $value = substr($value, 0, -1); $ok = is_numeric($value) && ($value >= 0) && ($value <=100); if ($ok) { if (in_array(self::EXT_TYPE_PERCENTAGE, $supportedTypes)) { $ltiOutcome->type = self::EXT_TYPE_PERCENTAGE; } else { $ltiOutcome->setValue($value / 100); $ltiOutcome->type = self::EXT_TYPE_DECIMAL; } } } } } return $ok; } /** * Send a service request to the tool consumer. * * @param string $type Message type value * @param string $url URL to send request to * @param array $params Associative array of parameter values to be passed * * @return boolean True if the request successfully obtained a response */ private function doService($type, $url, $params) { $ok = false; $this->extRequest = null; $this->extRequestHeaders = ''; $this->extResponse = null; $this->extResponseHeaders = ''; if (!empty($url)) { $params = $this->getConsumer()->signParameters($url, $type, $this->getConsumer()->ltiVersion, $params); // Connect to tool consumer $http = new HTTPMessage($url, 'POST', $params); // Parse XML response if ($http->send()) { $this->extResponse = $http->response; $this->extResponseHeaders = $http->responseHeaders; try { $this->extDoc = new DOMDocument(); $this->extDoc->loadXML($http->response); $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement); if (isset($this->extNodes['statusinfo']['codemajor']) && ($this->extNodes['statusinfo']['codemajor'] === 'Success')) { $ok = true; } } catch (\Exception $e) { } } $this->extRequest = $http->request; $this->extRequestHeaders = $http->requestHeaders; } return $ok; } /** * Send a service request to the tool consumer. * * @param string $type Message type value * @param string $url URL to send request to * @param string $xml XML of message request * * @return boolean True if the request successfully obtained a response */ private function doLTI11Service($type, $url, $xml) { $ok = false; $this->extRequest = null; $this->extRequestHeaders = ''; $this->extResponse = null; $this->extResponseHeaders = ''; if (!empty($url)) { $id = uniqid(); $xmlRequest = <<< EOD V1.0 {$id} <{$type}Request> {$xml} EOD; // Calculate body hash $hash = base64_encode(sha1($xmlRequest, true)); $params = array('oauth_body_hash' => $hash); // Add OAuth signature $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1(); $consumer = new OAuth\OAuthConsumer($this->getConsumer()->getKey(), $this->getConsumer()->secret, null); $req = OAuth\OAuthRequest::from_consumer_and_token($consumer, null, 'POST', $url, $params); $req->sign_request($hmacMethod, $consumer, null); $params = $req->get_parameters(); $header = $req->to_header(); $header .= "\nContent-Type: application/xml"; // Connect to tool consumer $http = new HTTPMessage($url, 'POST', $xmlRequest, $header); // Parse XML response if ($http->send()) { $this->extResponse = $http->response; $this->extResponseHeaders = $http->responseHeaders; try { $this->extDoc = new DOMDocument(); $this->extDoc->loadXML($http->response); $this->extNodes = $this->domnodeToArray($this->extDoc->documentElement); if (isset($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor']) && ($this->extNodes['imsx_POXHeader']['imsx_POXResponseHeaderInfo']['imsx_statusInfo']['imsx_codeMajor'] === 'success')) { $ok = true; } } catch (\Exception $e) { } } $this->extRequest = $http->request; $this->extRequestHeaders = $http->requestHeaders; } return $ok; } /** * Convert DOM nodes to array. * * @param DOMElement $node XML element * * @return array Array of XML document elements */ private function domnodeToArray($node) { $output = ''; switch ($node->nodeType) { case XML_CDATA_SECTION_NODE: case XML_TEXT_NODE: $output = trim($node->textContent); break; case XML_ELEMENT_NODE: for ($i = 0; $i < $node->childNodes->length; $i++) { $child = $node->childNodes->item($i); $v = $this->domnodeToArray($child); if (isset($child->tagName)) { $t = $child->tagName; if (!isset($output[$t])) { $output[$t] = array(); } $output[$t][] = $v; } else { $s = (string) $v; if (strlen($s) > 0) { $output = $s; } } } if (is_array($output)) { if ($node->attributes->length) { $a = array(); foreach ($node->attributes as $attrName => $attrNode) { $a[$attrName] = (string) $attrNode->value; } $output['@attributes'] = $a; } foreach ($output as $t => $v) { if (is_array($v) && count($v)==1 && $t!='@attributes') { $output[$t] = $v[0]; } } } break; } return $output; } } OAuthDataStore.php000064400000005251152267612520010117 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class OAuthDataStore extends OAuth\OAuthDataStore { /** * Tool Provider object. * * @var ToolProvider $toolProvider */ private $toolProvider = null; /** * Class constructor. * * @param ToolProvider $toolProvider Tool_Provider object */ public function __construct($toolProvider) { $this->toolProvider = $toolProvider; } /** * Create an OAuthConsumer object for the tool consumer. * * @param string $consumerKey Consumer key value * * @return OAuthConsumer OAuthConsumer object */ function lookup_consumer($consumerKey) { return new OAuth\OAuthConsumer($this->toolProvider->consumer->getKey(), $this->toolProvider->consumer->secret); } /** * Create an OAuthToken object for the tool consumer. * * @param string $consumer OAuthConsumer object * @param string $tokenType Token type * @param string $token Token value * * @return OAuthToken OAuthToken object */ function lookup_token($consumer, $tokenType, $token) { return new OAuth\OAuthToken($consumer, ''); } /** * Lookup nonce value for the tool consumer. * * @param OAuthConsumer $consumer OAuthConsumer object * @param string $token Token value * @param string $value Nonce value * @param string $timestamp Date/time of request * * @return boolean True if the nonce value already exists */ function lookup_nonce($consumer, $token, $value, $timestamp) { $nonce = new ConsumerNonce($this->toolProvider->consumer, $value); $ok = !$nonce->load(); if ($ok) { $ok = $nonce->save(); } if (!$ok) { $this->toolProvider->reason = 'Invalid nonce.'; } return !$ok; } /** * Get new request token. * * @param OAuthConsumer $consumer OAuthConsumer object * @param string $callback Callback URL * * @return string Null value */ function new_request_token($consumer, $callback = null) { return null; } /** * Get new access token. * * @param string $token Token value * @param OAuthConsumer $consumer OAuthConsumer object * @param string $verifier Verification code * * @return string Null value */ function new_access_token($token, $consumer, $verifier = null) { return null; } } DataConnector/DataConnector_pdo.php000064400000142567152267612520013416 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class DataConnector_pdo extends DataConnector { /** * Class constructor * * @param object $db Database connection object * @param string $dbTableNamePrefix Prefix for database table names (optional, default is none) */ public function __construct($db, $dbTableNamePrefix = '') { parent::__construct($db, $dbTableNamePrefix); if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'oci') { $this->date_format = 'd-M-Y'; } } ### ### ToolConsumer methods ### /** * Load tool consumer object. * * @param ToolConsumer $consumer ToolConsumer object * * @return boolean True if the tool consumer object was successfully loaded */ public function loadToolConsumer($consumer) { $ok = false; if (!empty($consumer->getRecordId())) { $sql = 'SELECT consumer_pk, name, consumer_key256, consumer_key, secret, lti_version, ' . 'consumer_name, consumer_version, consumer_guid, ' . 'profile, tool_proxy, settings, protected, enabled, ' . 'enable_from, enable_until, last_access, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . 'WHERE consumer_pk = :id'; $query = $this->db->prepare($sql); $id = $consumer->getRecordId(); $query->bindValue('id', $id, PDO::PARAM_INT); } else { $sql = 'SELECT consumer_pk, name, consumer_key256, consumer_key, secret, lti_version, ' . 'consumer_name, consumer_version, consumer_guid, ' . 'profile, tool_proxy, settings, protected, enabled, ' . 'enable_from, enable_until, last_access, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . 'WHERE consumer_key256 = :key256'; $query = $this->db->prepare($sql); $key256 = DataConnector::getConsumerKey($consumer->getKey()); $query->bindValue('key256', $key256, PDO::PARAM_STR); } if ($query->execute()) { while ($row = $query->fetch(PDO::FETCH_ASSOC)) { $row = array_change_key_case($row); if (empty($key256) || empty($row['consumer_key']) || ($consumer->getKey() === $row['consumer_key'])) { $consumer->setRecordId(intval($row['consumer_pk'])); $consumer->name = $row['name']; $consumer->setkey(empty($row['consumer_key']) ? $row['consumer_key256'] : $row['consumer_key']); $consumer->secret = $row['secret']; $consumer->ltiVersion = $row['lti_version']; $consumer->consumerName = $row['consumer_name']; $consumer->consumerVersion = $row['consumer_version']; $consumer->consumerGuid = $row['consumer_guid']; $consumer->profile = json_decode($row['profile']); $consumer->toolProxy = $row['tool_proxy']; $settings = unserialize($row['settings']); if (!is_array($settings)) { $settings = array(); } $consumer->setSettings($settings); $consumer->protected = (intval($row['protected']) === 1); $consumer->enabled = (intval($row['enabled']) === 1); $consumer->enableFrom = null; if (!is_null($row['enable_from'])) { $consumer->enableFrom = strtotime($row['enable_from']); } $consumer->enableUntil = null; if (!is_null($row['enable_until'])) { $consumer->enableUntil = strtotime($row['enable_until']); } $consumer->lastAccess = null; if (!is_null($row['last_access'])) { $consumer->lastAccess = strtotime($row['last_access']); } $consumer->created = strtotime($row['created']); $consumer->updated = strtotime($row['updated']); $ok = true; break; } } } return $ok; } /** * Save tool consumer object. * * @param ToolConsumer $consumer Consumer object * * @return boolean True if the tool consumer object was successfully saved */ public function saveToolConsumer($consumer) { $id = $consumer->getRecordId(); $key = $consumer->getKey(); $key256 = $this->getConsumerKey($key); if ($key === $key256) { $key = null; } $protected = ($consumer->protected) ? 1 : 0; $enabled = ($consumer->enabled)? 1 : 0; $profile = (!empty($consumer->profile)) ? json_encode($consumer->profile) : null; $settingsValue = serialize($consumer->getSettings()); $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); $from = null; if (!is_null($consumer->enableFrom)) { $from = date("{$this->dateFormat} {$this->timeFormat}", $consumer->enableFrom); } $until = null; if (!is_null($consumer->enableUntil)) { $until = date("{$this->dateFormat} {$this->timeFormat}", $consumer->enableUntil); } $last = null; if (!is_null($consumer->lastAccess)) { $last = date($this->dateFormat, $consumer->lastAccess); } if (empty($id)) { $sql = "INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' (consumer_key256, consumer_key, name, ' . 'secret, lti_version, consumer_name, consumer_version, consumer_guid, profile, tool_proxy, settings, protected, enabled, ' . 'enable_from, enable_until, last_access, created, updated) ' . 'VALUES (:key256, :key, :name, :secret, :lti_version, :consumer_name, :consumer_version, :consumer_guid, :profile, :tool_proxy, :settings, ' . ':protected, :enabled, :enable_from, :enable_until, :last_access, :created, :updated)'; $query = $this->db->prepare($sql); $query->bindValue('key256', $key256, PDO::PARAM_STR); $query->bindValue('key', $key, PDO::PARAM_STR); $query->bindValue('name', $consumer->name, PDO::PARAM_STR); $query->bindValue('secret', $consumer->secret, PDO::PARAM_STR); $query->bindValue('lti_version', $consumer->ltiVersion, PDO::PARAM_STR); $query->bindValue('consumer_name', $consumer->consumerName, PDO::PARAM_STR); $query->bindValue('consumer_version', $consumer->consumerVersion, PDO::PARAM_STR); $query->bindValue('consumer_guid', $consumer->consumerGuid, PDO::PARAM_STR); $query->bindValue('profile', $profile, PDO::PARAM_STR); $query->bindValue('tool_proxy', $consumer->toolProxy, PDO::PARAM_STR); $query->bindValue('settings', $settingsValue, PDO::PARAM_STR); $query->bindValue('protected', $protected, PDO::PARAM_INT); $query->bindValue('enabled', $enabled, PDO::PARAM_INT); $query->bindValue('enable_from', $from, PDO::PARAM_STR); $query->bindValue('enable_until', $until, PDO::PARAM_STR); $query->bindValue('last_access', $last, PDO::PARAM_STR); $query->bindValue('created', $now, PDO::PARAM_STR); $query->bindValue('updated', $now, PDO::PARAM_STR); } else { $sql = 'UPDATE ' . $this->dbTableNamePrefix . DataConnector::CONSUMER_TABLE_NAME . ' ' . 'SET consumer_key256 = :key256, consumer_key = :key, name = :name, secret = :secret, lti_version = :lti_version, ' . 'consumer_name = :consumer_name, consumer_version = :consumer_version, consumer_guid = :consumer_guid, ' . 'profile = :profile, tool_proxy = :tool_proxy, settings = :settings, ' . 'protected = :protected, enabled = :enabled, enable_from = :enable_from, enable_until = :enable_until, last_access = :last_access, updated = :updated ' . 'WHERE consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('key256', $key256, PDO::PARAM_STR); $query->bindValue('key', $key, PDO::PARAM_STR); $query->bindValue('name', $consumer->name, PDO::PARAM_STR); $query->bindValue('secret', $consumer->secret, PDO::PARAM_STR); $query->bindValue('lti_version', $consumer->ltiVersion, PDO::PARAM_STR); $query->bindValue('consumer_name', $consumer->consumerName, PDO::PARAM_STR); $query->bindValue('consumer_version', $consumer->consumerVersion, PDO::PARAM_STR); $query->bindValue('consumer_guid', $consumer->consumerGuid, PDO::PARAM_STR); $query->bindValue('profile', $profile, PDO::PARAM_STR); $query->bindValue('tool_proxy', $consumer->toolProxy, PDO::PARAM_STR); $query->bindValue('settings', $settingsValue, PDO::PARAM_STR); $query->bindValue('protected', $protected, PDO::PARAM_INT); $query->bindValue('enabled', $enabled, PDO::PARAM_INT); $query->bindValue('enable_from', $from, PDO::PARAM_STR); $query->bindValue('enable_until', $until, PDO::PARAM_STR); $query->bindValue('last_access', $last, PDO::PARAM_STR); $query->bindValue('updated', $now, PDO::PARAM_STR); $query->bindValue('id', $id, PDO::PARAM_INT); } $ok = $query->execute(); if ($ok) { if (empty($id)) { $consumer->setRecordId(intval($this->db->lastInsertId())); $consumer->created = $time; } $consumer->updated = $time; } return $ok; } /** * Delete tool consumer object. * * @param ToolConsumer $consumer Consumer object * * @return boolean True if the tool consumer object was successfully deleted */ public function deleteToolConsumer($consumer) { $id = $consumer->getRecordId(); // Delete any nonce values for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any outstanding share keys for resource links for this consumer $sql = 'DELETE sk ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' sk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON sk.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any outstanding share keys for resource links for contexts in this consumer $sql = 'DELETE sk ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' sk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON sk.resource_link_pk = rl.resource_link_pk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'WHERE c.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any users in resource links for this consumer $sql = 'DELETE u ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON u.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any users in resource links for contexts in this consumer $sql = 'DELETE u ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON u.resource_link_pk = rl.resource_link_pk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'WHERE c.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Update any resource links for which this consumer is acting as a primary resource link $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' prl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON prl.primary_resource_link_pk = rl.resource_link_pk ' . 'SET prl.primary_resource_link_pk = NULL, prl.share_approved = NULL ' . 'WHERE rl.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Update any resource links for contexts in which this consumer is acting as a primary resource link $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' prl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON prl.primary_resource_link_pk = rl.resource_link_pk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'SET prl.primary_resource_link_pk = NULL, prl.share_approved = NULL ' . 'WHERE c.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any resource links for this consumer $sql = 'DELETE rl ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 'WHERE rl.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any resource links for contexts in this consumer $sql = 'DELETE rl ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'WHERE c.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any contexts for this consumer $sql = 'DELETE c ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ' . 'WHERE c.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete consumer $sql = 'DELETE c ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' c ' . 'WHERE c.consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); if ($ok) { $consumer->initialize(); } return $ok; } ### # Load all tool consumers from the database ### public function getToolConsumers() { $consumers = array(); $sql = 'SELECT consumer_pk, name, consumer_key256, consumer_key, secret, lti_version, ' . 'consumer_name, consumer_version, consumer_guid, ' . 'profile, tool_proxy, settings, protected, enabled, ' . 'enable_from, enable_until, last_access, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . 'ORDER BY name'; $query = $this->db->prepare($sql); $ok = ($query !== FALSE); if ($ok) { $ok = $query->execute(); } if ($ok) { while ($row = $query->fetch(PDO::FETCH_ASSOC)) { $row = array_change_key_case($row); $key = empty($row['consumer_key']) ? $row['consumer_key256'] : $row['consumer_key']; $consumer = new ToolProvider\ToolConsumer($key, $this); $consumer->setRecordId(intval($row['consumer_pk'])); $consumer->name = $row['name']; $consumer->secret = $row['secret']; $consumer->ltiVersion = $row['lti_version']; $consumer->consumerName = $row['consumer_name']; $consumer->consumerVersion = $row['consumer_version']; $consumer->consumerGuid = $row['consumer_guid']; $consumer->profile = json_decode($row['profile']); $consumer->toolProxy = $row['tool_proxy']; $settings = unserialize($row['settings']); if (!is_array($settings)) { $settings = array(); } $consumer->setSettings($settings); $consumer->protected = (intval($row['protected']) === 1); $consumer->enabled = (intval($row['enabled']) === 1); $consumer->enableFrom = null; if (!is_null($row['enable_from'])) { $consumer->enableFrom = strtotime($row['enable_from']); } $consumer->enableUntil = null; if (!is_null($row['enable_until'])) { $consumer->enableUntil = strtotime($row['enable_until']); } $consumer->lastAccess = null; if (!is_null($row['last_access'])) { $consumer->lastAccess = strtotime($row['last_access']); } $consumer->created = strtotime($row['created']); $consumer->updated = strtotime($row['updated']); $consumers[] = $consumer; } } return $consumers; } ### ### ToolProxy methods ### ### # Load the tool proxy from the database ### public function loadToolProxy($toolProxy) { return false; } ### # Save the tool proxy to the database ### public function saveToolProxy($toolProxy) { return false; } ### # Delete the tool proxy from the database ### public function deleteToolProxy($toolProxy) { return false; } ### ### Context methods ### /** * Load context object. * * @param Context $context Context object * * @return boolean True if the context object was successfully loaded */ public function loadContext($context) { $ok = false; if (!empty($context->getRecordId())) { $sql = 'SELECT context_pk, consumer_pk, lti_context_id, type, settings, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 'WHERE (context_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $context->getRecordId(), PDO::PARAM_INT); } else { $sql = 'SELECT context_pk, consumer_pk, lti_context_id, type, settings, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 'WHERE (consumer_pk = :cid) AND (lti_context_id = :ctx)'; $query = $this->db->prepare($sql); $query->bindValue('cid', $context->getConsumer()->getRecordId(), PDO::PARAM_INT); $query->bindValue('ctx', $context->ltiContextId, PDO::PARAM_STR); } $ok = $query->execute(); if ($ok) { $row = $query->fetch(PDO::FETCH_ASSOC); $ok = ($row !== FALSE); } if ($ok) { $row = array_change_key_case($row); $context->setRecordId(intval($row['context_pk'])); $context->setConsumerId(intval($row['consumer_pk'])); $context->ltiContextId = $row['lti_context_id']; $context->type = $row['type']; $settings = unserialize($row['settings']); if (!is_array($settings)) { $settings = array(); } $context->setSettings($settings); $context->created = strtotime($row['created']); $context->updated = strtotime($row['updated']); } return $ok; } /** * Save context object. * * @param Context $context Context object * * @return boolean True if the context object was successfully saved */ public function saveContext($context) { $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); $settingsValue = serialize($context->getSettings()); $id = $context->getRecordId(); $consumer_pk = $context->getConsumer()->getRecordId(); if (empty($id)) { $sql = "INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' (consumer_pk, lti_context_id, ' . 'type, settings, created, updated) ' . 'VALUES (:cid, :ctx, :type, :settings, :created, :updated)'; $query = $this->db->prepare($sql); $query->bindValue('cid', $consumer_pk, PDO::PARAM_INT); $query->bindValue('ctx', $context->ltiContextId, PDO::PARAM_STR); $query->bindValue('type', $context->type, PDO::PARAM_STR); $query->bindValue('settings', $settingsValue, PDO::PARAM_STR); $query->bindValue('created', $now, PDO::PARAM_STR); $query->bindValue('updated', $now, PDO::PARAM_STR); } else { $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' SET ' . 'lti_context_id = :ctx, type = :type, settings = :settings, '. 'updated = :updated ' . 'WHERE (consumer_pk = :cid) AND (context_pk = :ctxid)'; $query = $this->db->prepare($sql); $query->bindValue('ctx', $context->ltiContextId, PDO::PARAM_STR); $query->bindValue('type', $context->type, PDO::PARAM_STR); $query->bindValue('settings', $settingsValue, PDO::PARAM_STR); $query->bindValue('updated', $now, PDO::PARAM_STR); $query->bindValue('cid', $consumer_pk, PDO::PARAM_INT); $query->bindValue('ctxid', $id, PDO::PARAM_INT); } $ok = $query->execute(); if ($ok) { if (empty($id)) { $context->setRecordId(intval($this->db->lastInsertId())); $context->created = $time; } $context->updated = $time; } return $ok; } /** * Delete context object. * * @param Context $context Context object * * @return boolean True if the Context object was successfully deleted */ public function deleteContext($context) { $id = $context->getRecordId(); // Delete any outstanding share keys for resource links for this context $sql = 'DELETE sk ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' sk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON sk.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.context_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any users in resource links for this context $sql = 'DELETE u ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON u.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.context_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Update any resource links for which this consumer is acting as a primary resource link $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' prl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON prl.primary_resource_link_pk = rl.resource_link_pk ' . 'SET prl.primary_resource_link_pk = null, prl.share_approved = null ' . 'WHERE rl.context_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any resource links for this consumer $sql = 'DELETE rl ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 'WHERE rl.context_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete context $sql = 'DELETE c ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ' . 'WHERE c.context_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); if ($ok) { $context->initialize(); } return $ok; } ### ### ResourceLink methods ### /** * Load resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully loaded */ public function loadResourceLink($resourceLink) { if (!empty($resourceLink->getRecordId())) { $sql = 'SELECT resource_link_pk, context_pk, consumer_pk, lti_resource_link_id, settings, primary_resource_link_pk, share_approved, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $resourceLink->getRecordId(), PDO::PARAM_INT); } else if (!empty($resourceLink->getContext())) { $sql = 'SELECT resource_link_pk, context_pk, consumer_pk, lti_resource_link_id, settings, primary_resource_link_pk, share_approved, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (context_pk = :id) AND (lti_resource_link_id = :rlid)'; $query = $this->db->prepare($sql); $query->bindValue('id', $resourceLink->getContext()->getRecordId(), PDO::PARAM_INT); $query->bindValue('rlid', $resourceLink->getId(), PDO::PARAM_STR); } else { $sql = 'SELECT r.resource_link_pk, r.context_pk, r.consumer_pk, r.lti_resource_link_id, r.settings, r.primary_resource_link_pk, r.share_approved, r.created, r.updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' r LEFT OUTER JOIN ' . $this->dbTableNamePrefix . DataConnector::CONTEXT_TABLE_NAME . ' c ON r.context_pk = c.context_pk ' . ' WHERE ((r.consumer_pk = :id1) OR (c.consumer_pk = :id2)) AND (lti_resource_link_id = :rlid)'; $query = $this->db->prepare($sql); $query->bindValue('id1', $resourceLink->getConsumer()->getRecordId(), PDO::PARAM_INT); $query->bindValue('id2', $resourceLink->getConsumer()->getRecordId(), PDO::PARAM_INT); $query->bindValue('rlid', $resourceLink->getId(), PDO::PARAM_STR); } $ok = $query->execute(); if ($ok) { $row = $query->fetch(PDO::FETCH_ASSOC); $ok = ($row !== FALSE); } if ($ok) { $row = array_change_key_case($row); $resourceLink->setRecordId(intval($row['resource_link_pk'])); if (!is_null($row['context_pk'])) { $resourceLink->setContextId(intval($row['context_pk'])); } else { $resourceLink->setContextId(null); } if (!is_null($row['consumer_pk'])) { $resourceLink->setConsumerId(intval($row['consumer_pk'])); } else { $resourceLink->setConsumerId(null); } $resourceLink->ltiResourceLinkId = $row['lti_resource_link_id']; $settings = unserialize($row['settings']); if (!is_array($settings)) { $settings = array(); } $resourceLink->setSettings($settings); if (!is_null($row['primary_resource_link_pk'])) { $resourceLink->primaryResourceLinkId = intval($row['primary_resource_link_pk']); } else { $resourceLink->primaryResourceLinkId = null; } $resourceLink->shareApproved = (is_null($row['share_approved'])) ? null : (intval($row['share_approved']) === 1); $resourceLink->created = strtotime($row['created']); $resourceLink->updated = strtotime($row['updated']); } return $ok; } /** * Save resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully saved */ public function saveResourceLink($resourceLink) { $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); $settingsValue = serialize($resourceLink->getSettings()); if (!empty($resourceLink->getContext())) { $consumerId = null; $contextId = strval($resourceLink->getContext()->getRecordId()); } else if (!empty($resourceLink->getContextId())) { $consumerId = null; $contextId = strval($resourceLink->getContextId()); } else { $consumerId = strval($resourceLink->getConsumer()->getRecordId()); $contextId = null; } if (empty($resourceLink->primaryResourceLinkId)) { $primaryResourceLinkId = null; } else { $primaryResourceLinkId = $resourceLink->primaryResourceLinkId; } $id = $resourceLink->getRecordId(); if (empty($id)) { $sql = "INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' (consumer_pk, context_pk, ' . 'lti_resource_link_id, settings, primary_resource_link_pk, share_approved, created, updated) ' . 'VALUES (:cid, :ctx, :rlid, :settings, :prlid, :share_approved, :created, :updated)'; $query = $this->db->prepare($sql); $query->bindValue('cid', $consumerId, PDO::PARAM_INT); $query->bindValue('ctx', $contextId, PDO::PARAM_INT); $query->bindValue('rlid', $resourceLink->getId(), PDO::PARAM_STR); $query->bindValue('settings', $settingsValue, PDO::PARAM_STR); $query->bindValue('prlid', $primaryResourceLinkId, PDO::PARAM_INT); $query->bindValue('share_approved', $resourceLink->shareApproved, PDO::PARAM_INT); $query->bindValue('created', $now, PDO::PARAM_STR); $query->bindValue('updated', $now, PDO::PARAM_STR); } else if (!is_null($contextId)) { $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' SET ' . 'consumer_pk = NULL, context_pk = :ctx, lti_resource_link_id = :rlid, settings = :settings, '. 'primary_resource_link_pk = :prlid, share_approved = :share_approved, updated = :updated ' . 'WHERE (resource_link_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('ctx', $contextId, PDO::PARAM_INT); $query->bindValue('rlid', $resourceLink->getId(), PDO::PARAM_STR); $query->bindValue('settings', $settingsValue, PDO::PARAM_STR); $query->bindValue('prlid', $primaryResourceLinkId, PDO::PARAM_INT); $query->bindValue('share_approved', $resourceLink->shareApproved, PDO::PARAM_INT); $query->bindValue('updated', $now, PDO::PARAM_STR); $query->bindValue('id', $id, PDO::PARAM_INT); } else { $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' SET ' . 'context_pk = :ctx, lti_resource_link_id = :rlid, settings = :settings, '. 'primary_resource_link_pk = :prlid, share_approved = :share_approved, updated = :updated ' . 'WHERE (consumer_pk = :cid) AND (resource_link_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('ctx', $contextId, PDO::PARAM_INT); $query->bindValue('rlid', $resourceLink->getId(), PDO::PARAM_STR); $query->bindValue('settings', $settingsValue, PDO::PARAM_STR); $query->bindValue('prlid', $primaryResourceLinkId, PDO::PARAM_INT); $query->bindValue('share_approved', $resourceLink->shareApproved, PDO::PARAM_INT); $query->bindValue('updated', $now, PDO::PARAM_STR); $query->bindValue('cid', $consumerId, PDO::PARAM_INT); $query->bindValue('id', $id, PDO::PARAM_INT); } $ok = $query->execute(); if ($ok) { if (empty($id)) { $resourceLink->setRecordId(intval($this->db->lastInsertId())); $resourceLink->created = $time; } $resourceLink->updated = $time; } return $ok; } /** * Delete resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully deleted */ public function deleteResourceLink($resourceLink) { $id = $resourceLink->getRecordId(); // Delete any outstanding share keys for resource links for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); // Delete users if ($ok) { $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); } // Update any resource links for which this is the primary resource link if ($ok) { $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'SET primary_resource_link_pk = NULL ' . 'WHERE (primary_resource_link_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); } // Delete resource link if ($ok) { $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); } if ($ok) { $resourceLink->initialize(); } return $ok; } /** * Get array of user objects. * * Obtain an array of User objects for users with a result sourcedId. The array may include users from other * resource links which are sharing this resource link. It may also be optionally indexed by the user ID of a specified scope. * * @param ResourceLink $resourceLink Resource link object * @param boolean $localOnly True if only users within the resource link are to be returned (excluding users sharing this resource link) * @param int $idScope Scope value to use for user IDs * * @return array Array of User objects */ public function getUserResultSourcedIDsResourceLink($resourceLink, $localOnly, $idScope) { $id = $resourceLink->getRecordId(); $users = array(); if ($localOnly) { $sql = 'SELECT u.user_pk, u.lti_result_sourcedid, u.lti_user_id, u.created, u.updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' AS u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' AS rl ' . 'ON u.resource_link_pk = rl.resource_link_pk ' . 'WHERE (rl.resource_link_pk = :id) AND (rl.primary_resource_link_pk IS NULL)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); } else { $sql = 'SELECT u.user_pk, u.lti_result_sourcedid, u.lti_user_id, u.created, u.updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' AS u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' AS rl ' . 'ON u.resource_link_pk = rl.resource_link_pk ' . 'WHERE ((rl.resource_link_pk = :id) AND (rl.primary_resource_link_pk IS NULL)) OR ' . '((rl.primary_resource_link_pk = :pid) AND (share_approved = 1))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->bindValue('pid', $id, PDO::PARAM_INT); } if ($query->execute()) { while ($row = $query->fetch(PDO::FETCH_ASSOC)) { $row = array_change_key_case($row); $user = ToolProvider\User::fromRecordId($row['user_pk'], $resourceLink->getDataConnector()); if (is_null($idScope)) { $users[] = $user; } else { $users[$user->getId($idScope)] = $user; } } } return $users; } /** * Get array of shares defined for this resource link. * * @param ResourceLink $resourceLink Resource_Link object * * @return array Array of ResourceLinkShare objects */ public function getSharesResourceLink($resourceLink) { $id = $resourceLink->getRecordId(); $shares = array(); $sql = 'SELECT consumer_pk, resource_link_pk, share_approved ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (primary_resource_link_pk = :id) ' . 'ORDER BY consumer_pk'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); if ($query->execute()) { while ($row = $query->fetch(PDO::FETCH_ASSOC)) { $row = array_change_key_case($row); $share = new ToolProvider\ResourceLinkShare(); $share->resourceLinkId = intval($row['resource_link_pk']); $share->approved = (intval($row['share_approved']) === 1); $shares[] = $share; } } return $shares; } ### ### ConsumerNonce methods ### /** * Load nonce object. * * @param ConsumerNonce $nonce Nonce object * * @return boolean True if the nonce object was successfully loaded */ public function loadConsumerNonce($nonce) { $ok = true; // Delete any expired nonce values $now = date("{$this->dateFormat} {$this->timeFormat}", time()); $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE expires <= :now'; $query = $this->db->prepare($sql); $query->bindValue('now', $now, PDO::PARAM_STR); $query->execute(); // Load the nonce $id = $nonce->getConsumer()->getRecordId(); $value = $nonce->getValue(); $sql = "SELECT value T FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE (consumer_pk = :id) AND (value = :value)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->bindValue('value', $value, PDO::PARAM_STR); $ok = $query->execute(); if ($ok) { $row = $query->fetch(PDO::FETCH_ASSOC); if ($row === false) { $ok = false; } } return $ok; } /** * Save nonce object. * * @param ConsumerNonce $nonce Nonce object * * @return boolean True if the nonce object was successfully saved */ public function saveConsumerNonce($nonce) { $id = $nonce->getConsumer()->getRecordId(); $value = $nonce->getValue(); $expires = date("{$this->dateFormat} {$this->timeFormat}", $nonce->expires); $sql = "INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' (consumer_pk, value, expires) VALUES (:id, :value, :expires)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->bindValue('value', $value, PDO::PARAM_STR); $query->bindValue('expires', $expires, PDO::PARAM_STR); $ok = $query->execute(); return $ok; } ### ### ResourceLinkShareKey methods ### /** * Load resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource_Link share key object * * @return boolean True if the resource link share key object was successfully loaded */ public function loadResourceLinkShareKey($shareKey) { $ok = false; // Clear expired share keys $now = date("{$this->dateFormat} {$this->timeFormat}", time()); $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' WHERE expires <= :now'; $query = $this->db->prepare($sql); $query->bindValue('now', $now, PDO::PARAM_STR); $query->execute(); // Load share key $id = $shareKey->getId(); $sql = 'SELECT resource_link_pk, auto_approve, expires ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . 'WHERE share_key_id = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_STR); if ($query->execute()) { $row = $query->fetch(PDO::FETCH_ASSOC); if ($row !== FALSE) { $row = array_change_key_case($row); if (intval($row['resource_link_pk']) === $shareKey->resourceLinkId) { $shareKey->autoApprove = ($row['auto_approve'] === 1); $shareKey->expires = strtotime($row['expires']); $ok = true; } } } return $ok; } /** * Save resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource link share key object * * @return boolean True if the resource link share key object was successfully saved */ public function saveResourceLinkShareKey($shareKey) { $id = $shareKey->getId(); $expires = date("{$this->dateFormat} {$this->timeFormat}", $shareKey->expires); $sql = "INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . '(share_key_id, resource_link_pk, auto_approve, expires) ' . 'VALUES (:id, :prlid, :approve, :expires)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_STR); $query->bindValue('prlid', $shareKey->resourceLinkId, PDO::PARAM_INT); $query->bindValue('approve', $shareKey->autoApprove, PDO::PARAM_INT); $query->bindValue('expires', $expires, PDO::PARAM_STR); $ok = $query->execute(); return $ok; } /** * Delete resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource link share key object * * @return boolean True if the resource link share key object was successfully deleted */ public function deleteResourceLinkShareKey($shareKey) { $id = $shareKey->getId(); $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' WHERE share_key_id = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_STR); $ok = $query->execute(); if ($ok) { $shareKey->initialize(); } return $ok; } ### ### User methods ### /** * Load user object. * * @param User $user User object * * @return boolean True if the user object was successfully loaded */ public function loadUser($user) { $ok = false; if (!empty($user->getRecordId())) { $id = $user->getRecordId(); $sql = 'SELECT user_pk, resource_link_pk, lti_user_id, lti_result_sourcedid, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (user_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); } else { $id = $user->getResourceLink()->getRecordId(); $uid = $user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY); $sql = 'SELECT user_pk, resource_link_pk, lti_user_id, lti_result_sourcedid, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = :id) AND (lti_user_id = :uid)'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->bindValue('uid', $uid, PDO::PARAM_STR); } if ($query->execute()) { $row = $query->fetch(PDO::FETCH_ASSOC); if ($row !== false) { $row = array_change_key_case($row); $user->setRecordId(intval($row['user_pk'])); $user->setResourceLinkId(intval($row['resource_link_pk'])); $user->ltiUserId = $row['lti_user_id']; $user->ltiResultSourcedId = $row['lti_result_sourcedid']; $user->created = strtotime($row['created']); $user->updated = strtotime($row['updated']); $ok = true; } } return $ok; } /** * Save user object. * * @param User $user User object * * @return boolean True if the user object was successfully saved */ public function saveUser($user) { $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); if (is_null($user->created)) { $sql = "INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' (resource_link_pk, ' . 'lti_user_id, lti_result_sourcedid, created, updated) ' . 'VALUES (:rlid, :uid, :sourcedid, :created, :updated)'; $query = $this->db->prepare($sql); $query->bindValue('rlid', $user->getResourceLink()->getRecordId(), PDO::PARAM_INT); $query->bindValue('uid', $user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY), PDO::PARAM_STR); $query->bindValue('sourcedid', $user->ltiResultSourcedId, PDO::PARAM_STR); $query->bindValue('created', $now, PDO::PARAM_STR); $query->bindValue('updated', $now, PDO::PARAM_STR); } else { $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'SET lti_result_sourcedid = :sourcedid, updated = :updated ' . 'WHERE (user_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('sourcedid', $user->ltiResultSourcedId, PDO::PARAM_STR); $query->bindValue('updated', $now, PDO::PARAM_STR); $query->bindValue('id', $user->getRecordId(), PDO::PARAM_INT); } $ok = $query->execute(); if ($ok) { if (is_null($user->created)) { $user->setRecordId(intval($this->db->lastInsertId())); $user->created = $time; } $user->updated = $time; } return $ok; } /** * Delete user object. * * @param User $user User object * * @return boolean True if the user object was successfully deleted */ public function deleteUser($user) { $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (user_pk = :id)'; $query = $this->db->prepare($sql); $query->bindValue('id', $user->getRecordId(), PDO::PARAM_INT); $ok = $query->execute(); if ($ok) { $user->initialize(); } return $ok; } } DataConnector/DataConnector_mysql.php000064400000127733152267612520013777 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ ### # NB This class assumes that a MySQL connection has already been opened to the appropriate schema ### #[\AllowDynamicProperties] class DataConnector_mysql extends DataConnector { ### ### ToolConsumer methods ### /** * Load tool consumer object. * * @param ToolConsumer $consumer ToolConsumer object * * @return boolean True if the tool consumer object was successfully loaded */ public function loadToolConsumer($consumer) { $ok = false; if (!empty($consumer->getRecordId())) { $sql = sprintf('SELECT consumer_pk, name, consumer_key256, consumer_key, secret, lti_version, ' . 'consumer_name, consumer_version, consumer_guid, ' . 'profile, tool_proxy, settings, protected, enabled, ' . 'enable_from, enable_until, last_access, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . "WHERE consumer_pk = %d", $consumer->getRecordId()); } else { $key256 = DataConnector::getConsumerKey($consumer->getKey()); $sql = sprintf('SELECT consumer_pk, name, consumer_key256, consumer_key, secret, lti_version, ' . 'consumer_name, consumer_version, consumer_guid, ' . 'profile, tool_proxy, settings, protected, enabled, ' . 'enable_from, enable_until, last_access, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . "WHERE consumer_key256 = %s", DataConnector::quoted($key256)); } $rsConsumer = mysql_query($sql); if ($rsConsumer) { while ($row = mysql_fetch_object($rsConsumer)) { if (empty($key256) || empty($row->consumer_key) || ($consumer->getKey() === $row->consumer_key)) { $consumer->setRecordId(intval($row->consumer_pk)); $consumer->name = $row->name; $consumer->setkey(empty($row->consumer_key) ? $row->consumer_key256 : $row->consumer_key); $consumer->secret = $row->secret; $consumer->ltiVersion = $row->lti_version; $consumer->consumerName = $row->consumer_name; $consumer->consumerVersion = $row->consumer_version; $consumer->consumerGuid = $row->consumer_guid; $consumer->profile = json_decode($row->profile); $consumer->toolProxy = $row->tool_proxy; $settings = unserialize($row->settings); if (!is_array($settings)) { $settings = array(); } $consumer->setSettings($settings); $consumer->protected = (intval($row->protected) === 1); $consumer->enabled = (intval($row->enabled) === 1); $consumer->enableFrom = null; if (!is_null($row->enable_from)) { $consumer->enableFrom = strtotime($row->enable_from); } $consumer->enableUntil = null; if (!is_null($row->enable_until)) { $consumer->enableUntil = strtotime($row->enable_until); } $consumer->lastAccess = null; if (!is_null($row->last_access)) { $consumer->lastAccess = strtotime($row->last_access); } $consumer->created = strtotime($row->created); $consumer->updated = strtotime($row->updated); $ok = true; break; } } mysql_free_result($rsConsumer); } return $ok; } /** * Save tool consumer object. * * @param ToolConsumer $consumer Consumer object * * @return boolean True if the tool consumer object was successfully saved */ public function saveToolConsumer($consumer) { $id = $consumer->getRecordId(); $key = $consumer->getKey(); $key256 = DataConnector::getConsumerKey($key); if ($key === $key256) { $key = null; } $protected = ($consumer->protected) ? 1 : 0; $enabled = ($consumer->enabled)? 1 : 0; $profile = (!empty($consumer->profile)) ? json_encode($consumer->profile) : null; $settingsValue = serialize($consumer->getSettings()); $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); $from = null; if (!is_null($consumer->enableFrom)) { $from = date("{$this->dateFormat} {$this->timeFormat}", $consumer->enableFrom); } $until = null; if (!is_null($consumer->enableUntil)) { $until = date("{$this->dateFormat} {$this->timeFormat}", $consumer->enableUntil); } $last = null; if (!is_null($consumer->lastAccess)) { $last = date($this->dateFormat, $consumer->lastAccess); } if (empty($id)) { $sql = sprintf("INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' (consumer_key256, consumer_key, name, ' . 'secret, lti_version, consumer_name, consumer_version, consumer_guid, profile, tool_proxy, settings, protected, enabled, ' . 'enable_from, enable_until, last_access, created, updated) ' . 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d, %d, %s, %s, %s, %s, %s)', DataConnector::quoted($key256), DataConnector::quoted($key), DataConnector::quoted($consumer->name), DataConnector::quoted($consumer->secret), DataConnector::quoted($consumer->ltiVersion), DataConnector::quoted($consumer->consumerName), DataConnector::quoted($consumer->consumerVersion), DataConnector::quoted($consumer->consumerGuid), DataConnector::quoted($profile), DataConnector::quoted($consumer->toolProxy), DataConnector::quoted($settingsValue), $protected, $enabled, DataConnector::quoted($from), DataConnector::quoted($until), DataConnector::quoted($last), DataConnector::quoted($now), DataConnector::quoted($now)); } else { $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' SET ' . 'consumer_key256 = %s, consumer_key = %s, ' . 'name = %s, secret= %s, lti_version = %s, consumer_name = %s, consumer_version = %s, consumer_guid = %s, ' . 'profile = %s, tool_proxy = %s, settings = %s, ' . 'protected = %d, enabled = %d, enable_from = %s, enable_until = %s, last_access = %s, updated = %s ' . 'WHERE consumer_pk = %d', DataConnector::quoted($key256), DataConnector::quoted($key), DataConnector::quoted($consumer->name), DataConnector::quoted($consumer->secret), DataConnector::quoted($consumer->ltiVersion), DataConnector::quoted($consumer->consumerName), DataConnector::quoted($consumer->consumerVersion), DataConnector::quoted($consumer->consumerGuid), DataConnector::quoted($profile), DataConnector::quoted($consumer->toolProxy), DataConnector::quoted($settingsValue), $protected, $enabled, DataConnector::quoted($from), DataConnector::quoted($until), DataConnector::quoted($last), DataConnector::quoted($now), $consumer->getRecordId()); } $ok = mysql_query($sql); if ($ok) { if (empty($id)) { $consumer->setRecordId(mysql_insert_id()); $consumer->created = $time; } $consumer->updated = $time; } return $ok; } /** * Delete tool consumer object. * * @param ToolConsumer $consumer Consumer object * * @return boolean True if the tool consumer object was successfully deleted */ public function deleteToolConsumer($consumer) { // Delete any nonce values for this consumer $sql = sprintf("DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Delete any outstanding share keys for resource links for this consumer $sql = sprintf('DELETE sk ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' sk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON sk.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Delete any outstanding share keys for resource links for contexts in this consumer $sql = sprintf('DELETE sk ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' sk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON sk.resource_link_pk = rl.resource_link_pk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'WHERE c.consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Delete any users in resource links for this consumer $sql = sprintf('DELETE u ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON u.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Delete any users in resource links for contexts in this consumer $sql = sprintf('DELETE u ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON u.resource_link_pk = rl.resource_link_pk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'WHERE c.consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Update any resource links for which this consumer is acting as a primary resource link $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' prl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON prl.primary_resource_link_pk = rl.resource_link_pk ' . 'SET prl.primary_resource_link_pk = NULL, prl.share_approved = NULL ' . 'WHERE rl.consumer_pk = %d', $consumer->getRecordId()); $ok = mysql_query($sql); // Update any resource links for contexts in which this consumer is acting as a primary resource link $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' prl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON prl.primary_resource_link_pk = rl.resource_link_pk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'SET prl.primary_resource_link_pk = NULL, prl.share_approved = NULL ' . 'WHERE c.consumer_pk = %d', $consumer->getRecordId()); $ok = mysql_query($sql); // Delete any resource links for this consumer $sql = sprintf('DELETE rl ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 'WHERE rl.consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Delete any resource links for contexts in this consumer $sql = sprintf('DELETE rl ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . 'WHERE c.consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Delete any contexts for this consumer $sql = sprintf('DELETE c ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ' . 'WHERE c.consumer_pk = %d', $consumer->getRecordId()); mysql_query($sql); // Delete consumer $sql = sprintf('DELETE c ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' c ' . 'WHERE c.consumer_pk = %d', $consumer->getRecordId()); $ok = mysql_query($sql); if ($ok) { $consumer->initialize(); } return $ok; } ### # Load all tool consumers from the database ### public function getToolConsumers() { $consumers = array(); $sql = 'SELECT consumer_pk, consumer_key, consumer_key, name, secret, lti_version, consumer_name, consumer_version, consumer_guid, ' . 'profile, tool_proxy, settings, ' . 'protected, enabled, enable_from, enable_until, last_access, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . 'ORDER BY name'; $rsConsumers = mysql_query($sql); if ($rsConsumers) { while ($row = mysql_fetch_object($rsConsumers)) { $consumer = new ToolProvider\ToolConsumer($row->consumer_key, $this); $consumer->setRecordId(intval($row->consumer_pk)); $consumer->name = $row->name; $consumer->secret = $row->secret; $consumer->ltiVersion = $row->lti_version; $consumer->consumerName = $row->consumer_name; $consumer->consumerVersion = $row->consumer_version; $consumer->consumerGuid = $row->consumer_guid; $consumer->profile = json_decode($row->profile); $consumer->toolProxy = $row->tool_proxy; $settings = unserialize($row->settings); if (!is_array($settings)) { $settings = array(); } $consumer->setSettings($settings); $consumer->protected = (intval($row->protected) === 1); $consumer->enabled = (intval($row->enabled) === 1); $consumer->enableFrom = null; if (!is_null($row->enable_from)) { $consumer->enableFrom = strtotime($row->enable_from); } $consumer->enableUntil = null; if (!is_null($row->enable_until)) { $consumer->enableUntil = strtotime($row->enable_until); } $consumer->lastAccess = null; if (!is_null($row->last_access)) { $consumer->lastAccess = strtotime($row->last_access); } $consumer->created = strtotime($row->created); $consumer->updated = strtotime($row->updated); $consumers[] = $consumer; } mysql_free_result($rsConsumers); } return $consumers; } ### ### ToolProxy methods ### ### # Load the tool proxy from the database ### public function loadToolProxy($toolProxy) { return false; } ### # Save the tool proxy to the database ### public function saveToolProxy($toolProxy) { return false; } ### # Delete the tool proxy from the database ### public function deleteToolProxy($toolProxy) { return false; } ### ### Context methods ### /** * Load context object. * * @param Context $context Context object * * @return boolean True if the context object was successfully loaded */ public function loadContext($context) { $ok = false; if (!empty($context->getRecordId())) { $sql = sprintf('SELECT context_pk, consumer_pk, lti_context_id, type, settings, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 'WHERE (context_pk = %d)', $context->getRecordId()); } else { $sql = sprintf('SELECT context_pk, consumer_pk, lti_context_id, type, settings, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 'WHERE (consumer_pk = %d) AND (lti_context_id = %s)', $context->getConsumer()->getRecordId(), DataConnector::quoted($context->ltiContextId)); } $rs_context = mysql_query($sql); if ($rs_context) { $row = mysql_fetch_object($rs_context); if ($row) { $context->setRecordId(intval($row->context_pk)); $context->setConsumerId(intval($row->consumer_pk)); $context->ltiContextId = $row->lti_context_id; $context->type = $row->type; $settings = unserialize($row->settings); if (!is_array($settings)) { $settings = array(); } $context->setSettings($settings); $context->created = strtotime($row->created); $context->updated = strtotime($row->updated); $ok = true; } } return $ok; } /** * Save context object. * * @param Context $context Context object * * @return boolean True if the context object was successfully saved */ public function saveContext($context) { $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); $settingsValue = serialize($context->getSettings()); $id = $context->getRecordId(); $consumer_pk = $context->getConsumer()->getRecordId(); if (empty($id)) { $sql = sprintf("INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' (consumer_pk, lti_context_id, ' . 'type, settings, created, updated) ' . 'VALUES (%d, %s, %s, %s, %s, %s)', $consumer_pk, DataConnector::quoted($context->ltiContextId), DataConnector::quoted($context->type), DataConnector::quoted($settingsValue), DataConnector::quoted($now), DataConnector::quoted($now)); } else { $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' SET ' . 'lti_context_id = %s, type = %s, settings = %s, '. 'updated = %s' . 'WHERE (consumer_pk = %d) AND (context_pk = %d)', DataConnector::quoted($context->ltiContextId), DataConnector::quoted($context->type), DataConnector::quoted($settingsValue), DataConnector::quoted($now), $consumer_pk, $id); } $ok = mysql_query($sql); if ($ok) { if (empty($id)) { $context->setRecordId(mysql_insert_id()); $context->created = $time; } $context->updated = $time; } return $ok; } /** * Delete context object. * * @param Context $context Context object * * @return boolean True if the Context object was successfully deleted */ public function deleteContext($context) { // Delete any outstanding share keys for resource links for this context $sql = sprintf('DELETE sk ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' sk ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON sk.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.context_pk = %d', $context->getRecordId()); mysql_query($sql); // Delete any users in resource links for this context $sql = sprintf('DELETE u ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON u.resource_link_pk = rl.resource_link_pk ' . 'WHERE rl.context_pk = %d', $context->getRecordId()); mysql_query($sql); // Update any resource links for which this consumer is acting as a primary resource link $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' prl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ON prl.primary_resource_link_pk = rl.resource_link_pk ' . 'SET prl.primary_resource_link_pk = null, prl.share_approved = null ' . 'WHERE rl.context_pk = %d', $context->getRecordId()); $ok = mysql_query($sql); // Delete any resource links for this consumer $sql = sprintf('DELETE rl ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . 'WHERE rl.context_pk = %d', $context->getRecordId()); mysql_query($sql); // Delete context $sql = sprintf('DELETE c ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ', 'WHERE c.context_pk = %d', $context->getRecordId()); $ok = mysql_query($sql); if ($ok) { $context->initialize(); } return $ok; } ### ### ResourceLink methods ### /** * Load resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully loaded */ public function loadResourceLink($resourceLink) { $ok = false; if (!empty($resourceLink->getRecordId())) { $sql = sprintf('SELECT resource_link_pk, context_pk, consumer_pk, lti_resource_link_id, settings, primary_resource_link_pk, share_approved, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = %d)', $resourceLink->getRecordId()); } else if (!empty($resourceLink->getContext())) { $sql = sprintf('SELECT resource_link_pk, context_pk, consumer_pk, lti_resource_link_id, settings, primary_resource_link_pk, share_approved, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (context_pk = %d) AND (lti_resource_link_id = %s)', $resourceLink->getContext()->getRecordId(), DataConnector::quoted($resourceLink->getId())); } else { $sql = sprintf('SELECT r.resource_link_pk, r.context_pk, r.consumer_pk, r.lti_resource_link_id, r.settings, r.primary_resource_link_pk, r.share_approved, r.created, r.updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' r LEFT OUTER JOIN ' . $this->dbTableNamePrefix . DataConnector::CONTEXT_TABLE_NAME . ' c ON r.context_pk = c.context_pk ' . ' WHERE ((r.consumer_pk = %d) OR (c.consumer_pk = %d)) AND (lti_resource_link_id = %s)', $resourceLink->getConsumer()->getRecordId(), $resourceLink->getConsumer()->getRecordId(), DataConnector::quoted($resourceLink->getId())); } $rsContext = mysql_query($sql); if ($rsContext) { $row = mysql_fetch_object($rsContext); if ($row) { $resourceLink->setRecordId(intval($row->resource_link_pk)); if (!is_null($row->context_pk)) { $resourceLink->setContextId(intval($row->context_pk)); } else { $resourceLink->setContextId(null); } if (!is_null($row->consumer_pk)) { $resourceLink->setConsumerId(intval($row->consumer_pk)); } else { $resourceLink->setConsumerId(null); } $resourceLink->ltiResourceLinkId = $row->lti_resource_link_id; $settings = unserialize($row->settings); if (!is_array($settings)) { $settings = array(); } $resourceLink->setSettings($settings); if (!is_null($row->primary_resource_link_pk)) { $resourceLink->primaryResourceLinkId = intval($row->primary_resource_link_pk); } else { $resourceLink->primaryResourceLinkId = null; } $resourceLink->shareApproved = (is_null($row->share_approved)) ? null : (intval($row->share_approved) === 1); $resourceLink->created = strtotime($row->created); $resourceLink->updated = strtotime($row->updated); $ok = true; } } return $ok; } /** * Save resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully saved */ public function saveResourceLink($resourceLink) { if (is_null($resourceLink->shareApproved)) { $approved = 'NULL'; } else if ($resourceLink->shareApproved) { $approved = '1'; } else { $approved = '0'; } if (empty($resourceLink->primaryResourceLinkId)) { $primaryResourceLinkId = 'NULL'; } else { $primaryResourceLinkId = strval($resourceLink->primaryResourceLinkId); } $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); $settingsValue = serialize($resourceLink->getSettings()); if (!empty($resourceLink->getContext())) { $consumerId = 'NULL'; $contextId = strval($resourceLink->getContext()->getRecordId()); } else if (!empty($resourceLink->getContextId())) { $consumerId = 'NULL'; $contextId = strval($resourceLink->getContextId()); } else { $consumerId = strval($resourceLink->getConsumer()->getRecordId()); $contextId = 'NULL'; } $id = $resourceLink->getRecordId(); if (empty($id)) { $sql = sprintf("INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' (consumer_pk, context_pk, ' . 'lti_resource_link_id, settings, primary_resource_link_pk, share_approved, created, updated) ' . 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)', $consumerId, $contextId, DataConnector::quoted($resourceLink->getId()), DataConnector::quoted($settingsValue), $primaryResourceLinkId, $approved, DataConnector::quoted($now), DataConnector::quoted($now)); } else if ($contextId !== 'NULL') { $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' SET ' . 'consumer_pk = %s, lti_resource_link_id = %s, settings = %s, '. 'primary_resource_link_pk = %s, share_approved = %s, updated = %s ' . 'WHERE (context_pk = %s) AND (resource_link_pk = %d)', $consumerId, DataConnector::quoted($resourceLink->getId()), DataConnector::quoted($settingsValue), $primaryResourceLinkId, $approved, DataConnector::quoted($now), $contextId, $id); } else { $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' SET ' . 'context_pk = %s, lti_resource_link_id = %s, settings = %s, '. 'primary_resource_link_pk = %s, share_approved = %s, updated = %s ' . 'WHERE (consumer_pk = %s) AND (resource_link_pk = %d)', $contextId, DataConnector::quoted($resourceLink->getId()), DataConnector::quoted($settingsValue), $primaryResourceLinkId, $approved, DataConnector::quoted($now), $consumerId, $id); } $ok = mysql_query($sql); if ($ok) { if (empty($id)) { $resourceLink->setRecordId(mysql_insert_id()); $resourceLink->created = $time; } $resourceLink->updated = $time; } return $ok; } /** * Delete resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully deleted */ public function deleteResourceLink($resourceLink) { // Delete any outstanding share keys for resource links for this consumer $sql = sprintf("DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = %d)', $resourceLink->getRecordId()); $ok = mysql_query($sql); // Delete users if ($ok) { $sql = sprintf("DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = %d)', $resourceLink->getRecordId()); $ok = mysql_query($sql); } // Update any resource links for which this is the primary resource link if ($ok) { $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'SET primary_resource_link_pk = NULL ' . 'WHERE (primary_resource_link_pk = %d)', $resourceLink->getRecordId()); $ok = mysql_query($sql); } // Delete resource link if ($ok) { $sql = sprintf("DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = %s)', $resourceLink->getRecordId()); $ok = mysql_query($sql); } if ($ok) { $resourceLink->initialize(); } return $ok; } /** * Get array of user objects. * * Obtain an array of User objects for users with a result sourcedId. The array may include users from other * resource links which are sharing this resource link. It may also be optionally indexed by the user ID of a specified scope. * * @param ResourceLink $resourceLink Resource link object * @param boolean $localOnly True if only users within the resource link are to be returned (excluding users sharing this resource link) * @param int $idScope Scope value to use for user IDs * * @return array Array of User objects */ public function getUserResultSourcedIDsResourceLink($resourceLink, $localOnly, $idScope) { $users = array(); if ($localOnly) { $sql = sprintf('SELECT u.user_pk, u.lti_result_sourcedid, u.lti_user_id, u.created, u.updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' AS u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' AS rl ' . 'ON u.resource_link_pk = rl.resource_link_pk ' . "WHERE (rl.resource_link_pk = %d) AND (rl.primary_resource_link_pk IS NULL)", $resourceLink->getRecordId()); } else { $sql = sprintf('SELECT u.user_pk, u.lti_result_sourcedid, u.lti_user_id, u.created, u.updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' AS u ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' AS rl ' . 'ON u.resource_link_pk = rl.resource_link_pk ' . 'WHERE ((rl.resource_link_pk = %d) AND (rl.primary_resource_link_pk IS NULL)) OR ' . '((rl.primary_resource_link_pk = %d) AND (share_approved = 1))', $resourceLink->getRecordId(), $resourceLink->getRecordId()); } $rsUser = mysql_query($sql); if ($rsUser) { while ($row = mysql_fetch_object($rsUser)) { $user = ToolProvider\User::fromResourceLink($resourceLink, $row->lti_user_id); $user->setRecordId(intval($row->user_pk)); $user->ltiResultSourcedId = $row->lti_result_sourcedid; $user->created = strtotime($row->created); $user->updated = strtotime($row->updated); if (is_null($idScope)) { $users[] = $user; } else { $users[$user->getId($idScope)] = $user; } } } return $users; } /** * Get array of shares defined for this resource link. * * @param ResourceLink $resourceLink Resource_Link object * * @return array Array of ResourceLinkShare objects */ public function getSharesResourceLink($resourceLink) { $shares = array(); $sql = sprintf('SELECT consumer_pk, resource_link_pk, share_approved ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE (primary_resource_link_pk = %d) ' . 'ORDER BY consumer_pk', $resourceLink->getRecordId()); $rsShare = mysql_query($sql); if ($rsShare) { while ($row = mysql_fetch_object($rsShare)) { $share = new ToolProvider\ResourceLinkShare(); $share->resourceLinkId = intval($row->resource_link_pk); $share->approved = (intval($row->share_approved) === 1); $shares[] = $share; } } return $shares; } ### ### ConsumerNonce methods ### /** * Load nonce object. * * @param ConsumerNonce $nonce Nonce object * * @return boolean True if the nonce object was successfully loaded */ public function loadConsumerNonce($nonce) { $ok = true; // Delete any expired nonce values $now = date("{$this->dateFormat} {$this->timeFormat}", time()); $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . " WHERE expires <= '{$now}'"; mysql_query($sql); // Load the nonce $sql = sprintf("SELECT value AS T FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE (consumer_pk = %d) AND (value = %s)', $nonce->getConsumer()->getRecordId(), DataConnector::quoted($nonce->getValue())); $rs_nonce = mysql_query($sql); if ($rs_nonce) { $row = mysql_fetch_object($rs_nonce); if ($row === false) { $ok = false; } } return $ok; } /** * Save nonce object. * * @param ConsumerNonce $nonce Nonce object * * @return boolean True if the nonce object was successfully saved */ public function saveConsumerNonce($nonce) { $expires = date("{$this->dateFormat} {$this->timeFormat}", $nonce->expires); $sql = sprintf("INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . " (consumer_pk, value, expires) VALUES (%d, %s, %s)", $nonce->getConsumer()->getRecordId(), DataConnector::quoted($nonce->getValue()), DataConnector::quoted($expires)); $ok = mysql_query($sql); return $ok; } ### ### ResourceLinkShareKey methods ### /** * Load resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource_Link share key object * * @return boolean True if the resource link share key object was successfully loaded */ public function loadResourceLinkShareKey($shareKey) { $ok = false; // Clear expired share keys $now = date("{$this->dateFormat} {$this->timeFormat}", time()); $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . " WHERE expires <= '{$now}'"; mysql_query($sql); // Load share key $id = mysql_real_escape_string($shareKey->getId()); $sql = 'SELECT resource_link_pk, auto_approve, expires ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . "WHERE share_key_id = '{$id}'"; $rsShareKey = mysql_query($sql); if ($rsShareKey) { $row = mysql_fetch_object($rsShareKey); if ($row && (intval($row->resource_link_pk) === $shareKey->resourceLinkId)) { $shareKey->autoApprove = (intval($row->auto_approve) === 1); $shareKey->expires = strtotime($row->expires); $ok = true; } } return $ok; } /** * Save resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource link share key object * * @return boolean True if the resource link share key object was successfully saved */ public function saveResourceLinkShareKey($shareKey) { if ($shareKey->autoApprove) { $approve = 1; } else { $approve = 0; } $expires = date("{$this->dateFormat} {$this->timeFormat}", $shareKey->expires); $sql = sprintf("INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . '(share_key_id, resource_link_pk, auto_approve, expires) ' . "VALUES (%s, %d, {$approve}, '{$expires}')", DataConnector::quoted($shareKey->getId()), $shareKey->resourceLinkId); $ok = mysql_query($sql); return $ok; } /** * Delete resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource link share key object * * @return boolean True if the resource link share key object was successfully deleted */ public function deleteResourceLinkShareKey($shareKey) { $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . " WHERE share_key_id = '{$shareKey->getId()}'"; $ok = mysql_query($sql); if ($ok) { $shareKey->initialize(); } return $ok; } ### ### User methods ### /** * Load user object. * * @param User $user User object * * @return boolean True if the user object was successfully loaded */ public function loadUser($user) { $ok = false; if (!empty($user->getRecordId())) { $sql = sprintf('SELECT user_pk, resource_link_pk, lti_user_id, lti_result_sourcedid, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (user_pk = %d)', $user->getRecordId()); } else { $sql = sprintf('SELECT user_pk, resource_link_pk, lti_user_id, lti_result_sourcedid, created, updated ' . "FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (resource_link_pk = %d) AND (lti_user_id = %s)', $user->getResourceLink()->getRecordId(), DataConnector::quoted($user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY))); } $rsUser = mysql_query($sql); if ($rsUser) { $row = mysql_fetch_object($rsUser); if ($row) { $user->setRecordId(intval($row->user_pk)); $user->setResourceLinkId(intval($row->resource_link_pk)); $user->ltiUserId = $row->lti_user_id; $user->ltiResultSourcedId = $row->lti_result_sourcedid; $user->created = strtotime($row->created); $user->updated = strtotime($row->updated); $ok = true; } } return $ok; } /** * Save user object. * * @param User $user User object * * @return boolean True if the user object was successfully saved */ public function saveUser($user) { $time = time(); $now = date("{$this->dateFormat} {$this->timeFormat}", $time); if (is_null($user->created)) { $sql = sprintf("INSERT INTO {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' (resource_link_pk, ' . 'lti_user_id, lti_result_sourcedid, created, updated) ' . 'VALUES (%d, %s, %s, %s, %s)', $user->getResourceLink()->getRecordId(), DataConnector::quoted($user->getId(ToolProvider\ToolProvider::ID_SCOPE_ID_ONLY)), DataConnector::quoted($user->ltiResultSourcedId), DataConnector::quoted($now), DataConnector::quoted($now)); } else { $sql = sprintf("UPDATE {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'SET lti_result_sourcedid = %s, updated = %s ' . 'WHERE (user_pk = %d)', DataConnector::quoted($user->ltiResultSourcedId), DataConnector::quoted($now), $user->getRecordId()); } $ok = mysql_query($sql); if ($ok) { if (is_null($user->created)) { $user->setRecordId(mysql_insert_id()); $user->created = $time; } $user->updated = $time; } return $ok; } /** * Delete user object. * * @param User $user User object * * @return boolean True if the user object was successfully deleted */ public function deleteUser($user) { $sql = sprintf("DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . 'WHERE (user_pk = %d)', $user->getRecordId()); $ok = mysql_query($sql); if ($ok) { $user->initialize(); } return $ok; } } DataConnector/DataConnector_pdo_sqlite.php000064400000023151152267612520014762 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class DataConnector_pdo_sqlite extends DataConnector_pdo { ### ### ToolConsumer methods ### /** * Delete tool consumer object. * * @param ToolConsumer $consumer Consumer object * * @return boolean True if the tool consumer object was successfully deleted */ public function deleteToolConsumer($consumer) { $id = $consumer->getRecordId(); // Delete any nonce values for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::NONCE_TABLE_NAME . ' WHERE consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any outstanding share keys for resource links for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any outstanding share keys for resource links for contexts in this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any users in resource links for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any users in resource links for contexts in this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Update any resource links for which this consumer is acting as a primary resource link $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'SET primary_resource_link_pk = NULL, share_approved = NULL ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.consumer_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Update any resource links for contexts in which this consumer is acting as a primary resource link $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'SET primary_resource_link_pk = NULL, share_approved = NULL ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "INNER JOIN {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ON rl.context_pk = c.context_pk ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (c.consumer_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any resource links for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any resource links for contexts in this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' c ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.context_pk = c.context_pk) AND (c.consumer_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any contexts for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 'WHERE consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONSUMER_TABLE_NAME . ' ' . 'WHERE consumer_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); if ($ok) { $consumer->initialize(); } return $ok; } ### ### Context methods ### /** * Delete context object. * * @param Context $context Context object * * @return boolean True if the Context object was successfully deleted */ public function deleteContext($context) { $id = $context->getRecordId(); // Delete any outstanding share keys for resource links for this context $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . ' ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_SHARE_KEY_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any users in resource links for this context $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . ' ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::USER_RESULT_TABLE_NAME . '.resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Update any resource links for which this consumer is acting as a primary resource link $sql = "UPDATE {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'SET primary_resource_link_pk = null, share_approved = null ' . "WHERE EXISTS (SELECT * FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' rl ' . "WHERE ({$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . '.primary_resource_link_pk = rl.resource_link_pk) AND (rl.context_pk = :id))'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete any resource links for this consumer $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::RESOURCE_LINK_TABLE_NAME . ' ' . 'WHERE context_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $query->execute(); // Delete context $sql = "DELETE FROM {$this->dbTableNamePrefix}" . DataConnector::CONTEXT_TABLE_NAME . ' ' . 'WHERE context_pk = :id'; $query = $this->db->prepare($sql); $query->bindValue('id', $id, PDO::PARAM_INT); $ok = $query->execute(); if ($ok) { $context->initialize(); } return $ok; } } DataConnector/DataConnector.php000064400000032123152267612520012536 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.0 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class DataConnector { /** * Default name for database table used to store tool consumers. */ const CONSUMER_TABLE_NAME = 'lti2_consumer'; /** * Default name for database table used to store pending tool proxies. */ const TOOL_PROXY_TABLE_NAME = 'lti2_tool_proxy'; /** * Default name for database table used to store contexts. */ const CONTEXT_TABLE_NAME = 'lti2_context'; /** * Default name for database table used to store resource links. */ const RESOURCE_LINK_TABLE_NAME = 'lti2_resource_link'; /** * Default name for database table used to store users. */ const USER_RESULT_TABLE_NAME = 'lti2_user_result'; /** * Default name for database table used to store resource link share keys. */ const RESOURCE_LINK_SHARE_KEY_TABLE_NAME = 'lti2_share_key'; /** * Default name for database table used to store nonce values. */ const NONCE_TABLE_NAME = 'lti2_nonce'; /** * Database object. * * @var object $db */ protected $db = null; /** * Prefix for database table names. * * @var string $dbTableNamePrefix */ protected $dbTableNamePrefix = ''; /** * SQL date format (default = 'Y-m-d') * * @var string $dateFormat */ protected $dateFormat = 'Y-m-d'; /** * SQL time format (default = 'H:i:s') * * @var string $timeFormat */ protected $timeFormat = 'H:i:s'; /** * Class constructor * * @param object $db Database connection object * @param string $dbTableNamePrefix Prefix for database table names (optional, default is none) */ public function __construct($db, $dbTableNamePrefix = '') { $this->db = $db; $this->dbTableNamePrefix = $dbTableNamePrefix; } ### ### ToolConsumer methods ### /** * Load tool consumer object. * * @param ToolConsumer $consumer ToolConsumer object * * @return boolean True if the tool consumer object was successfully loaded */ public function loadToolConsumer($consumer) { $consumer->secret = 'secret'; $consumer->enabled = true; $now = time(); $consumer->created = $now; $consumer->updated = $now; return true; } /** * Save tool consumer object. * * @param ToolConsumer $consumer Consumer object * * @return boolean True if the tool consumer object was successfully saved */ public function saveToolConsumer($consumer) { $consumer->updated = time(); return true; } /** * Delete tool consumer object. * * @param ToolConsumer $consumer Consumer object * * @return boolean True if the tool consumer object was successfully deleted */ public function deleteToolConsumer($consumer) { $consumer->initialize(); return true; } /** * Load tool consumer objects. * * @return array Array of all defined ToolConsumer objects */ public function getToolConsumers() { return array(); } ### ### ToolProxy methods ### /** * Load tool proxy object. * * @param ToolProxy $toolProxy ToolProxy object * * @return boolean True if the tool proxy object was successfully loaded */ public function loadToolProxy($toolProxy) { $now = time(); $toolProxy->created = $now; $toolProxy->updated = $now; return true; } /** * Save tool proxy object. * * @param ToolProxy $toolProxy ToolProxy object * * @return boolean True if the tool proxy object was successfully saved */ public function saveToolProxy($toolProxy) { $toolProxy->updated = time(); return true; } /** * Delete tool proxy object. * * @param ToolProxy $toolProxy ToolProxy object * * @return boolean True if the tool proxy object was successfully deleted */ public function deleteToolProxy($toolProxy) { $toolProxy->initialize(); return true; } ### ### Context methods ### /** * Load context object. * * @param Context $context Context object * * @return boolean True if the context object was successfully loaded */ public function loadContext($context) { $now = time(); $context->created = $now; $context->updated = $now; return true; } /** * Save context object. * * @param Context $context Context object * * @return boolean True if the context object was successfully saved */ public function saveContext($context) { $context->updated = time(); return true; } /** * Delete context object. * * @param Context $context Context object * * @return boolean True if the Context object was successfully deleted */ public function deleteContext($context) { $context->initialize(); return true; } ### ### ResourceLink methods ### /** * Load resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully loaded */ public function loadResourceLink($resourceLink) { $now = time(); $resourceLink->created = $now; $resourceLink->updated = $now; return true; } /** * Save resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully saved */ public function saveResourceLink($resourceLink) { $resourceLink->updated = time(); return true; } /** * Delete resource link object. * * @param ResourceLink $resourceLink Resource_Link object * * @return boolean True if the resource link object was successfully deleted */ public function deleteResourceLink($resourceLink) { $resourceLink->initialize(); return true; } /** * Get array of user objects. * * Obtain an array of User objects for users with a result sourcedId. The array may include users from other * resource links which are sharing this resource link. It may also be optionally indexed by the user ID of a specified scope. * * @param ResourceLink $resourceLink Resource link object * @param boolean $localOnly True if only users within the resource link are to be returned (excluding users sharing this resource link) * @param int $idScope Scope value to use for user IDs * * @return array Array of User objects */ public function getUserResultSourcedIDsResourceLink($resourceLink, $localOnly, $idScope) { return array(); } /** * Get array of shares defined for this resource link. * * @param ResourceLink $resourceLink Resource_Link object * * @return array Array of ResourceLinkShare objects */ public function getSharesResourceLink($resourceLink) { return array(); } ### ### ConsumerNonce methods ### /** * Load nonce object. * * @param ConsumerNonce $nonce Nonce object * * @return boolean True if the nonce object was successfully loaded */ public function loadConsumerNonce($nonce) { return false; // assume the nonce does not already exist } /** * Save nonce object. * * @param ConsumerNonce $nonce Nonce object * * @return boolean True if the nonce object was successfully saved */ public function saveConsumerNonce($nonce) { return true; } ### ### ResourceLinkShareKey methods ### /** * Load resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource_Link share key object * * @return boolean True if the resource link share key object was successfully loaded */ public function loadResourceLinkShareKey($shareKey) { return true; } /** * Save resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource link share key object * * @return boolean True if the resource link share key object was successfully saved */ public function saveResourceLinkShareKey($shareKey) { return true; } /** * Delete resource link share key object. * * @param ResourceLinkShareKey $shareKey Resource link share key object * * @return boolean True if the resource link share key object was successfully deleted */ public function deleteResourceLinkShareKey($shareKey) { return true; } ### ### User methods ### /** * Load user object. * * @param User $user User object * * @return boolean True if the user object was successfully loaded */ public function loadUser($user) { $now = time(); $user->created = $now; $user->updated = $now; return true; } /** * Save user object. * * @param User $user User object * * @return boolean True if the user object was successfully saved */ public function saveUser($user) { $user->updated = time(); return true; } /** * Delete user object. * * @param User $user User object * * @return boolean True if the user object was successfully deleted */ public function deleteUser($user) { $user->initialize(); return true; } ### ### Other methods ### /** * Return a hash of a consumer key for values longer than 255 characters. * * @param string $key * @return string */ protected static function getConsumerKey($key) { $len = strlen($key); if ($len > 255) { $key = 'sha512:' . hash('sha512', $key); } return $key; } /** * Create data connector object. * * A data connector provides access to persistent storage for the different objects. * * Names of tables may be given a prefix to allow multiple versions to share the same schema. A separate sub-class is defined for * each different database connection - the class to use is determined by inspecting the database object passed, but this can be overridden * (for example, to use a bespoke connector) by specifying a type. If no database is passed then this class is used which acts as a dummy * connector with no persistence. * * @param string $dbTableNamePrefix Prefix for database table names (optional, default is none) * @param object $db A database connection object or string (optional, default is no persistence) * @param string $type The type of data connector (optional, default is based on $db parameter) * * @return DataConnector Data connector object */ public static function getDataConnector($dbTableNamePrefix = '', $db = null, $type = '') { if (is_null($dbTableNamePrefix)) { $dbTableNamePrefix = ''; } if (!is_null($db) && empty($type)) { if (is_object($db)) { $type = get_class($db); } } $type = strtolower($type); if (($type === 'pdo') && ($db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite')) { $type .= '_sqlite'; } if (!empty($type)) { $type ="DataConnector_{$type}"; } else { $type ='DataConnector'; } $type = "\\IMSGlobal\\LTI\\ToolProvider\\DataConnector\\{$type}"; $dataConnector = new $type($db, $dbTableNamePrefix); return $dataConnector; } /** * Generate a random string. * * The generated string will only comprise letters (upper- and lower-case) and digits. * * @param int $length Length of string to be generated (optional, default is 8 characters) * * @return string Random string */ static function getRandomString($length = 8) { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; $value = ''; $charsLength = strlen($chars) - 1; for ($i = 1 ; $i <= $length; $i++) { $value .= $chars[rand(0, $charsLength)]; } return $value; } /** * Quote a string for use in a database query. * * Any single quotes in the value passed will be replaced with two single quotes. If a null value is passed, a string * of 'null' is returned (which will never be enclosed in quotes irrespective of the value of the $addQuotes parameter. * * @param string $value Value to be quoted * @param bool $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true) * @return string The quoted string. */ static function quoted($value, $addQuotes = true) { if (is_null($value)) { $value = 'null'; } else { $value = str_replace('\'', '\'\'', $value); if ($addQuotes) { $value = "'{$value}'"; } } return $value; } } ContentItemPlacement.php000064400000002201152267612520011342 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ContentItemPlacement { /** * Class constructor. * * @param int $displayWidth Width of item location * @param int $displayHeight Height of item location * @param string $documentTarget Location to open content in * @param string $windowTarget Name of window target */ function __construct($displayWidth, $displayHeight, $documentTarget, $windowTarget) { if (!empty($displayWidth)) { $this->displayWidth = $displayWidth; } if (!empty($displayHeight)) { $this->displayHeight = $displayHeight; } if (!empty($documentTarget)) { $this->documentTarget = $documentTarget; } if (!empty($windowTarget)) { $this->windowTarget = $windowTarget; } } } ContentItem.php000064400000006113152267612520007517 0ustar00 * @copyright IMS Global Learning Consortium Inc * @date 2016 * @version 3.0.2 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 */ #[\AllowDynamicProperties] class ContentItem { /** * Media type for LTI launch links. */ const LTI_LINK_MEDIA_TYPE = 'application/vnd.ims.lti.v1.ltilink'; /** * Class constructor. * * @param string $type Class type of content-item * @param ContentItemPlacement $placementAdvice Placement object for item (optional) * @param string $id URL of content-item (optional) */ function __construct($type, $placementAdvice = null, $id = null) { $this->{'@type'} = $type; if (is_object($placementAdvice) && (count(get_object_vars($placementAdvice)) > 0)) { $this->placementAdvice = $placementAdvice; } if (!empty($id)) { $this->{'@id'} = $id; } } /** * Set a URL value for the content-item. * * @param string $url URL value */ public function setUrl($url) { if (!empty($url)) { $this->url = $url; } else { unset($this->url); } } /** * Set a media type value for the content-item. * * @param string $mediaType Media type value */ public function setMediaType($mediaType) { if (!empty($mediaType)) { $this->mediaType = $mediaType; } else { unset($this->mediaType); } } /** * Set a title value for the content-item. * * @param string $title Title value */ public function setTitle($title) { if (!empty($title)) { $this->title = $title; } else if (isset($this->title)) { unset($this->title); } } /** * Set a link text value for the content-item. * * @param string $text Link text value */ public function setText($text) { if (!empty($text)) { $this->text = $text; } else if (isset($this->text)) { unset($this->text); } } /** * Wrap the content items to form a complete application/vnd.ims.lti.v1.contentitems+json media type instance. * * @param mixed $items An array of content items or a single item * @return string */ public static function toJson($items) { /* $data = array(); if (!is_array($items)) { $data[] = json_encode($items); } else { foreach ($items as $item) { $data[] = json_encode($item); } } $json = '{ "@context" : "http://purl.imsglobal.org/ctx/lti/v1/ContentItem", "@graph" : [' . implode(", ", $data) . '] }'; */ $obj = new \stdClass(); $obj->{'@context'} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem'; if (!is_array($items)) { $obj->{'@graph'} = array(); $obj->{'@graph'}[] = $items; } else { $obj->{'@graph'} = $items; } return json_encode($obj); } }