Procházet zdrojové kódy

Merge branch '1.11.x' of github.com:chamilo/chamilo-lms into 1.11.x

Julio Montoya před 6 roky
rodič
revize
f999ea46a0

+ 5 - 0
.htaccess

@@ -23,6 +23,8 @@ RewriteRule ^courses/([^/]+)/?$ main/course_home/course_home.php?cDir=$1 [QSA,L]
 RewriteRule ^courses/([^/]+)/index.php$ main/course_home/course_home.php?cDir=$1 [QSA,L]
 
 # Rewrite everything in the scorm folder of a course to the download script
+# except JS and CSS files, which can be served directly
+RewriteRule ^courses/([^/]+)/scorm/(.*([\.js|\.css]))$ app/courses/$1/scorm/$2 [QSA,L]
 RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_url=/$2&cDir=$1 [QSA,L]
 
 # Rewrite everything in the document folder of a course to the download script
@@ -64,6 +66,9 @@ RewriteRule ^main/newscorm/(.*)$ main/lp/$1 [QSA,L]
 # service Information
 RewriteRule ^service/(\d{1,})$ plugin/buycourses/src/service_information.php?service_id=$1 [L]
 
+# LTI outcome service
+RewriteRule ^ims_lti/outcome_service/(\d{1,})$ plugin/ims_lti/outcome_service.php?t=$1 [L]
+
 # This rule is very generic and should always remain at the bottom of .htaccess
 # http://my.chamilo.net/jdoe to http://my.chamilo.net/user.php?jdoe
 RewriteRule ^([^/.]+)/?$ user.php?$1 [L]

+ 18 - 5
main/admin/index.php

@@ -113,11 +113,6 @@ if (api_is_platform_admin()) {
     if (api_get_configuration_value('show_link_request_hrm_user')) {
         $items[] = ['url' => 'user_linking_requests.php', 'label' => get_lang('UserLinkingRequests')];
     }
-} elseif (api_is_session_admin() && api_get_configuration_value('limit_session_admin_role')) {
-    $items = [
-        ['url' => 'user_list.php', 'label' => get_lang('UserList')],
-        ['url' => 'user_add.php', 'label' => get_lang('AddUsers')],
-    ];
 } else {
     $items = [
         ['url' => 'user_list.php', 'label' => get_lang('UserList')],
@@ -125,6 +120,24 @@ if (api_is_platform_admin()) {
         ['url' => 'user_import.php', 'label' => get_lang('ImportUserListXMLCSV')],
         ['url' => 'usergroups.php', 'label' => get_lang('Classes')],
     ];
+
+    if (api_is_session_admin()) {
+        if (true === api_get_configuration_value('limit_session_admin_role')) {
+            $items = array_filter($items, function (array $item) {
+                $urls = ['user_list.php', 'user_add.php'];
+
+                return in_array($item['url'], $urls);
+            });
+        }
+
+        if (true === api_get_configuration_value('limit_session_admin_list_users')) {
+            $items = array_filter($items, function (array $item) {
+                $urls = ['user_list.php'];
+
+                return !in_array($item['url'], $urls);
+            });
+        }
+    }
 }
 
 $blocks['users']['items'] = $items;

+ 2 - 0
main/admin/user_list.php

@@ -12,6 +12,8 @@ use ChamiloSession as Session;
 $cidReset = true;
 require_once __DIR__.'/../inc/global.inc.php';
 
+api_protect_session_admin_list_users();
+
 $urlId = api_get_current_access_url_id();
 $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
 

+ 1 - 1
main/course_info/about.php

@@ -51,7 +51,7 @@ $courseDescriptionTools = $em->getRepository('ChamiloCourseBundle:CCourseDescrip
 $courseValues = new ExtraFieldValue('course');
 $userValues = new ExtraFieldValue('user');
 
-$urlCourse = api_get_path(WEB_PATH).'main/course/about.php?course_id='.$courseId;
+$urlCourse = api_get_path(WEB_PATH).'main/course_info/about.php?course_id='.$courseId;
 $courseTeachers = $course->getTeachers();
 $teachersData = [];
 

+ 13 - 0
main/inc/lib/api.lib.php

@@ -8993,6 +8993,19 @@ function api_protect_limit_for_session_admin()
     }
 }
 
+/**
+ * Limits that a session admin has access to list users.
+ * When limit_session_admin_list_users configuration variable is set to true.
+ */
+function api_protect_session_admin_list_users()
+{
+    $limitAdmin = api_get_configuration_value('limit_session_admin_list_users');
+
+    if (api_is_session_admin() && true === $limitAdmin) {
+        api_not_allowed(true);
+    }
+}
+
 /**
  * @return bool
  */

+ 2 - 0
main/install/configuration.dist.php

@@ -411,6 +411,8 @@ ALTER TABLE portfolio_category CHANGE title title LONGTEXT NOT NULL;
 //$_configuration['system_announce_extra_roles'] = false;
 // Limits the features that a session admin has access to from the main admin panel (removes users import and usergroups)
 //$_configuration['limit_session_admin_role'] = false;
+// Limits that a session admin has access to list users
+//$_configuration['limit_session_admin_list_users'] = false;
 // Course tools visibility edition in sessions
 //$_configuration['allow_edit_tool_visibility_in_session'] = false;
 // Enable the support to ODF files

+ 31 - 15
plugin/ims_lti/ImsLtiPlugin.php

@@ -1,18 +1,17 @@
 <?php
 /* For license terms, see /license.txt */
 
+use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\CourseRelUser;
-use Chamilo\CoreBundle\Entity\GradebookEvaluation;
 use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CourseBundle\Entity\CTool;
-use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
 use Chamilo\UserBundle\Entity\User;
+use Doctrine\DBAL\DBALException;
 use Doctrine\DBAL\Schema\Schema;
 use Doctrine\DBAL\Types\Type;
-use Doctrine\DBAL\DBALException;
 use Symfony\Component\Filesystem\Filesystem;
-use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
 
 /**
  * Description of MsiLti
@@ -127,8 +126,9 @@ class ImsLtiPlugin extends Plugin
             $toolTable->addColumn('shared_secret', Type::STRING);
             $toolTable->addColumn('custom_params', Type::TEXT)->setNotnull(false);
             $toolTable->addColumn('is_global', Type::BOOLEAN);
-            $toolTable->addColumn('active_deep_linking', Type::BOOLEAN)->setNotnull(false)->setDefault(false);
-            $toolTable->addColumn('c_id', Type::INTEGER);
+            $toolTable->addColumn('active_deep_linking', Type::BOOLEAN)->setDefault(false);
+            $toolTable->addColumn('c_id', Type::INTEGER)->setNotnull(false);
+            $toolTable->addColumn('gradebook_eval_id', Type::INTEGER)->setNotnull(false);
             $toolTable->addForeignKeyConstraint(
                 'course',
                 ['c_id'],
@@ -136,7 +136,6 @@ class ImsLtiPlugin extends Plugin
                 [],
                 'FK_C5E47F7C91D79BD3'
             );
-            $toolTable->addColumn('gradebook_eval_id', Type::INTEGER, []);
             $toolTable->addForeignKeyConstraint(
                 'gradebook_evaluation',
                 ['gradebook_eval_id'],
@@ -196,7 +195,7 @@ class ImsLtiPlugin extends Plugin
     {
         $button = Display::toolbarButton(
             $this->get_lang('ConfigureExternalTool'),
-            api_get_path(WEB_PLUGIN_PATH).'ims_lti/add.php?'.api_get_cidreq(),
+            api_get_path(WEB_PLUGIN_PATH).'ims_lti/configure.php?'.api_get_cidreq(),
             'cog',
             'primary'
         );
@@ -365,36 +364,36 @@ class ImsLtiPlugin extends Plugin
 
     /**
      * @param array      $contentItem
-     * @param ImsLtiTool $ltiTool
+     * @param ImsLtiTool $baseLtiTool
      * @param Course     $course
      *
      * @throws \Doctrine\ORM\OptimisticLockException
      */
-    public function saveItemAsLtiLink(array $contentItem, ImsLtiTool $ltiTool, Course $course)
+    public function saveItemAsLtiLink(array $contentItem, ImsLtiTool $baseLtiTool, Course $course)
     {
         $em = Database::getManager();
         $ltiToolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
 
-        $url = empty($contentItem['url']) ? $ltiTool->getLaunchUrl() : $contentItem['url'];
+        $url = empty($contentItem['url']) ? $baseLtiTool->getLaunchUrl() : $contentItem['url'];
 
         /** @var ImsLtiTool $newLtiTool */
-        $newLtiTool = $ltiToolRepo->findOneBy(['launchUrl' => $url, 'isGlobal' => false]);
+        $newLtiTool = $ltiToolRepo->findOneBy(['launchUrl' => $url, 'isGlobal' => false, 'course' => $course]);
 
         if (empty($newLtiTool)) {
             $newLtiTool = new ImsLtiTool();
             $newLtiTool
                 ->setLaunchUrl($url)
                 ->setConsumerKey(
-                    $ltiTool->getConsumerKey()
+                    $baseLtiTool->getConsumerKey()
                 )
                 ->setSharedSecret(
-                    $ltiTool->getSharedSecret()
+                    $baseLtiTool->getSharedSecret()
                 );
         }
 
         $newLtiTool
             ->setName(
-                !empty($contentItem['title']) ? $contentItem['title'] : $ltiTool->getName()
+                !empty($contentItem['title']) ? $contentItem['title'] : $baseLtiTool->getName()
             )
             ->setDescription(
                 !empty($contentItem['text']) ? $contentItem['text'] : null
@@ -447,4 +446,21 @@ class ImsLtiPlugin extends Plugin
 
         return $response;
     }
+
+    /**
+     * @param int    $toolId
+     * @param Course $course
+     *
+     * @return bool
+     */
+    public static function existsToolInCourse($toolId, Course $course)
+    {
+        $em = Database::getManager();
+        $toolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
+
+        /** @var ImsLtiTool $tool */
+        $tool = $toolRepo->findOneBy(['id' => $toolId, 'isGlobal' => false, 'course' => $course]);
+
+        return !empty($tool);
+    }
 }

+ 399 - 317
plugin/ims_lti/OAuthSimple.php

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * OAuthSimple - A simpler version of OAuth
  *
@@ -7,35 +8,34 @@
  * @author     jr conlin <src@jrconlin.com>
  * @copyright  unitedHeroes.net 2011
  * @version    1.3
- * @license    See license.txt
- *
+ * @license    BSD licence.
  */
-
-class OAuthSimple {
+class OAuthSimple
+{
     private $_secrets;
     private $_default_signature_method;
     private $_action;
     private $_nonce_chars;
 
     /**
-	 * Constructor
+     * OAuthSimple constructor.
      *
-	 * @access public
-     * @param api_key (String) The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use.
-     * @param shared_secret (String) The shared secret. This value is also usually provided by the site you wish to use.
-	 * @return OAuthSimple (Object)
+     * @param string $APIKey       The API Key (sometimes referred to as the consumer key). This value is usually
+     *                             supplied by the site you wish to use.
+     * @param string $sharedSecret The shared secret. This value is also usually provided by the site you wish to use.
+     *
+     * @return OAuthSimple
      */
-    function __construct ($APIKey = "", $sharedSecret=""){
+    function __construct($APIKey = "", $sharedSecret = "")
+    {
 
-        if (!empty($APIKey))
-		{
-			$this->_secrets['consumer_key'] = $APIKey;
-		}
+        if (!empty($APIKey)) {
+            $this->_secrets['consumer_key'] = $APIKey;
+        }
 
-        if (!empty($sharedSecret))
-		{
-			$this->_secrets['shared_secret'] = $sharedSecret;
-		}
+        if (!empty($sharedSecret)) {
+            $this->_secrets['shared_secret'] = $sharedSecret;
+        }
 
         $this->_default_signature_method = "HMAC-SHA1";
         $this->_action = "GET";
@@ -45,495 +45,577 @@ class OAuthSimple {
     }
 
     /**
-	 * Reset the parameters and URL
-	 *
-	 * @access public
-	 * @return OAuthSimple (Object)
-	 */
-    public function reset() {
+     * Reset the parameters and URL.
+     *
+     * @return OAuthSimple
+     */
+    public function reset()
+    {
         $this->_parameters = Array();
-        $this->path = NULL;
-        $this->sbs = NULL;
+        $this->path = null;
+        $this->sbs = null;
 
         return $this;
     }
 
     /**
-	 * Set the parameters either from a hash or a string
-	 *
-	 * @access public
-	 * @param(string, object) List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash)
-	 * @return OAuthSimple (Object)
-	 */
-    public function setParameters ($parameters=Array()) {
-
-        if (is_string($parameters))
-		{
-			$parameters = $this->_parseParameterString($parameters);
-		}
-        if (empty($this->_parameters))
-		{
-			$this->_parameters = $parameters;
-		}
-        else if (!empty($parameters))
-		{
-			$this->_parameters = array_merge($this->_parameters,$parameters);
-		}
-        if (empty($this->_parameters['oauth_nonce']))
-		{
-			$this->_getNonce();
-		}
-        if (empty($this->_parameters['oauth_timestamp']))
-		{
-			 $this->_getTimeStamp();
-		}
-        if (empty($this->_parameters['oauth_consumer_key']))
-		{
-			$this->_getApiKey();
-		}
-        if (empty($this->_parameters['oauth_token']))
-		{
-			  $this->_getAccessToken();
-		}
-        if (empty($this->_parameters['oauth_signature_method']))
-		{
+     * Set the parameters either from a hash or a string.
+     *
+     * @param array $parameters List of parameters for the call,
+     *                          this can either be a URI string (e.g."foo=bar&gorp=banana" or an object/hash)
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return OAuthSimple
+     */
+    public function setParameters($parameters = [])
+    {
+
+        if (is_string($parameters)) {
+            $parameters = $this->_parseParameterString($parameters);
+        }
+        if (empty($this->_parameters)) {
+            $this->_parameters = $parameters;
+        } elseif (!empty($parameters)) {
+            $this->_parameters = array_merge($this->_parameters, $parameters);
+        }
+        if (empty($this->_parameters['oauth_nonce'])) {
+            $this->_getNonce();
+        }
+        if (empty($this->_parameters['oauth_timestamp'])) {
+            $this->_getTimeStamp();
+        }
+        if (empty($this->_parameters['oauth_consumer_key'])) {
+            $this->_getApiKey();
+        }
+        if (empty($this->_parameters['oauth_token'])) {
+            $this->_getAccessToken();
+        }
+        if (empty($this->_parameters['oauth_signature_method'])) {
             $this->setSignatureMethod();
-		}
-        if (empty($this->_parameters['oauth_version']))
-		{
-            $this->_parameters['oauth_version']="1.0";
-		}
+        }
+        if (empty($this->_parameters['oauth_version'])) {
+            $this->_parameters['oauth_version'] = "1.0";
+        }
 
         return $this;
     }
 
     /**
-	 * Convenience method for setParameters
-	 *
-	 * @access public
-	 * @see setParameters
-	 */
-    public function setQueryString ($parameters)
+     * Convenience method for setParameters.
+     *
+     * @param array $parameters
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return OAuthSimple
+     */
+    public function setQueryString($parameters)
     {
         return $this->setParameters($parameters);
     }
 
     /**
-	 * Set the target URL (does not include the parameters)
-	 *
-     * @param path (String) the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
-	 * @return OAuthSimple (Object)
+     * Set the target URL (does not include the parameters).
+     *
+     * @param string $path The fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return OAuthSimple
      */
-    public function setURL ($path)
-	{
-        if (empty($path))
-		{
+    public function setURL($path)
+    {
+        if (empty($path)) {
             throw new OAuthSimpleException('No path specified for OAuthSimple.setURL');
-		}
-        $this->_path=$path;
+        }
+
+        $this->_path = $path;
 
         return $this;
     }
 
     /**
-	 * Convenience method for setURL
+     * Convenience method for setURL.
+     *
+     * @param string $path
      *
-     * @param path (String)
-	 * @see setURL
+     * @return mixed
      */
-    public function setPath ($path)
+    public function setPath($path)
     {
-        return $this->_path=$path;
+        return $this->_path = $path;
     }
 
     /**
-	 * Set the "action" for the url, (e.g. GET,POST, DELETE, etc.)
+     * Set the "action" for the url, (e.g. GET,POST, DELETE, etc.).
      *
-     * @param action (String) HTTP Action word.
-	 * @return OAuthSimple (Object)
+     * @param string $action HTTP Action word.
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return $this
      */
-    public function setAction ($action)
+    public function setAction($action)
     {
-        if (empty($action))
-		{
-			$action = 'GET';
-		}
+        if (empty($action)) {
+            $action = 'GET';
+        }
         $action = strtoupper($action);
-        if (preg_match('/[^A-Z]/',$action))
-		{
+        if (preg_match('/[^A-Z]/', $action)) {
             throw new OAuthSimpleException('Invalid action specified for OAuthSimple.setAction');
-		}
+        }
         $this->_action = $action;
 
         return $this;
     }
 
     /**
-	 * Set the signatures (as well as validate the ones you have)
+     * Set the signatures (as well as validate the ones you have).
+     *
+     * @param array $signatures   object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token:
+     *                            oauth_secret:}
      *
-     * @param signatures (object) object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:}
-	 * @return OAuthSimple (Object)
+     * @throws OAuthSimpleException
+     *
+     * @return $this
      */
-    public function signatures ($signatures)
+    public function signatures($signatures)
     {
-        if (!empty($signatures) && !is_array($signatures))
-		{
+        if (!empty($signatures) && !is_array($signatures)) {
             throw new OAuthSimpleException('Must pass dictionary array to OAuthSimple.signatures');
-		}
-        if (!empty($signatures))
-        {
-            if (empty($this->_secrets))
-            {
-                $this->_secrets=Array();
+        }
+        if (!empty($signatures)) {
+            if (empty($this->_secrets)) {
+                $this->_secrets = Array();
             }
-            $this->_secrets=array_merge($this->_secrets,$signatures);
+            $this->_secrets = array_merge($this->_secrets, $signatures);
         }
-        if (isset($this->_secrets['api_key']))
-		{
+        if (isset($this->_secrets['api_key'])) {
             $this->_secrets['consumer_key'] = $this->_secrets['api_key'];
-		}
-        if (isset($this->_secrets['access_token']))
-		{
+        }
+        if (isset($this->_secrets['access_token'])) {
             $this->_secrets['oauth_token'] = $this->_secrets['access_token'];
-		}
-        if (isset($this->_secrets['access_secret']))
-		{
+        }
+        if (isset($this->_secrets['access_secret'])) {
             $this->_secrets['shared_secret'] = $this->_secrets['access_secret'];
         }
-        if (isset($this->_secrets['oauth_token_secret']))
-		{
+        if (isset($this->_secrets['oauth_token_secret'])) {
             $this->_secrets['oauth_secret'] = $this->_secrets['oauth_token_secret'];
-		}
-        if (empty($this->_secrets['consumer_key']))
-		{
+        }
+        if (empty($this->_secrets['consumer_key'])) {
             throw new OAuthSimpleException('Missing required consumer_key in OAuthSimple.signatures');
         }
-        if (empty($this->_secrets['shared_secret']))
-		{
+        if (empty($this->_secrets['shared_secret'])) {
             throw new OAuthSimpleException('Missing requires shared_secret in OAuthSimple.signatures');
-		}
-        if (!empty($this->_secrets['oauth_token']) && empty($this->_secrets['oauth_secret']))
-		{
+        }
+        if (!empty($this->_secrets['oauth_token']) && empty($this->_secrets['oauth_secret'])) {
             throw new OAuthSimpleException('Missing oauth_secret for supplied oauth_token in OAuthSimple.signatures');
-		}
+        }
 
         return $this;
     }
 
+    /**
+     * @param array $signatures
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return OAuthSimple
+     */
     public function setTokensAndSecrets($signatures)
     {
         return $this->signatures($signatures);
     }
 
     /**
-	 * Set the signature method (currently only Plaintext or SHA-MAC1)
-     *
-     * @param method (String) Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now)
-	 * @return OAuthSimple (Object)
-    */
-    public function setSignatureMethod ($method="")
-	{
-        if (empty($method))
-		{
+     * Set the signature method (currently only Plaintext or SHA-MAC1).
+     *
+     * @param string $method Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now).
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return $this
+     */
+    public function setSignatureMethod($method = "")
+    {
+        if (empty($method)) {
             $method = $this->_default_signature_method;
-		}
+        }
         $method = strtoupper($method);
-        switch($method)
-        {
+        switch ($method) {
             case 'PLAINTEXT':
             case 'HMAC-SHA1':
-                $this->_parameters['oauth_signature_method']=$method;
-				break;
+                $this->_parameters['oauth_signature_method'] = $method;
+                break;
             default:
-                throw new OAuthSimpleException ("Unknown signing method $method specified for OAuthSimple.setSignatureMethod");
-				break;
+                throw new OAuthSimpleException (
+                    "Unknown signing method $method specified for OAuthSimple.setSignatureMethod"
+                );
+                break;
         }
 
-		return $this;
+        return $this;
     }
 
-    /** sign the request
+    /**
+     * Sign the request.
      *
      * note: all arguments are optional, provided you've set them using the
      * other helper functions.
      *
-     * @param args (Array) hash of arguments for the call {action, path, parameters (array), method, signatures (array)} all arguments are optional.
-	 * @return (Array) signed values
+     * @param array $args Optional.
+     *                    Hash of arguments for the call {action, path, parameters (array), method, signatures, (array)}
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return array
      */
-    public function sign($args=array())
+    public function sign($args = array())
     {
-        if (!empty($args['action']))
-		{
+        if (!empty($args['action'])) {
             $this->setAction($args['action']);
-		}
-        if (!empty($args['path']))
-		{
+        }
+        if (!empty($args['path'])) {
             $this->setPath($args['path']);
         }
-        if (!empty($args['method']))
-		{
+        if (!empty($args['method'])) {
             $this->setSignatureMethod($args['method']);
-		}
-        if (!empty($args['signatures']))
-		{
+        }
+        if (!empty($args['signatures'])) {
             $this->signatures($args['signatures']);
-		}
-        if (empty($args['parameters']))
-		{
-            $args['parameters']=array();
-		}
+        }
+        if (empty($args['parameters'])) {
+            $args['parameters'] = array();
+        }
         $this->setParameters($args['parameters']);
         $normParams = $this->_normalizedParameters();
 
-        return Array (
+        return Array(
             'parameters' => $this->_parameters,
             'signature' => self::_oauthEscape($this->_parameters['oauth_signature']),
-            'signed_url' => $this->_path . '?' . $normParams,
+            'signed_url' => $this->_path.'?'.$normParams,
             'header' => $this->getHeaderString(),
-            'sbs'=> $this->sbs
-            );
+            'sbs' => $this->sbs,
+        );
     }
 
     /**
-	 * Return a formatted "header" string
+     * Return a formatted "header" string.
      *
      * NOTE: This doesn't set the "Authorization: " prefix, which is required.
      * It's not set because various set header functions prefer different
      * ways to do that.
      *
-     * @param args (Array)
-	 * @return $result (String)
-    */
-    public function getHeaderString ($args=array())
+     * @param array $args
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return null|string|string[]
+     */
+    public function getHeaderString($args = array())
     {
-        if (empty($this->_parameters['oauth_signature']))
-		{
+        if (empty($this->_parameters['oauth_signature'])) {
             $this->sign($args);
-		}
+        }
         $result = 'OAuth ';
 
-        foreach ($this->_parameters as $pName => $pValue)
-        {
-            if (strpos($pName,'oauth_') !== 0)
-			{
+        foreach ($this->_parameters as $pName => $pValue) {
+            if (strpos($pName, 'oauth_') !== 0) {
                 continue;
-			}
-            if (is_array($pValue))
-            {
-                foreach ($pValue as $val)
-                {
-                    $result .= $pName .'="' . self::_oauthEscape($val) . '", ';
-                }
             }
-            else
-            {
-                $result .= $pName . '="' . self::_oauthEscape($pValue) . '", ';
+            if (is_array($pValue)) {
+                foreach ($pValue as $val) {
+                    $result .= $pName.'="'.self::_oauthEscape($val).'", ';
+                }
+            } else {
+                $result .= $pName.'="'.self::_oauthEscape($pValue).'", ';
             }
         }
 
-        return preg_replace('/, $/','',$result);
+        return preg_replace('/, $/', '', $result);
     }
 
-    private function _parseParameterString ($paramString)
+    /**
+     * @param string $paramString
+     *
+     * @return array
+     */
+    private function _parseParameterString($paramString)
     {
-        $elements = explode('&',$paramString);
+        $elements = explode('&', $paramString);
         $result = array();
-        foreach ($elements as $element)
-        {
-            list ($key,$token) = explode('=',$element);
-            if ($token)
-			{
+        foreach ($elements as $element) {
+            list ($key, $token) = explode('=', $element);
+            if ($token) {
                 $token = urldecode($token);
-			}
-            if (!empty($result[$key]))
-            {
-                if (!is_array($result[$key]))
-				{
-                    $result[$key] = array($result[$key],$token);
-				}
-                else
-				{
-                    array_push($result[$key],$token);
-				}
             }
-            else
-                $result[$key]=$token;
+            if (!empty($result[$key])) {
+                if (!is_array($result[$key])) {
+                    $result[$key] = array($result[$key], $token);
+                } else {
+                    array_push($result[$key], $token);
+                }
+            } else {
+                $result[$key] = $token;
+            }
         }
+
         return $result;
     }
 
-
+    /**
+     * @param string $string
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return int|mixed|string
+     */
     private static function _oauthEscape($string)
     {
-        if ($string === 0) { return 0; }
-		if ($string == '0') { return '0'; }
-        if (strlen($string) == 0) { return ''; }
+        if ($string === 0) {
+            return 0;
+        }
+        if ($string == '0') {
+            return '0';
+        }
+        if (strlen($string) == 0) {
+            return '';
+        }
         if (is_array($string)) {
             throw new OAuthSimpleException('Array passed to _oauthEscape');
-		}
+        }
         $string = urlencode($string);
 
-	    //FIX: urlencode of ~ and '+'
+        //FIX: urlencode of ~ and '+'
         $string = str_replace(
-            Array('%7E','+'  ), // Replace these
-            Array('~',  '%20'), // with these
-            $string);
+            Array('%7E', '+'), // Replace these
+            Array('~', '%20'), // with these
+            $string
+        );
 
         return $string;
     }
 
-    private function _getNonce($length=5)
+    /**
+     * @param int $length
+     *
+     * @return string
+     */
+    private function _getNonce($length = 5)
     {
         $result = '';
         $cLength = strlen($this->_nonce_chars);
-        for ($i=0; $i < $length; $i++)
-        {
-            $rnum = rand(0,$cLength - 1);
-            $result .= substr($this->_nonce_chars,$rnum,1);
+        for ($i = 0; $i < $length; $i++) {
+            $rnum = rand(0, $cLength - 1);
+            $result .= substr($this->_nonce_chars, $rnum, 1);
         }
         $this->_parameters['oauth_nonce'] = $result;
 
         return $result;
     }
 
+    /**
+     * @throws OAuthSimpleException
+     *
+     * @return mixed
+     */
     private function _getApiKey()
     {
-        if (empty($this->_secrets['consumer_key']))
-        {
+        if (empty($this->_secrets['consumer_key'])) {
             throw new OAuthSimpleException('No consumer_key set for OAuthSimple');
         }
-        $this->_parameters['oauth_consumer_key']=$this->_secrets['consumer_key'];
+        $this->_parameters['oauth_consumer_key'] = $this->_secrets['consumer_key'];
 
         return $this->_parameters['oauth_consumer_key'];
     }
 
+    /**
+     * @throws OAuthSimpleException
+     *
+     * @return string
+     */
     private function _getAccessToken()
     {
-        if (!isset($this->_secrets['oauth_secret']))
-		{
+        if (!isset($this->_secrets['oauth_secret'])) {
             return '';
-		}
-        if (!isset($this->_secrets['oauth_token']))
-		{
+        }
+        if (!isset($this->_secrets['oauth_token'])) {
             throw new OAuthSimpleException('No access token (oauth_token) set for OAuthSimple.');
-		}
+        }
         $this->_parameters['oauth_token'] = $this->_secrets['oauth_token'];
 
         return $this->_parameters['oauth_token'];
     }
 
+    /**
+     * @return int
+     */
     private function _getTimeStamp()
     {
         return $this->_parameters['oauth_timestamp'] = time();
     }
 
+    /**
+     * @throws OAuthSimpleException
+     *
+     * @return string
+     */
     private function _normalizedParameters()
     {
-		$normalized_keys = array();
-		$return_array = array();
+        $normalized_keys = array();
+        $return_array = array();
 
-        foreach ( $this->_parameters as $paramName=>$paramValue) {
+        foreach ($this->_parameters as $paramName => $paramValue) {
             if (preg_match('/w+_secret/', $paramName) OR
                 $paramName == "oauth_signature") {
-                    continue;
-                }
+                continue;
+            }
             // Read parameters from a file. Hope you're practicing safe PHP.
             //if (strpos($paramValue, '@') !== 0 && !file_exists(substr($paramValue, 1)))
-			//{
-				if (is_array($paramValue))
-				{
-					$normalized_keys[self::_oauthEscape($paramName)] = array();
-					foreach($paramValue as $item)
-					{
-						array_push($normalized_keys[self::_oauthEscape($paramName)],  self::_oauthEscape($item));
-					}
-				}
-				else
-				{
-					$normalized_keys[self::_oauthEscape($paramName)] = self::_oauthEscape($paramValue);
-				}
-			//}
-        }
-
-		ksort($normalized_keys);
-
-		foreach($normalized_keys as $key=>$val)
-		{
-			if (is_array($val))
-			{
-				sort($val);
-				foreach($val as $element)
-				{
-					array_push($return_array, $key . "=" . $element);
-				}
-			}
-			else
-			{
-				array_push($return_array, $key .'='. $val);
-			}
+            //{
+            if (is_array($paramValue)) {
+                $normalized_keys[self::_oauthEscape($paramName)] = array();
+                foreach ($paramValue as $item) {
+                    array_push($normalized_keys[self::_oauthEscape($paramName)], self::_oauthEscape($item));
+                }
+            } else {
+                $normalized_keys[self::_oauthEscape($paramName)] = self::_oauthEscape($paramValue);
+            }
+            //}
+        }
+
+        ksort($normalized_keys);
+
+        foreach ($normalized_keys as $key => $val) {
+            if (is_array($val)) {
+                sort($val);
+                foreach ($val as $element) {
+                    array_push($return_array, $key."=".$element);
+                }
+            } else {
+                array_push($return_array, $key.'='.$val);
+            }
 
         }
         $presig = join("&", $return_array);
         $sig = $this->_generateSignature($presig);
-        $this->_parameters['oauth_signature']=$sig;
+        $this->_parameters['oauth_signature'] = $sig;
         array_push($return_array, "oauth_signature=$sig");
-		return join("&", $return_array);
-    }
 
+        return join("&", $return_array);
+    }
 
-    private function _generateSignature ($parameters="")
+    /**
+     * @param string $parameters
+     *
+     * @throws OAuthSimpleException
+     *
+     * @return string
+     */
+    private function _generateSignature($parameters = "")
     {
         $secretKey = '';
-		if(isset($this->_secrets['shared_secret']))
-		{
-			$secretKey = self::_oauthEscape($this->_secrets['shared_secret']);
-		}
-
-		$secretKey .= '&';
-		if(isset($this->_secrets['oauth_secret']))
-		{
+        if (isset($this->_secrets['shared_secret'])) {
+            $secretKey = self::_oauthEscape($this->_secrets['shared_secret']);
+        }
+
+        $secretKey .= '&';
+        if (isset($this->_secrets['oauth_secret'])) {
             $secretKey .= self::_oauthEscape($this->_secrets['oauth_secret']);
         }
-        if(!empty($parameters)){
+        if (!empty($parameters)) {
             $parameters = urlencode($parameters);
         }
-        switch($this->_parameters['oauth_signature_method'])
-        {
+        switch ($this->_parameters['oauth_signature_method']) {
             case 'PLAINTEXT':
                 return urlencode($secretKey);;
             case 'HMAC-SHA1':
                 $this->sbs = self::_oauthEscape($this->_action).'&'.self::_oauthEscape($this->_path).'&'.$parameters;
 
-                return base64_encode(hash_hmac('sha1',$this->sbs,$secretKey,TRUE));
+                return base64_encode(hash_hmac('sha1', $this->sbs, $secretKey, true));
             default:
                 throw new OAuthSimpleException('Unknown signature method for OAuthSimple');
-				break;
+                break;
+        }
+    }
+
+    /**
+     * @param $string
+     *
+     * @return string
+     */
+    public static function generateBodyHash($string)
+    {
+        $hash = sha1($string, true);
+
+        return base64_encode($hash);
+    }
+
+    /**
+     * @param string $authorizationHeader
+     *
+     * @return array
+     */
+    public static function getAuthorizationParams($authorizationHeader)
+    {
+        if ('OAuth ' !== substr($authorizationHeader, 0, 6)) {
+            return [];
+        }
+
+        $params = [];
+        $authString = str_replace('OAuth ', '', $authorizationHeader);
+        $authParts = explode(',', $authString);
+
+        foreach ($authParts as $authPart) {
+            list($key, $value) = explode('=', $authPart, 2);
+
+            $key = trim($key);
+            $value = trim($value, " \"");
+
+            $params[$key] = urldecode($value);
         }
+
+        return $params;
     }
 }
 
-class OAuthSimpleException extends Exception {
-
-	public function __construct($err, $isDebug = FALSE)
-	{
-		self::log_error($err);
-		if ($isDebug)
-		{
-			self::display_error($err, TRUE);
-		}
-	}
-
-	public static function log_error($err)
-	{
-		error_log($err, 0);
-	}
-
-	public static function display_error($err, $kill = FALSE)
-	{
-		print_r($err);
-		if ($kill === FALSE)
-		{
-			die();
-		}
-	}
+/**
+ * Class OAuthSimpleException.
+ */
+class OAuthSimpleException extends Exception
+{
+    /**
+     * OAuthSimpleException constructor.
+     *
+     * @param string $err
+     * @param bool   $isDebug
+     */
+    public function __construct($err, $isDebug = false)
+    {
+        self::log_error($err);
+        if ($isDebug) {
+            self::display_error($err, true);
+        }
+    }
+
+    /**
+     * @param string $err
+     */
+    public static function log_error($err)
+    {
+        error_log($err, 0);
+    }
+
+    /**
+     * @param string $err
+     * @param bool   $kill
+     */
+    public static function display_error($err, $kill = false)
+    {
+        print_r($err);
+        if ($kill === false) {
+            die();
+        }
+    }
 }

+ 0 - 116
plugin/ims_lti/add.php

@@ -1,116 +0,0 @@
-<?php
-/* For license terms, see /license.txt */
-
-use Chamilo\CoreBundle\Entity\Course;
-use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
-
-require_once __DIR__.'/../../main/inc/global.inc.php';
-
-api_protect_course_script();
-api_protect_teacher_script();
-
-$plugin = ImsLtiPlugin::create();
-$em = Database::getManager();
-$toolsRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
-
-/** @var ImsLtiTool $baseTool */
-$baseTool = isset($_REQUEST['type']) ? $toolsRepo->find(intval($_REQUEST['type'])) : null;
-
-/** @var Course $course */
-$course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
-$globalTools = $toolsRepo->findBy(['isGlobal' => true]);
-
-if ($baseTool && !$baseTool->isGlobal()) {
-    Display::addFlash(
-        Display::return_message($plugin->get_lang('ToolNotAvailable'), 'warning')
-    );
-
-    header('Location: '.api_get_self().'?'.api_get_cidreq());
-    exit;
-}
-
-$form = new FormValidator('ims_lti_add_tool');
-$form->addHeader($plugin->get_lang('ToolSettings'));
-
-if ($baseTool) {
-    $form->addHtml('<p class="lead">'.Security::remove_XSS($baseTool->getDescription()).'</p>');
-}
-
-$form->addText('name', get_lang('Title'));
-
-if (!$baseTool) {
-    $form->addElement('url', 'url', $plugin->get_lang('LaunchUrl'));
-    $form->addText('consumer_key', $plugin->get_lang('ConsumerKey'), true);
-    $form->addText('shared_secret', $plugin->get_lang('SharedSecret'), true);
-    $form->addRule('url', get_lang('Required'), 'required');
-}
-
-$form->addButtonAdvancedSettings('lti_adv');
-$form->addHtml('<div id="lti_adv_options" style="display:none;">');
-$form->addTextarea('description', get_lang('Description'), ['rows' => 3]);
-
-if (!$baseTool) {
-    $form->addTextarea('custom_params', [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]);
-    $form->addCheckBox('deep_linking', $plugin->get_lang('SupportDeepLinking'), get_lang('Yes'));
-}
-
-if ($baseTool) {
-    $form->addHidden('type', $baseTool->getId());
-}
-
-$form->addHtml('</div>');
-
-$form->addButtonCreate($plugin->get_lang('AddExternalTool'));
-
-if ($form->validate()) {
-    $formValues = $form->getSubmitValues();
-    $tool = null;
-
-    if ($baseTool) {
-        $tool = clone $baseTool;
-    } else {
-        $tool = new ImsLtiTool();
-        $tool
-            ->setLaunchUrl($formValues['url'])
-            ->setConsumerKey($formValues['consumer_key'])
-            ->setSharedSecret($formValues['shared_secret'])
-            ->setCustomParams(
-                empty($formValues['custom_params']) ? null : $formValues['custom_params']
-            );
-    }
-
-    $tool
-        ->setName($formValues['name'])
-        ->setDescription(
-            empty($formValues['description']) ? null : $formValues['description']
-        )
-        ->setIsGlobal(false)
-        ->setCourse($course);
-    $em->persist($tool);
-    $em->flush();
-
-    $plugin->addCourseTool($course, $tool);
-
-    Display::addFlash(
-        Display::return_message($plugin->get_lang('ToolAdded'), 'success')
-    );
-
-    header('Location: '.api_get_course_url());
-    exit;
-}
-
-$template = new Template($plugin->get_lang('AddExternalTool'));
-$template->assign('type', $baseTool ? $baseTool->getId() : null);
-$template->assign('tools', $globalTools);
-$template->assign('form', $form->returnForm());
-
-$content = $template->fetch('ims_lti/view/add.tpl');
-
-$actions = Display::url(
-    Display::return_icon('add.png', $plugin->get_lang('AddExternalTool'), [], ICON_SIZE_MEDIUM),
-    api_get_self().'?'.api_get_cidreq()
-);
-
-$template->assign('actions', Display::toolbarAction('lti_toolbar', [$actions]));
-$template->assign('content', $content);
-$template->display_one_col_template();

+ 210 - 0
plugin/ims_lti/configure.php

@@ -0,0 +1,210 @@
+<?php
+/* For license terms, see /license.txt */
+
+use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
+
+require_once __DIR__.'/../../main/inc/global.inc.php';
+
+api_protect_course_script();
+api_protect_teacher_script();
+
+$plugin = ImsLtiPlugin::create();
+$em = Database::getManager();
+$toolsRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
+
+/** @var ImsLtiTool $baseTool */
+$baseTool = isset($_REQUEST['type']) ? $toolsRepo->find(intval($_REQUEST['type'])) : null;
+$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : 'add';
+
+/** @var Course $course */
+$course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
+$addedTools = $toolsRepo->findBy(['course' => $course]);
+$globalTools = $toolsRepo->findBy(['isGlobal' => true]);
+
+if ($baseTool && !$baseTool->isGlobal()) {
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('ToolNotAvailable'), 'warning')
+    );
+
+    header('Location: '.api_get_self().'?'.api_get_cidreq());
+    exit;
+}
+
+switch ($action) {
+    case 'add':
+        $form = new FormValidator('ims_lti_add_tool');
+        $form->addHeader($plugin->get_lang('ToolSettings'));
+
+        if ($baseTool) {
+            $form->addHtml('<p class="lead">'.Security::remove_XSS($baseTool->getDescription()).'</p>');
+        }
+
+        $form->addText('name', get_lang('Name'));
+
+        if (!$baseTool) {
+            $form->addElement('url', 'url', $plugin->get_lang('LaunchUrl'));
+            $form->addText('consumer_key', $plugin->get_lang('ConsumerKey'), true);
+            $form->addText('shared_secret', $plugin->get_lang('SharedSecret'), true);
+            $form->addRule('url', get_lang('Required'), 'required');
+        }
+
+        $form->addButtonAdvancedSettings('lti_adv');
+        $form->addHtml('<div id="lti_adv_options" style="display:none;">');
+        $form->addTextarea('description', get_lang('Description'), ['rows' => 3]);
+
+        if (!$baseTool) {
+            $form->addTextarea(
+                'custom_params',
+                [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]
+            );
+            $form->addCheckBox('deep_linking', $plugin->get_lang('SupportDeepLinking'), get_lang('Yes'));
+        }
+
+        if ($baseTool) {
+            $form->addHidden('type', $baseTool->getId());
+        }
+
+        $form->addHtml('</div>');
+
+        $form->addButtonCreate($plugin->get_lang('AddExternalTool'));
+
+        if ($form->validate()) {
+            $formValues = $form->getSubmitValues();
+            $tool = null;
+
+            if ($baseTool) {
+                $tool = clone $baseTool;
+            } else {
+                $tool = new ImsLtiTool();
+                $tool
+                    ->setLaunchUrl($formValues['url'])
+                    ->setConsumerKey($formValues['consumer_key'])
+                    ->setSharedSecret($formValues['shared_secret'])
+                    ->setCustomParams(
+                        empty($formValues['custom_params']) ? null : $formValues['custom_params']
+                    );
+            }
+
+            $tool
+                ->setName($formValues['name'])
+                ->setDescription(
+                    empty($formValues['description']) ? null : $formValues['description']
+                )
+                ->setIsGlobal(false)
+                ->setCourse($course);
+
+            $em->persist($tool);
+            $em->flush();
+
+            $plugin->addCourseTool($course, $tool);
+
+            Display::addFlash(
+                Display::return_message($plugin->get_lang('ToolAdded'), 'success')
+            );
+
+            header('Location: '.api_get_self().'?'.api_get_cidreq());
+            exit;
+        }
+        break;
+    case 'edit':
+        $form = new FormValidator('ims_lti_edit_tool');
+        $form->addHeader($plugin->get_lang('ToolSettings'));
+
+        /** @var ImsLtiTool|null $tool */
+        $tool = null;
+
+        if (!empty($_REQUEST['id'])) {
+            $tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', (int) $_REQUEST['id']);
+        }
+
+        if (empty($tool)) {
+            Display::addFlash(
+                Display::return_message($plugin->get_lang('ToolNotAvailable'), 'warning')
+            );
+
+            break;
+        }
+        
+        if (!ImsLtiPlugin::existsToolInCourse($tool->getId(), $course)) {
+            Display::addFlash(
+                Display::return_message($plugin->get_lang('ToolNotAvailable'), 'warning')
+            );
+
+            break;
+        }
+
+        $form->addText('name', get_lang('Title'));
+        $form->addButtonAdvancedSettings('lti_adv');
+        $form->addHtml('<div id="lti_adv_options" style="display:none;">');
+        $form->addTextarea('description', get_lang('Description'), ['rows' => 3]);
+        $form->addTextarea(
+            'custom_params',
+            [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]
+        );
+        $form->addHtml('</div>');
+        $form->addButtonUpdate($plugin->get_lang('EditExternalTool'));
+        $form->addHidden('id', $tool->getId());
+        $form->addHidden('action', 'edit');
+        $form->applyFilter('__ALL__', 'Security::remove_XSS');
+
+        if ($form->validate()) {
+            $formValues = $form->getSubmitValues();
+
+            $tool
+                ->setName($formValues['name'])
+                ->setDescription($formValues['description'])
+                ->setCustomParams(
+                    empty($formValues['custom_params']) ? null : $formValues['custom_params']
+                );
+
+            $em->persist($tool);
+            $em->flush();
+
+            $courseTool = $plugin->findCourseToolByLink($course, $tool);
+
+            if ($courseTool) {
+                $plugin->updateCourseTool($courseTool, $tool);
+            }
+
+            Display::addFlash(
+                Display::return_message($plugin->get_lang('ToolEdited'), 'success')
+            );
+
+            header('Location: '.api_get_self().'?'.api_get_cidreq());
+            exit;
+        }
+
+        $form->setDefaults([
+            'name' => $tool->getName(),
+            'description' => $tool->getDescription(),
+            'custom_params' => $tool->getCustomParams(),
+        ]);
+        break;
+}
+
+$categories = Category::load(null, null, $course->getCode());
+
+$template = new Template($plugin->get_lang('AddExternalTool'));
+$template->assign('type', $baseTool ? $baseTool->getId() : null);
+$template->assign('added_tools', $addedTools);
+$template->assign('global_tools', $globalTools);
+$template->assign('form', $form->returnForm());
+
+$content = $template->fetch('ims_lti/view/add.tpl');
+
+$actions = Display::url(
+    Display::return_icon('add.png', $plugin->get_lang('AddExternalTool'), [], ICON_SIZE_MEDIUM),
+    api_get_self().'?'.api_get_cidreq()
+);
+
+if (!empty($categories)) {
+    $actions .= Display::url(
+        Display::return_icon('gradebook.png', $plugin->get_lang('AddToolToGradebook'), [], ICON_SIZE_MEDIUM),
+        './gradebook/add_eval.php?selectcat='.$categories[0]->get_id().'&'.api_get_cidreq()
+    );
+}
+
+$template->assign('actions', Display::toolbarAction('lti_toolbar', [$actions]));
+$template->assign('content', $content);
+$template->display_one_col_template();

+ 5 - 2
plugin/ims_lti/create.php

@@ -13,6 +13,7 @@ $plugin = ImsLtiPlugin::create();
 $em = Database::getManager();
 
 $form = new FormValidator('ism_lti_create_tool');
+$form->addHeader($plugin->get_lang('ToolSettings'));
 $form->addText('name', get_lang('Name'));
 $form->addText('base_url', $plugin->get_lang('LaunchUrl'));
 $form->addText('consumer_key', $plugin->get_lang('ConsumerKey'));
@@ -52,11 +53,13 @@ if ($form->validate()) {
     exit;
 }
 
-$template = new Template($plugin->get_lang('AddExternalTool'));
+$pageTitle = $plugin->get_lang('AddExternalTool');
+
+$template = new Template($pageTitle);
 $template->assign('form', $form->returnForm());
 
 $content = $template->fetch('ims_lti/view/add.tpl');
 
-$template->assign('header', $plugin->get_title());
+$template->assign('header', $pageTitle);
 $template->assign('content', $content);
 $template->display_one_col_template();

+ 6 - 3
plugin/ims_lti/form.php

@@ -15,7 +15,9 @@ api_block_anonymous_users(false);
 $em = Database::getManager();
 
 /** @var ImsLtiTool $tool */
-$tool = isset($_GET['id']) ? $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', intval($_GET['id'])) : 0;
+$tool = isset($_GET['id'])
+    ? $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', (int) $_GET['id'])
+    : null;
 
 if (!$tool) {
     api_not_allowed(true);
@@ -30,6 +32,7 @@ $course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
 /** @var User $user */
 $user = $em->find('ChamiloUserBundle:User', api_get_user_id());
 
+$pluginPath = api_get_path(WEB_PLUGIN_PATH).'ims_lti/';
 $toolUserId = ImsLtiPlugin::generateToolUserId($user->getId());
 $platformDomain = str_replace(['https://', 'http://'], '', api_get_setting('InstitutionUrl'));
 
@@ -38,7 +41,7 @@ $params['lti_version'] = 'LTI-1p0';
 
 if ($tool->isActiveDeepLinking()) {
     $params['lti_message_type'] = 'ContentItemSelectionRequest';
-    $params['content_item_return_url'] = api_get_path(WEB_PLUGIN_PATH).'ims_lti/item_return.php';
+    $params['content_item_return_url'] = $pluginPath.'item_return.php';
     $params['accept_media_types'] = '*/*';
     $params['accept_presentation_document_targets'] = 'iframe';
     //$params['accept_unsigned'];
@@ -58,7 +61,7 @@ if ($tool->isActiveDeepLinking()) {
 
     if (!empty($toolEval)) {
         $params['lis_result_sourcedid'] = $toolEval->getId().':'.$user->getId();
-        $params['lis_outcome_service_url'] = api_get_path(WEB_PLUGIN_PATH).'ims_lti/outcome_service.php';
+        $params['lis_outcome_service_url'] = api_get_path(WEB_PATH).'ims_lti/outcome_service/'.$tool->getId();
         $params['lis_person_sourcedid'] = "$platformDomain:$toolUserId";
         $params['lis_course_offering_sourcedid'] = "$platformDomain:".$course->getId();
 

+ 13 - 1
plugin/ims_lti/gradebook/add_eval.php

@@ -20,9 +20,21 @@ $select_cat = isset($_GET['selectcat']) ? (int) $_GET['selectcat'] : 0;
 $is_allowedToEdit = $is_courseAdmin;
 
 $em = Database::getManager();
+/** @var \Chamilo\CoreBundle\Entity\Course $course */
 $course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
 $ltiToolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
 
+$categories = Category::load(null, null, $course->getCode());
+
+if (empty($categories)) {
+    $message = Display::return_message(
+        get_plugin_lang('GradebookNotSetWarning', 'ImsLtiPlugin'),
+        'warning'
+    );
+
+    api_not_allowed(true, $message);
+}
+
 $evaladd = new Evaluation();
 $evaladd->set_user_id($_user['user_id']);
 
@@ -48,7 +60,7 @@ $slcLtiTools = $form->createElement('select', 'name', get_lang('Tool'));
 $form->insertElementBefore($slcLtiTools, 'hid_category_id');
 $form->addRule('name', get_lang('ThisFieldIsRequired'), 'required');
 
-$ltiTools = $ltiToolRepo->findByCourse($course);
+$ltiTools = $ltiToolRepo->findBy(['course' => $course, 'gradebookEval' => null]);
 
 /** @var ImsLtiTool $ltiTool */
 foreach ($ltiTools as $ltiTool) {

+ 4 - 0
plugin/ims_lti/lang/english.php

@@ -27,3 +27,7 @@ $strings['ToolUpdated'] = 'Tool updated';
 $strings['PressToContinue'] = 'Press to continue to external tool';
 $strings['ConfigureExternalTool'] = 'Configure external tools';
 $strings['SupportDeepLinking'] = 'Support Deep-Linking';
+$strings['ScoreNotSet'] = 'Score not set';
+$strings['ScoreForXUserIsYScore'] = 'Score for %s is %s';
+$strings['AddedTools'] = 'Added tools';
+$strings['ToolEdited'] = 'Tool edited';

+ 2 - 0
plugin/ims_lti/lang/french.php

@@ -27,3 +27,5 @@ $strings['ToolAdded'] = 'outil mis à jour';
 $strings['PressToContinue'] = 'Appuyez sur pour continuer à l\'outil externe';
 $strings['ConfigureExternalTool'] = 'Configure external tools';
 $strings['SupportDeepLinking'] = 'Support Deep-Linking';
+$strings['ScoreNotSet'] = 'Score non défini';
+$strings['ScoreForXUserIsYScore'] = 'Score pour %s est %s';

+ 2 - 0
plugin/ims_lti/lang/spanish.php

@@ -27,3 +27,5 @@ $strings['ToolUpdated'] = 'Herramienta actualizada';
 $strings['PressToContinue'] = 'Presione para continuar con la herramienta externa';
 $strings['ConfigureExternalTool'] = 'Configure external tools';
 $strings['SupportDeepLinking'] = 'Support Deep-Linking';
+$strings['ScoreNotSet'] = 'Puntuación no establecida';
+$strings['ScoreForXUserIsYScore'] = 'Puntuación para %s es %s';

+ 54 - 2
plugin/ims_lti/outcome_service.php

@@ -1,12 +1,64 @@
 <?php
 /* For license terms, see /license.txt */
 
+use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
+
 require_once __DIR__.'/../../main/inc/global.inc.php';
+require_once './OAuthSimple.php';
+
+header('Content-Type: application/xml');
+
+if (empty($_GET['t'])) {
+    exit;
+}
+
+$em = Database::getManager();
+/** @var ImsLtiTool $tool */
+$tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', (int) $_GET['t']);
+
+if (empty($tool)) {
+    exit;
+}
+
+$body = file_get_contents('php://input');
+$bodyHash = OAuthSimple::generateBodyHash($body);
+
+$url = api_get_path(WEB_PATH).'ims_lti/outcome_service/'.$tool->getId();
+$headers = getallheaders();
+
+$params = OAuthSimple::getAuthorizationParams($headers['Authorization']);
+
+if (empty($params)) {
+    exit;
+}
+
+$oauth = new OAuthSimple(
+    $params['oauth_consumer_key'],
+    $tool->getSharedSecret()
+);
+$oauth->setAction('POST');
+$oauth->setSignatureMethod('HMAC-SHA1');
+$result = $oauth->sign(
+    [
+        'path' => $url,
+        'parameters' => [
+            'oauth_body_hash' => $params['oauth_body_hash'],
+            'oauth_nonce' => $params['oauth_nonce'],
+            'oauth_timestamp' => $params['oauth_timestamp'],
+            'oauth_signature_method' => $params['oauth_signature_method'],
+        ],
+    ]
+);
+
+$signatureValid = urldecode($result['signature']) == $params['oauth_signature'];
+$bodyHashValid = $bodyHash === $params['oauth_body_hash'];
+
+if (!$signatureValid || !$bodyHashValid) {
+    exit;
+}
 
 $plugin = ImsLtiPlugin::create();
 
 $process = $plugin->processServiceRequest();
 
-error_log($process);
-
 echo $process;

+ 5 - 5
plugin/ims_lti/src/ImsLtiServiceDeleteRequest.php

@@ -24,7 +24,7 @@ class ImsLtiServiceDeleteRequest extends ImsLtiServiceRequest
 
     protected function processBody()
     {
-        $resultRecord = $this->body->replaceResultRequest->resultRecord;
+        $resultRecord = $this->xmlRequest->resultRecord;
         $sourcedId = (string) $resultRecord->sourcedGUID->sourcedId;
 
         $sourcedParts = explode(':', $sourcedId);
@@ -43,11 +43,9 @@ class ImsLtiServiceDeleteRequest extends ImsLtiServiceRequest
             return;
         }
 
-        $result = new Result();
-        $result->set_evaluation_id($evaluation->getId());
-        $result->set_user_id($user->getId());
+        $results = Result::load(null, $user->getId(), $evaluation->getId());
 
-        if (!$result->exists()) {
+        if (empty($results)) {
             $this->statusInfo
                 ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
                 ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
@@ -55,6 +53,8 @@ class ImsLtiServiceDeleteRequest extends ImsLtiServiceRequest
             return;
         }
 
+        /** @var Result $result */
+        $result = $results[0];
         $result->addResultLog($user->getId(), $evaluation->getId());
         $result->delete();
 

+ 13 - 0
plugin/ims_lti/src/ImsLtiServiceDeleteResponse.php

@@ -6,6 +6,19 @@
  */
 class ImsLtiServiceDeleteResponse extends ImsLtiServiceResponse
 {
+    /**
+     * ImsLtiServiceDeleteResponse constructor.
+     *
+     * @param ImsLtiServiceResponseStatus $statusInfo
+     * @param mixed|null                  $bodyParam
+     */
+    public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
+    {
+        $statusInfo->setOperationRefIdentifier('deleteResult');
+
+        parent::__construct($statusInfo, $bodyParam);
+    }
+
     /**
      * @param SimpleXMLElement $xmlBody
      */

+ 11 - 16
plugin/ims_lti/src/ImsLtiServiceReadRequest.php

@@ -43,35 +43,30 @@ class ImsLtiServiceReadRequest extends ImsLtiServiceRequest
             return;
         }
 
-        $result = new Result();
-        $result->set_evaluation_id($evaluation->getId());
-        $result->set_user_id($user->getId());
-
-        if (!$result->exists()) {
-            $this->statusInfo
-                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
-                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
-
-            return;
-        }
-
         $results = Result::load(null, $user->getId(), $evaluation->getId());
 
         $ltiScore = '';
+        $responseDescription = get_plugin_lang('ScoreNotSet', 'ImsLtiPlugin');
 
         if (!empty($results)) {
             /** @var Result $result */
             $result = $results[0];
 
-            $ltiScore = $evaluation->getMax() / $result->get_score() * 10;
+            if (!empty($result->get_score())) {
+                $ltiScore = $result->get_score() / $evaluation->getMax();
+
+                $responseDescription = sprintf(
+                    get_plugin_lang('ScoreForXUserIsYScore', 'ImsLtiPlugin'),
+                    $user->getId(),
+                    $ltiScore
+                );
+            }
         }
 
         $this->statusInfo
             ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
             ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_SUCCESS)
-            ->setDescription(
-                get_plugin_lang('ResultRead', 'ImsLtiPlugin')
-            );
+            ->setDescription($responseDescription);
 
         $this->responseBodyParam = (string) $ltiScore;
     }

+ 13 - 0
plugin/ims_lti/src/ImsLtiServiceReadResponse.php

@@ -6,6 +6,19 @@
  */
 class ImsLtiServiceReadResponse extends ImsLtiServiceResponse
 {
+    /**
+     * ImsLtiServiceReadResponse constructor.
+     *
+     * @param ImsLtiServiceResponseStatus $statusInfo
+     * @param mixed|null                  $bodyParam
+     */
+    public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
+    {
+        $statusInfo->setOperationRefIdentifier('readResult');
+
+        parent::__construct($statusInfo, $bodyParam);
+    }
+
     /**
      * @param SimpleXMLElement $xmlBody
      */

+ 15 - 12
plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php

@@ -9,11 +9,6 @@ use Chamilo\UserBundle\Entity\User;
  */
 class ImsLtiServiceReplaceRequest extends ImsLtiServiceRequest
 {
-    /**
-     * @var int
-     */
-    private $score;
-
     /**
      * ImsLtiReplaceServiceRequest constructor.
      *
@@ -57,17 +52,21 @@ class ImsLtiServiceReplaceRequest extends ImsLtiServiceRequest
             return;
         }
 
-        $this->score = $evaluation->getMax() * $resultScore;
+        $score = $evaluation->getMax() * $resultScore;
 
-        $result = new Result();
-        $result->set_evaluation_id($evaluation->getId());
-        $result->set_user_id($user->getId());
-        $result->set_score($this->score);
+        $results = Result::load(null, $user->getId(), $evaluation->getId());
 
-        if (!$result->exists()) {
+        if (empty($results)) {
+            $result = new Result();
+            $result->set_evaluation_id($evaluation->getId());
+            $result->set_user_id($user->getId());
+            $result->set_score($score);
             $result->add();
         } else {
+            /** @var Result $result */
+            $result = $results[0];
             $result->addResultLog($user->getId(), $evaluation->getId());
+            $result->set_score($score);
             $result->save();
         }
 
@@ -75,7 +74,11 @@ class ImsLtiServiceReplaceRequest extends ImsLtiServiceRequest
             ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
             ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_SUCCESS)
             ->setDescription(
-                sprintf(get_plugin_lang('', 'ImsLtiPlugin'), $user->getId(), $this->score)
+                sprintf(
+                    get_plugin_lang('ScoreForXUserIsYScore', 'ImsLtiPlugin'),
+                    $user->getId(),
+                    $resultScore
+                )
             );
     }
 }

+ 13 - 0
plugin/ims_lti/src/ImsLtiServiceReplaceResponse.php

@@ -6,6 +6,19 @@
  */
 class ImsLtiServiceReplaceResponse extends ImsLtiServiceResponse
 {
+    /**
+     * ImsLtiServiceReplaceResponse constructor.
+     *
+     * @param ImsLtiServiceResponseStatus $statusInfo
+     * @param mixed|null                  $bodyParam
+     */
+    public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
+    {
+        $statusInfo->setOperationRefIdentifier('replaceResult');
+
+        parent::__construct($statusInfo, $bodyParam);
+    }
+
     /**
      * @param SimpleXMLElement $xmlBody
      */

+ 2 - 0
plugin/ims_lti/src/ImsLtiServiceRequest.php

@@ -48,6 +48,8 @@ abstract class ImsLtiServiceRequest
     {
         $info = $this->xmlHeaderInfo;
 
+        $this->statusInfo->setMessageRefIdentifier($info->imsx_messageIdentifier);
+
         error_log("Service Request: tool version {$info->imsx_version} message ID {$info->imsx_messageIdentifier}");
     }
 

+ 1 - 1
plugin/ims_lti/src/ImsLtiServiceResponse.php

@@ -23,7 +23,7 @@ abstract class ImsLtiServiceResponse
      * ImsLtiServiceResponse constructor.
      *
      * @param ImsLtiServiceResponseStatus $statusInfo
-     * @param mixed                       $bodyParam
+     * @param mixed|null                  $bodyParam
      */
     public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
     {

+ 4 - 4
plugin/ims_lti/src/ImsLtiServiceResponseStatus.php

@@ -26,14 +26,14 @@ class ImsLtiServiceResponseStatus
     private $severity = '';
 
     /**
-     * @var int
+     * @var string
      */
-    private $messageRefIdentifier = 0;
+    private $messageRefIdentifier = '';
 
     /**
-     * @var int
+     * @var string
      */
-    private $operationRefIdentifier = 0;
+    private $operationRefIdentifier = '';
 
     /**
      * @var string

+ 29 - 33
plugin/ims_lti/view/add.tpl

@@ -1,41 +1,37 @@
 <div class="row">
-    {% if tools|length %}
+    {% if global_tools|length or added_tools|length %}
         <div class="col-sm-3">
-            <h2>{{ 'AvailableTools'|get_plugin_lang('ImsLtiPlugin') }}</h2>
-            <ul class="nav nav-pills nav-stacked">
-                {% for tool in tools %}
-                    <li class="{{ type == tool.id ? 'active' : '' }}">
-                        {% if tool.isActiveDeepLinking %}
-                            <a href="{{ _p.web_plugin }}ims_lti/start.php?id={{ tool.id }}&{{ _p.web_cid_query }}">{{ tool.name }}</a>
-                        {% else %}
-                            <a href="{{ _p.web_self }}?type={{ tool.id }}&{{ _p.web_cid_query }}">{{ tool.name }}</a>
-                        {% endif %}
-                    </li>
-                {% endfor %}
-            </ul>
+            {% if added_tools|length %}
+                <h2>{{ 'AddedTools'|get_plugin_lang('ImsLtiPlugin') }}</h2>
+                <ul class="nav nav-pills nav-stacked">
+                    {% for tool in added_tools %}
+                        <li class="{{ type == tool.id ? 'active' : '' }}">
+                            <a href="{{ _p.web_plugin }}ims_lti/configure.php?action=edit&id={{ tool.id }}&{{ _p.web_cid_query }}">
+                                {{ tool.name }}
+                            </a>
+                        </li>
+                    {% endfor %}
+                </ul>
+            {% endif %}
+
+            {% if global_tools|length %}
+                <h2>{{ 'AvailableTools'|get_plugin_lang('ImsLtiPlugin') }}</h2>
+                <ul class="nav nav-pills nav-stacked">
+                    {% for tool in global_tools %}
+                        <li class="{{ type == tool.id ? 'active' : '' }}">
+                            {% if tool.isActiveDeepLinking %}
+                                <a href="{{ _p.web_plugin }}ims_lti/start.php?id={{ tool.id }}&{{ _p.web_cid_query }}">{{ tool.name }}</a>
+                            {% else %}
+                                <a href="{{ _p.web_self }}?type={{ tool.id }}&{{ _p.web_cid_query }}">{{ tool.name }}</a>
+                            {% endif %}
+                        </li>
+                    {% endfor %}
+                </ul>
+            {% endif %}
         </div>
     {% endif %}
 
-    <div class="{{ tools|length ? 'col-sm-9' : 'col-sm-12' }}">
-        {% if tools|length == 0 %}
-            <h2>{{ 'ToolSettings'|get_plugin_lang('ImsLtiPlugin') }}</h2>
-        {% endif %}
-
+    <div class="col-sm-9 {{ not global_tools|length or not added_tools|length ? 'col-md-offset-3' : '' }}">
         {{ form }}
     </div>
 </div>
-
-<script>
-    $(document).on('ready', function () {
-        $('select[name="type"]').on('change', function () {
-            var advancedOptionsEl = $('#show_advanced_options');
-            var type = parseInt($(this).val());
-
-            if (type > 0) {
-                advancedOptionsEl.hide();
-            } else {
-                advancedOptionsEl.show();
-            }
-        });
-    });
-</script>