diff --git a/main/gradebook/index.php b/main/gradebook/index.php index 4ae86f2b86..04feeeb673 100755 --- a/main/gradebook/index.php +++ b/main/gradebook/index.php @@ -772,6 +772,12 @@ if (!api_is_allowed_to_edit(null, true)) { if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true)) { echo ''; } else { + // Tool introduction + Display::display_introduction_section( + TOOL_GRADEBOOK, + ['ToolbarSet' => 'AssessmentsIntroduction'] + ); + if (!empty($actionsLeft)) { echo $toolbar = Display::toolbarAction( 'gradebook-student-actions', @@ -780,11 +786,6 @@ if (isset($first_time) && $first_time == 1 && api_is_allowed_to_edit(null, true) } if (api_is_allowed_to_edit(null, true)) { - // Tool introduction - Display::display_introduction_section( - TOOL_GRADEBOOK, - ['ToolbarSet' => 'AssessmentsIntroduction'] - ); if (((empty($selectCat)) || (isset($_GET['cidReq']) && $_GET['cidReq'] !== '')) || (isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'false') diff --git a/main/inc/lib/template.lib.php b/main/inc/lib/template.lib.php index ddafc20605..d2a87662b7 100755 --- a/main/inc/lib/template.lib.php +++ b/main/inc/lib/template.lib.php @@ -1852,20 +1852,25 @@ class Template $course = api_get_course_entity($courseId); // @TODO: support right-to-left in title $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; $socialMeta .= ''."\n"; $metaDescription = api_get_setting('meta_description'); if (!empty($course->getDescription())) { $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; } elseif (!empty($metaDescription)) { $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; } $picture = CourseManager::getPicturePath($course, true); if (!empty($picture)) { $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; } else { - $socialMeta .= $this->getMetaPortalImagePath(); + $socialMeta .= $this->getMetaPortalImagePath($metaTitle); } } elseif ($sessionId !== 0) { // If we are on a session "about" screen, publish info about the session @@ -1873,6 +1878,7 @@ class Template $session = $em->find('ChamiloCoreBundle:Session', $sessionId); $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; $socialMeta .= 'getId()}/about/".'" />'."\n"; $sessionValues = new ExtraFieldValue('session'); @@ -1882,19 +1888,23 @@ class Template if (!empty($sessionImage) && is_file($sessionImageSysPath)) { $sessionImagePath = api_get_path(WEB_UPLOAD_PATH).$sessionImage; $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; } else { - $socialMeta .= $this->getMetaPortalImagePath(); + $socialMeta .= $this->getMetaPortalImagePath($metaTitle); } } else { // Otherwise (not a course nor a session, nor a user, nor a badge), publish portal info $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; $socialMeta .= ''."\n"; $metaDescription = api_get_setting('meta_description'); if (!empty($metaDescription)) { $socialMeta .= ''."\n"; + $socialMeta .= ''."\n"; } - $socialMeta .= $this->getMetaPortalImagePath(); + $socialMeta .= $this->getMetaPortalImagePath($metaTitle); } } } @@ -1906,10 +1916,10 @@ class Template /** * Get platform meta image tag (check meta_image_path setting, then use the logo). - * + * @param string $imageAlt The alt attribute for the image * @return string The meta image HTML tag, or empty */ - private function getMetaPortalImagePath() + private function getMetaPortalImagePath($imageAlt = '') { // Load portal meta image if defined $metaImage = api_get_setting('meta_image_path'); @@ -1919,11 +1929,15 @@ class Template if (!empty($metaImage)) { if (is_file($metaImageSysPath)) { $portalImageMeta = ''."\n"; + $portalImageMeta .= ''."\n"; + $portalImageMeta .= ''."\n"; } } else { $logo = ChamiloApi::getPlatformLogoPath($this->theme); if (!empty($logo)) { $portalImageMeta = ''."\n"; + $portalImageMeta .= ''."\n"; + $portalImageMeta .= ''."\n"; } } diff --git a/main/lang/english/trad4all.inc.php b/main/lang/english/trad4all.inc.php index a9a2d41b04..a8fdbfa9bb 100644 --- a/main/lang/english/trad4all.inc.php +++ b/main/lang/english/trad4all.inc.php @@ -7582,7 +7582,7 @@ $SkillProfile = "Skill profile"; $AchievedSkills = "Achieved skills"; $BusinessCard = "Business card"; $BadgeDetails = "Badge details"; -$TheUserXNotYetAchievedTheSkillX = "The user %s not yet achieved the skill \"%s\""; +$TheUserXNotYetAchievedTheSkillX = "The user %s has not achieved the skill \"%s\" yet"; $IssuedBadgeInformation = "Issued badge information"; $RecipientDetails = "Recipient details"; $SkillAcquiredAt = "Skill acquired at"; diff --git a/plugin/ims_lti/Entity/ImsLtiTool.php b/plugin/ims_lti/Entity/ImsLtiTool.php index 12bce88a29..1d4e05f25c 100644 --- a/plugin/ims_lti/Entity/ImsLtiTool.php +++ b/plugin/ims_lti/Entity/ImsLtiTool.php @@ -249,6 +249,26 @@ class ImsLtiTool return $this->course === null; } + /** + * @param array $params + * + * @return null|string + */ + public function encodeCustomParams(array $params) + { + if (empty($params)) { + return null; + } + + $pairs = []; + + foreach ($params as $key => $value) { + $pairs[] = "$key=$value"; + } + + return implode("\n", $pairs); + } + /** * @return array */ diff --git a/plugin/ims_lti/ImsLtiPlugin.php b/plugin/ims_lti/ImsLtiPlugin.php index 9c81379aac..1f85736fe5 100644 --- a/plugin/ims_lti/ImsLtiPlugin.php +++ b/plugin/ims_lti/ImsLtiPlugin.php @@ -298,18 +298,22 @@ class ImsLtiPlugin extends Plugin */ public static function getUserRoles(User $user) { + if (DRH === $user->getStatus()) { + return 'urn:lti:role:ims/lis/Mentor'; + } + if ($user->getStatus() === INVITEE) { - return 'Learner/GuestLearner,Learner'; + return 'Learner,urn:lti:role:ims/lis/Learner/GuestLearner'; } if (!api_is_allowed_to_edit(false, true)) { - return 'Learner,Learner/Learner'; + return 'Learner'; } $roles = ['Instructor']; if (api_is_platform_admin_by_id($user->getId())) { - $roles[] = 'Administrator/SystemAdministrator'; + $roles[] = 'urn:lti:role:ims/lis/Administrator'; } return implode(',', $roles); @@ -331,24 +335,22 @@ class ImsLtiPlugin extends Plugin } /** - * @param Course $course - * @param Session|null $session + * @param User $currentUser * * @return string */ - public static function getRoleScopeMentor(Course $course, Session $session = null) + public static function getRoleScopeMentor(User $currentUser) { - $scope = []; - - if ($session) { - $students = $session->getUserCourseSubscriptionsByStatus($course, Session::STUDENT); - } else { - $students = $course->getStudents(); + if (DRH !== $currentUser->getStatus()) { + return ''; } - /** @var SessionRelCourseRelUser|CourseRelUser $subscription */ - foreach ($students as $subscription) { - $scope[] = self::generateToolUserId($subscription->getUser()->getId()); + $followedUsers = UserManager::get_users_followed_by_drh($currentUser->getId()); + + $scope = []; + + foreach ($followedUsers as $userInfo) { + $scope[] = self::generateToolUserId($userInfo['user_id']); } return implode(',', $scope); @@ -394,6 +396,13 @@ class ImsLtiPlugin extends Plugin !empty($contentItem['text']) ? $contentItem['text'] : null ); + if (!empty($contentItem['custom'])) { + $newLtiTool + ->setCustomParams( + $newLtiTool->encodeCustomParams($contentItem['custom']) + ); + } + $em->persist($newLtiTool); $em->flush(); diff --git a/plugin/ims_lti/configure.php b/plugin/ims_lti/configure.php index b562f0d8bf..f9eb22f986 100644 --- a/plugin/ims_lti/configure.php +++ b/plugin/ims_lti/configure.php @@ -126,7 +126,9 @@ switch ($action) { $tool ->setName($formValues['name']) - ->setDescription($formValues['description']) + ->setDescription( + empty($formValues['description']) ? null : $formValues['description'] + ) ->setActiveDeepLinking( !empty($formValues['deep_linking']) ) diff --git a/plugin/ims_lti/form.php b/plugin/ims_lti/form.php index 247efc620e..ded7e2a42b 100644 --- a/plugin/ims_lti/form.php +++ b/plugin/ims_lti/form.php @@ -82,15 +82,19 @@ $params['roles'] = ImsLtiPlugin::getUserRoles($user); if ($tool->isSharingName()) { $params['lis_person_name_given'] = $user->getFirstname(); $params['lis_person_name_family'] = $user->getLastname(); - $params['lis_person_name_full'] = $user->getCompleteName(); + $params['lis_person_name_full'] = $user->getFirstname().' '.$user->getLastname(); } if ($tool->isSharingEmail()) { $params['lis_person_contact_email_primary'] = $user->getEmail(); } -if (api_is_allowed_to_edit(false, true)) { - $params['role_scope_mentor'] = ImsLtiPlugin::getRoleScopeMentor($course, $session); +if (DRH === $user->getStatus()) { + $scopeMentor = ImsLtiPlugin::getRoleScopeMentor($user); + + if (!empty($scopeMentor)) { + $params['role_scope_mentor'] = $scopeMentor; + } } $params['context_id'] = $course->getId(); @@ -132,7 +136,7 @@ $result = $oauth->sign(array( encType="application/x-www-form-urlencoded"> $values) { //Dump parameters - echo ''; + echo ''.PHP_EOL; } ?> diff --git a/plugin/ims_lti/src/Form/FrmAdd.php b/plugin/ims_lti/src/Form/FrmAdd.php index 2bfb512712..db0886a13f 100644 --- a/plugin/ims_lti/src/Form/FrmAdd.php +++ b/plugin/ims_lti/src/Form/FrmAdd.php @@ -42,8 +42,7 @@ class FrmAdd extends FormValidator $this->addTextarea('description', get_lang('Description')); if (null === $this->baseTool) { - $this->addElement('url', 'launch_url', $plugin->get_lang('LaunchUrl')); - $this->addRule('launch_url', get_lang('Required'), 'required'); + $this->addUrl('launch_url', $plugin->get_lang('LaunchUrl'), true); $this->addText('consumer_key', $plugin->get_lang('ConsumerKey')); $this->addText('shared_secret', $plugin->get_lang('SharedSecret')); } @@ -74,6 +73,8 @@ class FrmAdd extends FormValidator $this->addHtml(''); $this->addButtonCreate($plugin->get_lang('AddExternalTool')); $this->applyFilter('__ALL__', 'Security::remove_XSS'); + $this->applyFilter('__ALL__', 'htmlspecialchars_decode'); + $this->applyFilter('__ALL__', 'trim'); } public function setDefaultValues() diff --git a/plugin/ims_lti/src/Form/FrmEdit.php b/plugin/ims_lti/src/Form/FrmEdit.php index b323b222aa..14a29e4e01 100644 --- a/plugin/ims_lti/src/Form/FrmEdit.php +++ b/plugin/ims_lti/src/Form/FrmEdit.php @@ -59,8 +59,7 @@ class FrmEdit extends FormValidator $this->addTextarea('description', get_lang('Description')); if (null === $parent) { - $this->addElement('url', 'launch_url', $plugin->get_lang('LaunchUrl')); - $this->addRule('launch_url', get_lang('Required'), 'required'); + $this->addUrl('launch_url', $plugin->get_lang('LaunchUrl'), true); $this->addText('consumer_key', $plugin->get_lang('ConsumerKey')); $this->addText('shared_secret', $plugin->get_lang('SharedSecret')); } @@ -89,10 +88,12 @@ class FrmEdit extends FormValidator $this->addCheckBox('share_email', null, $plugin->get_lang('ShareLauncherEmail')); $this->addCheckBox('share_picture', null, $plugin->get_lang('ShareLauncherPicture')); $this->addHtml(''); - $this->addButtonCreate($plugin->get_lang('EditExternalTool')); + $this->addButtonUpdate($plugin->get_lang('EditExternalTool')); $this->addHidden('id', $this->tool->getId()); $this->addHidden('action', 'edit'); $this->applyFilter('__ALL__', 'Security::remove_XSS'); + $this->applyFilter('__ALL__', 'htmlspecialchars_decode'); + $this->applyFilter('__ALL__', 'trim'); } public function setDefaultValues() diff --git a/plugin/ims_lti/src/ImsLtiServiceReadRequest.php b/plugin/ims_lti/src/ImsLtiServiceReadRequest.php index d58881ab99..6f22ca545b 100644 --- a/plugin/ims_lti/src/ImsLtiServiceReadRequest.php +++ b/plugin/ims_lti/src/ImsLtiServiceReadRequest.php @@ -51,16 +51,17 @@ class ImsLtiServiceReadRequest extends ImsLtiServiceRequest if (!empty($results)) { /** @var Result $result */ $result = $results[0]; + $ltiScore = 0; if (!empty($result->get_score())) { $ltiScore = $result->get_score() / $evaluation->getMax(); - - $responseDescription = sprintf( - get_plugin_lang('ScoreForXUserIsYScore', 'ImsLtiPlugin'), - $user->getId(), - $ltiScore - ); } + + $responseDescription = sprintf( + get_plugin_lang('ScoreForXUserIsYScore', 'ImsLtiPlugin'), + $user->getId(), + $ltiScore + ); } $this->statusInfo diff --git a/plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php b/plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php index 685e7b410b..67569ccd93 100644 --- a/plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php +++ b/plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php @@ -18,7 +18,7 @@ class ImsLtiServiceReplaceRequest extends ImsLtiServiceRequest { parent::__construct($xml); - $this->responseType = ImsLtiServiceResponse::TYPE_DELETE; + $this->responseType = ImsLtiServiceResponse::TYPE_REPLACE; $this->xmlRequest = $this->xmlRequest->replaceResultRequest; } @@ -26,7 +26,17 @@ class ImsLtiServiceReplaceRequest extends ImsLtiServiceRequest { $resultRecord = $this->xmlRequest->resultRecord; $sourcedId = (string) $resultRecord->sourcedGUID->sourcedId; - $resultScore = (float) $resultRecord->result->resultScore->textString; + $resultScore = (string) $resultRecord->result->resultScore->textString; + + if (!is_numeric($resultScore)) { + $this->statusInfo + ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR) + ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE); + + return; + } + + $resultScore = (float) $resultScore; if (0 > $resultScore || 1 < $resultScore) { $this->statusInfo diff --git a/plugin/ims_lti/src/ImsLtiServiceRequestFactory.php b/plugin/ims_lti/src/ImsLtiServiceRequestFactory.php index 8c8dd4967a..0756e96aab 100644 --- a/plugin/ims_lti/src/ImsLtiServiceRequestFactory.php +++ b/plugin/ims_lti/src/ImsLtiServiceRequestFactory.php @@ -16,13 +16,19 @@ class ImsLtiServiceRequestFactory $bodyChildren = $xml->imsx_POXBody->children(); if (!empty($bodyChildren)) { - switch ($bodyChildren->getName()) { + $name = $bodyChildren->getName(); + + switch ($name) { case 'replaceResultRequest': return new ImsLtiServiceReplaceRequest($xml); case 'readResultRequest': return new ImsLtiServiceReadRequest($xml); case 'deleteResultRequest': return new ImsLtiServiceDeleteRequest($xml); + default: + $name = str_replace(['ResultRequest', 'Request'], '', $name); + + return new ImsLtiServiceUnsupportedRequest($xml, $name); } } diff --git a/plugin/ims_lti/src/ImsLtiServiceResponseFactory.php b/plugin/ims_lti/src/ImsLtiServiceResponseFactory.php index 9cac5508f0..cb8b0ae516 100644 --- a/plugin/ims_lti/src/ImsLtiServiceResponseFactory.php +++ b/plugin/ims_lti/src/ImsLtiServiceResponseFactory.php @@ -22,6 +22,8 @@ class ImsLtiServiceResponseFactory return new ImsLtiServiceReadResponse($statusInfo, $bodyParam); case ImsLtiServiceResponse::TYPE_DELETE: return new ImsLtiServiceDeleteResponse($statusInfo, $bodyParam); + default: + return new ImsLtiServiceUnsupportedResponse($statusInfo, $type); } return null; diff --git a/plugin/ims_lti/src/ImsLtiServiceResponseStatus.php b/plugin/ims_lti/src/ImsLtiServiceResponseStatus.php index 5f06c2bf91..4fb535d488 100644 --- a/plugin/ims_lti/src/ImsLtiServiceResponseStatus.php +++ b/plugin/ims_lti/src/ImsLtiServiceResponseStatus.php @@ -13,7 +13,7 @@ class ImsLtiServiceResponseStatus const CODEMAJOR_SUCCESS = 'success'; const CODEMAJOR_PROCESSING = 'processing'; const CODEMAJOR_FAILURE = 'failure'; - const CODEMAJOR_UNSUPPORTED = 'supported'; + const CODEMAJOR_UNSUPPORTED = 'unsupported'; /** * @var string diff --git a/plugin/ims_lti/src/ImsLtiServiceUnsupportedRequest.php b/plugin/ims_lti/src/ImsLtiServiceUnsupportedRequest.php new file mode 100644 index 0000000000..28f79f4d6e --- /dev/null +++ b/plugin/ims_lti/src/ImsLtiServiceUnsupportedRequest.php @@ -0,0 +1,31 @@ +responseType = $name; + } + + protected function processBody() + { + $this->statusInfo + ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS) + ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_UNSUPPORTED) + ->setDescription( + $this->responseType.' is not supported' + ); + } +} diff --git a/plugin/ims_lti/src/ImsLtiServiceUnsupportedResponse.php b/plugin/ims_lti/src/ImsLtiServiceUnsupportedResponse.php new file mode 100644 index 0000000000..1d45f583cb --- /dev/null +++ b/plugin/ims_lti/src/ImsLtiServiceUnsupportedResponse.php @@ -0,0 +1,28 @@ +setOperationRefIdentifier($type); + + parent::__construct($statusInfo); + } + + /** + * @param SimpleXMLElement $xmlBody + */ + protected function generateBody(SimpleXMLElement $xmlBody) + { + } +} diff --git a/plugin/ims_lti/view/start.tpl b/plugin/ims_lti/view/start.tpl index 7434fcdd1d..a8c13a39fa 100644 --- a/plugin/ims_lti/view/start.tpl +++ b/plugin/ims_lti/view/start.tpl @@ -1,5 +1,5 @@ {% if tool.description %} - {{ tool.description }} + {{ tool.description|nl2br }} {% endif %} diff --git a/tests/behat/features/bootstrap/FeatureContext.php b/tests/behat/features/bootstrap/FeatureContext.php index f891fb5788..e0bd97c381 100644 --- a/tests/behat/features/bootstrap/FeatureContext.php +++ b/tests/behat/features/bootstrap/FeatureContext.php @@ -384,10 +384,18 @@ class FeatureContext extends MinkContext */ public function waitForThePageToBeLoaded() { - //$this->getSession()->wait(10000, "document.readyState === 'complete'"); $this->getSession()->wait(3000); } + /** + * @When /^wait very long for the page to be loaded$/ + */ + public function waitVeryLongForThePageToBeLoaded() + { + //$this->getSession()->wait(10000, "document.readyState === 'complete'"); + $this->getSession()->wait(6000); + } + /** * @When /^I check the "([^"]*)" radio button$/ */ diff --git a/tests/behat/features/toolExercise.feature b/tests/behat/features/toolExercise.feature index a9184a225c..73e94d3b06 100644 --- a/tests/behat/features/toolExercise.feature +++ b/tests/behat/features/toolExercise.feature @@ -384,7 +384,7 @@ Feature: Exercise tool | none | 80 / 60 | 133.33% | | Total | 190 / 190 | 100% | - Scenario: Teacher see exercise results by categories + Scenario: Teacher looks at exercise results by categories Given I am on "/user_portal.php" And I am on course "TEMP" homepage in session "Session Exercise" Then I should see "TEMP (Session Exercise)" @@ -392,7 +392,7 @@ Feature: Exercise tool And I follow "Exercise for Behat test" And I follow "Results and feedback" Then I should see "Learner score" - And wait for the page to be loaded + And wait very long for the page to be loaded And I follow "Grade activity" Then I should see "Score for the test: 190 / 190" And I should see the table "#category_results":
{{ tool.description }}
{{ tool.description|nl2br }}