Pārlūkot izejas kodu

Partial merge with 1.11.x adding new files see BT#15952

Julio Montoya 5 gadi atpakaļ
vecāks
revīzija
a86b233445
56 mainītis faili ar 4869 papildinājumiem un 53 dzēšanām
  1. 70 0
      main/lp/readout_text.php
  2. 28 0
      main/template/default/admin/user_information.tpl
  3. 37 0
      main/template/default/exercise/partials/result_exercise.tpl
  4. 8 0
      main/template/default/mail/custom_calendar_welcome.dist.tpl
  5. 20 0
      main/template/default/my_space/course_summary.tpl
  6. 99 0
      main/template/default/my_space/partials/tracking_course_overview.tpl
  7. 76 0
      main/template/default/my_space/partials/tracking_user_overview.tpl
  8. 112 0
      main/template/default/my_space/pdf_tracking_lp.tpl
  9. 199 0
      main/template/default/my_space/user_details.tpl
  10. 46 0
      main/template/default/my_space/user_summary.tpl
  11. 519 0
      main/webservices/gradebook.php
  12. 148 0
      plugin/advanced_subscription/lang/french.php
  13. 8 0
      plugin/azure_active_directory/install.php
  14. 44 0
      plugin/azure_active_directory/layout/login_form.tpl
  15. 35 0
      plugin/azure_active_directory/login.php
  16. 19 0
      plugin/azure_active_directory/view/login.tpl
  17. 3 0
      plugin/buycourses/admin.php
  18. 14 0
      plugin/buycourses/update.php
  19. 36 0
      plugin/buycourses/view/service_message_transfer.tpl
  20. 228 0
      plugin/coursehomenotify/CourseHomeNotifyPlugin.php
  21. 152 0
      plugin/coursehomenotify/Entity/Notification.php
  22. 101 0
      plugin/coursehomenotify/Entity/NotificationRelUser.php
  23. 13 0
      plugin/coursehomenotify/README.md
  24. 100 0
      plugin/coursehomenotify/configure.php
  25. 52 0
      plugin/coursehomenotify/content.php
  26. 8 0
      plugin/coursehomenotify/install.php
  27. 17 0
      plugin/coursehomenotify/lang/english.php
  28. 17 0
      plugin/coursehomenotify/lang/spanish.php
  29. 4 0
      plugin/coursehomenotify/plugin.php
  30. 8 0
      plugin/coursehomenotify/uninstall.php
  31. 379 53
      plugin/ims_lti/ImsLtiPlugin.php
  32. 214 0
      plugin/ims_lti/configure.php
  33. 193 0
      plugin/ims_lti/gradebook/OutcomeForm.php
  34. 144 0
      plugin/ims_lti/gradebook/add_eval.php
  35. 74 0
      plugin/ims_lti/item_return.php
  36. 69 0
      plugin/ims_lti/outcome_service.php
  37. 93 0
      plugin/ims_lti/src/Form/FrmAdd.php
  38. 114 0
      plugin/ims_lti/src/Form/FrmEdit.php
  39. 132 0
      plugin/ims_lti/src/ImsLti.php
  40. 74 0
      plugin/ims_lti/src/ImsLtiServiceDeleteRequest.php
  41. 29 0
      plugin/ims_lti/src/ImsLtiServiceDeleteResponse.php
  42. 83 0
      plugin/ims_lti/src/ImsLtiServiceReadRequest.php
  43. 35 0
      plugin/ims_lti/src/ImsLtiServiceReadResponse.php
  44. 103 0
      plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php
  45. 29 0
      plugin/ims_lti/src/ImsLtiServiceReplaceResponse.php
  46. 82 0
      plugin/ims_lti/src/ImsLtiServiceRequest.php
  47. 37 0
      plugin/ims_lti/src/ImsLtiServiceRequestFactory.php
  48. 64 0
      plugin/ims_lti/src/ImsLtiServiceResponse.php
  49. 31 0
      plugin/ims_lti/src/ImsLtiServiceResponseFactory.php
  50. 162 0
      plugin/ims_lti/src/ImsLtiServiceResponseStatus.php
  51. 31 0
      plugin/ims_lti/src/ImsLtiServiceUnsupportedRequest.php
  52. 28 0
      plugin/ims_lti/src/ImsLtiServiceUnsupportedResponse.php
  53. BIN
      plugin/notebookteacher/resources/img/32/notebookteacher_na.png
  54. 28 0
      plugin/studentfollowup/demo_content.php
  55. 219 0
      src/Chamilo/CourseBundle/Entity/CExerciseCategory.php
  56. 201 0
      tests/datafiller/users_import_big_example.csv

+ 70 - 0
main/lp/readout_text.php

@@ -0,0 +1,70 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\CourseBundle\Entity\CDocument;
+
+/**
+ * Print a read-out text inside a session.
+ *
+ * @package chamilo.learnpath
+ */
+$_in_course = true;
+
+require_once __DIR__.'/../inc/global.inc.php';
+
+$current_course_tool = TOOL_LEARNPATH;
+
+api_protect_course_script(true);
+
+$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
+$lpId = isset($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0;
+$courseInfo = api_get_course_info();
+$courseCode = $courseInfo['code'];
+$courseId = $courseInfo['real_id'];
+$userId = api_get_user_id();
+$sessionId = api_get_session_id();
+
+$em = Database::getManager();
+$documentRepo = $em->getRepository('ChamiloCourseBundle:CDocument');
+
+// This page can only be shown from inside a learning path
+if (!$id && !$lpId) {
+    api_not_allowed(true);
+}
+
+/** @var CDocument $document */
+$document = $documentRepo->findOneBy(['cId' => $courseId, 'iid' => $id]);
+
+if (empty($document)) {
+    // Try with normal id
+    /** @var CDocument $document */
+    $document = $documentRepo->findOneBy(['cId' => $courseId, 'id' => $id]);
+
+    if (empty($document)) {
+        Display::return_message(get_lang('FileNotFound'), 'error');
+        exit;
+    }
+}
+
+$documentPathInfo = pathinfo($document->getPath());
+$coursePath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'];
+$documentPath = '/document'.$document->getPath();
+$documentText = file_get_contents($coursePath.$documentPath);
+$documentText = api_remove_tags_with_space($documentText);
+
+$wordsInfo = preg_split('/ |\n/', $documentText, -1, PREG_SPLIT_OFFSET_CAPTURE);
+$words = [];
+
+foreach ($wordsInfo as $wordInfo) {
+    $words[$wordInfo[1]] = nl2br($wordInfo[0]);
+}
+
+$htmlHeadXtra[] = '<script>
+    var words = '.json_encode($words, JSON_OBJECT_AS_ARRAY).',
+        wordsCount = '.count($words).'
+</script>';
+$htmlHeadXtra[] = api_get_js('readout_text/js/start.js');
+$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_JS_PATH).'readout_text/css/start.css');
+
+$template = new Template(strip_tags($document->getTitle()));
+$template->display_blank_template();

+ 28 - 0
main/template/default/admin/user_information.tpl

@@ -0,0 +1,28 @@
+{% import 'default/macro/macro.tpl' as display %}
+
+<div class="details">
+    <div class="row">
+        <div class="col-md-4">
+            {{ display.panel('', display.reporting_user_box(user), '') }}
+        </div>
+        <div class="col-md-8">
+            <div class="list-card">
+                {{ display.card_widget('FirstLoginInPlatform'|get_lang, user.first_connection, 'calendar') }}
+                {{ display.card_widget('LatestLoginInPlatform'|get_lang, user.last_connection, 'calendar') }}
+                {% if user.legal %}
+                    {{ display.card_widget('LegalAccepted'|get_lang, user.legal.datetime, 'gavel', user.legal.icon) }}
+                {% endif %}
+            </div>
+            {% if social_tool %}
+                <div class="list-box-widget">
+                    {{ display.box_widget('Friends'|get_lang, user.social.friends, 'users') }}
+                    {{ display.box_widget('InvitationSent'|get_lang, user.social.invitation_sent, 'paper-plane') }}
+                    {{ display.box_widget('InvitationReceived'|get_lang, user.social.invitation_received, 'smile-o') }}
+                    {{ display.box_widget('WallMessagesPosted'|get_lang, user.social.messages_posted, 'comments') }}
+                    {{ display.box_widget('MessagesSent'|get_lang, user.social.messages_sent, 'envelope') }}
+                    {{ display.box_widget('MessagesReceived'|get_lang, user.social.message_received, 'envelope-open-o') }}
+                </div>
+            {% endif %}
+        </div>
+    </div>
+</div>

+ 37 - 0
main/template/default/exercise/partials/result_exercise.tpl

@@ -0,0 +1,37 @@
+<div class="question-result">
+    <div class="panel panel-default">
+        <div class="panel-body">
+            <h3>{{ data.title }}</h3>
+            <div class="row">
+                <div class="col-md-3">
+                    <div class="user-avatar">
+                        <img src="{{ data.avatar }}">
+                    </div>
+                    <div class="user-info">
+                        <strong>{{ data.name_url }}</strong><br>
+                    </div>
+                </div>
+                <div class="col-md-9">
+                    <div class="group-data">
+                        <div class="list-data username">
+                            <span class="item">{{ 'Username'|get_lang }}</span>
+                            <i class="fa fa-user" aria-hidden="true"></i> {{ data.username }}
+                        </div>
+                        <div class="list-data start-date">
+                            <span class="item">{{ 'StartDate'|get_lang }}</span>
+                            <i class="fa fa-calendar" aria-hidden="true"></i> {{ data.start_date }}
+                        </div>
+                        <div class="list-data duration">
+                            <span class="item">{{ 'Duration'|get_lang }}</span>
+                            <i class="fa fa-clock-o" aria-hidden="true"></i> {{ data.duration }}
+                        </div>
+                        <div class="list-data ip">
+                            <span class="item">{{ 'IP'|get_lang }}</span>
+                            <i class="fa fa-laptop" aria-hidden="true"></i> {{ data.ip }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 8 - 0
main/template/default/mail/custom_calendar_welcome.dist.tpl

@@ -0,0 +1,8 @@
+{{ 'Dear'|get_lang }} ((user_firstname)) <br />
+
+<p>{{ 'WelcomeToPortalXInCourseSessionXCoursePartOfCareerX'|get_lang|format(site_name, course_title, career_name) }}</p>
+<p>{{ 'YourNextModule'|get_lang }}</p>
+<strong>{{ 'Module' | get_lang }}: {{ course_title }}</strong><br />
+{{ 'FirstLesson' | get_lang }}: {{ first_lesson }} <br />
+{{ 'Location' | get_lang }}: {{ location }}<br />
+{{ 'Group' | get_lang }}: {{ course_title }}<br />

+ 20 - 0
main/template/default/my_space/course_summary.tpl

@@ -0,0 +1,20 @@
+<div class="summary-legend">
+    {{ table }}
+</div>
+<script>
+    $(function() {
+        $('.easypiechart-blue').easyPieChart({
+            scaleColor: false,
+            barColor: '#30a5ff',
+            lineWidth:8,
+            trackColor: '#f2f2f2'
+        });
+
+        $('.easypiechart-red').easyPieChart({
+            scaleColor: false,
+            barColor: '#f9243f',
+            lineWidth:8,
+            trackColor: '#f2f2f2'
+        });
+    });
+</script>

+ 99 - 0
main/template/default/my_space/partials/tracking_course_overview.tpl

@@ -0,0 +1,99 @@
+{% import 'default/macro/macro.tpl' as display %}
+
+{% set content %}
+    <div class="summary-course" id="summary-{{ data.id }}">
+        <div class="row">
+            <div class="col-md-2">
+                <div class="course">
+                    <h4 class="title">{{ data.title }}</h4>
+                    <div class="image">
+                        <img src="{{ data.image_small }}" class="img-responsive"/>
+                    </div>
+                    <div class="info">
+                        {% if data.course_code %}
+                            <p><strong>{{ 'WantedCourseCode'|get_lang }}</strong><br>{{ data.course_code }}</p>
+                        {% endif %}
+                        {% if data.category %}
+                            <p><strong>{{ 'CourseFaculty'|get_lang }}</strong><br>{{ data.category }}</p>
+                        {% endif %}
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-2">
+                <div class="state">
+                    <div class="stat-text">
+                        <i class="fa fa-clock-o" aria-hidden="true"></i> {{ data.time_spent }}
+                    </div>
+                    <div class="stat-heading">
+                        {{ 'TimeSpentInTheCourse'|get_lang }}
+                    </div>
+                </div>
+                <div class="state">
+                    <div class="stat-text">
+                        {{ data.total_score }}
+                    </div>
+                    <div class="stat-heading">
+                        {{ 'TotalExercisesScoreObtained'|get_lang }}
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-4">
+                <div class="list-donut">
+                    <div class="easy-donut">
+                        <div class="easypiechart-blue easypiechart" title="{{ 'Progress'|get_lang }}"  data-percent="{{ data.avg_progress }}">
+                            <span class="percent">{{ data.avg_progress }}%</span>
+                        </div>
+                        <div class="easypiechart-legend">
+                            {{ 'AvgStudentsProgress'|get_lang }}
+                        </div>
+                    </div>
+                    <div class="easy-donut">
+                        <div class="easypiechart-red easypiechart" title="{{ 'Progress'|get_lang }}"  data-percent="{{ data.avg_score }}">
+                            <span class="percent">{{ data.avg_score }}%</span>
+                        </div>
+                        <div class="easypiechart-legend">
+                            {{ 'AvgCourseScore'|get_lang }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-4">
+
+                <dl class="dl-horizontal list-info">
+                    <dt>
+                        <span title="{{ 'TotalNumberOfMessages'|get_lang }}">{{ 'TotalNumberOfMessages'|get_lang }}</span>
+                    </dt>
+                    <dd>
+                        <span class="text-color"><i class="fa fa-comments" aria-hidden="true"></i></span>
+                        <span class="text-color">{{ data.number_message }}</span>
+                    </dd>
+                    <dt>
+                        <span title="{{ 'TotalNumberOfAssignments'|get_lang }}">{{ 'TotalNumberOfAssignments'|get_lang }}</span>
+                    </dt>
+                    <dd>
+                        <span class="text-color"><i class="fa fa-pencil" aria-hidden="true"></i></span>
+                        <span class="text-color">{{ data.number_assignments }}</span>
+                    </dd>
+                    <dt>
+                        <span title="{{ 'TotalExercisesAnswered'|get_lang }}">{{ 'TotalExercisesAnswered'|get_lang }}</span>
+                    </dt>
+                    <dd>
+                        <span class="text-color"><i class="fa fa-file-text" aria-hidden="true"></i></span>
+                        <span class="text-color">{{ data.questions_answered }}</span>
+                    </dd>
+                    <dt>
+                        <span title="{{ 'LatestLogin'|get_lang }}">{{ 'LatestLogin'|get_lang }}</span>
+                    </dt>
+                    <dd>
+                        <span class="text-color"><i class="fa fa-clock-o" aria-hidden="true"></i></span>
+                        <span class="text-color">{{ data.last_login }}</span>
+                    </dd>
+
+                </dl>
+
+            </div>
+        </div>
+    </div>
+{% endset %}
+
+{{ display.panel('',content ,'') }}

+ 76 - 0
main/template/default/my_space/partials/tracking_user_overview.tpl

@@ -0,0 +1,76 @@
+<div class="summary-height">
+    <div class="summary">
+        <div class="summary-body">
+            <div id="summary-user-{{ item.id }}" class="summary-item">
+                <div class="icon">
+                    <img src="{{ item.avatar }}" class="img-circle">
+                </div>
+                <div class="user">
+                    <a title="{{ item.complete_name }}" href="{{ _p.web }}main/social/profile.php?u={{ item.id }}" class="name">
+                        {{ item.complete_name }}
+                    </a>
+                    <div class="username">{{ item.username }}</div>
+                </div>
+                <div class="summary-course">
+                    {% if item.course %}
+                    {% for course in item.course %}
+                        <div id="course-{{ course.real_id }}" class="course-item">
+                            <div class="course-info">
+                                <h5><a title="{{ 'Course'|get_lang }} - {{ course.title }}" href="{{ _p.web ~ 'main/mySpace/myStudents.php?details=true' ~ _p.web_cid_query ~ '&course=' ~ course.code ~ '&origin=tracking_course&id_session=0&student=' ~ item.id }}" target="_blank">{{ course.title }}</a></h5>
+                                <span class="code">{{ course.code }}</span>
+                            </div>
+                            <div class="box time-spent" data-toggle="tooltip" data-placement="top" title="{{ 'CourseTimeInfo'|get_lang }}">
+                                <i class="fa fa-clock-o" aria-hidden="true"></i>
+                                 {{ course.time_spent }}
+                            </div>
+                            <div class="box" data-toggle="tooltip" data-placement="top" title="{{ 'AvgStudentsProgress'|get_lang }}">
+                                <span class="kt-badge student-progress">
+                                    {{ course.student_progress }} %
+                                </span>
+                            </div>
+                            <div class="box" data-toggle="tooltip" data-placement="top" title="{{ 'AvgCourseScore'|get_lang }}">
+                                <span class="kt-badge student-score">
+                                    {{ course.student_score }}
+                                </span>
+                            </div>
+                            <div class="box" data-toggle="tooltip" data-placement="top" title="{{ 'TotalNumberOfMessages'|get_lang }}">
+                                <span class="kt-badge student-message">
+                                    {{ course.student_message }}
+                                </span>
+                            </div>
+                            <div class="box" data-toggle="tooltip" data-placement="top" title="{{ 'TotalNumberOfAssignments'|get_lang }}">
+                                <span class="kt-badge student-assignments">
+                                    {{ course.student_assignments }}
+                                </span>
+                            </div>
+                            <div class="box">
+                                <span class="kt-badge student-exercises" data-toggle="tooltip" data-placement="top" title="{{ 'TotalExercisesScoreObtained'|get_lang }}">
+                                    {{ course.student_assignments }}
+                                </span>
+                            </div>
+                            <div class="box">
+                                <span class="kt-badge questions-answered" data-toggle="tooltip" data-placement="top" title="{{ 'TotalExercisesAnswered'|get_lang }}">
+                                    {{ course.questions_answered }}
+                                </span>
+                            </div>
+                            <div class="box box-date" data-toggle="tooltip" data-placement="top" title="{{ 'LatestLogin'|get_lang }}">
+                                {% if course.last_connection  %}
+                                <span class="kt-badge last-connection">
+                                     {{ course.last_connection }}
+                                </span>
+                                {% endif %}
+                            </div>
+                        </div>
+                    {% endfor %}
+                    {% else %}
+                        <div class="alert alert-warning" role="alert">
+                            {{ 'HaveNoCourse'|get_lang }}
+                        </div>
+                    {% endif %}
+                </div>
+
+            </div>
+        </div>
+    </div>
+</div>
+

+ 112 - 0
main/template/default/my_space/pdf_tracking_lp.tpl

@@ -0,0 +1,112 @@
+
+<h3 style="text-align: center; text-transform: uppercase; font-size: 20px; font-weight: bold;">{{ data.name }}</h3>
+<br>
+<p>{{ 'Candidate' | get_lang }} : {{ data.candidate }}</p>
+<p>{{ 'ScormStartAttemptDate' | get_lang }} : {{ data.start_date }}</p>
+<br>
+<table style="width: 100%; font-size: 12px; font-weight: normal;">
+    <tr>
+        <th style="text-align: center; height: 25px;  background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'Attempt'|get_lang }}
+        </th>
+        <th style="text-align: center; background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'Score'|get_lang }}
+        </th>
+        <th style="text-align: center; background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'Duration'|get_lang }}
+        </th>
+        <th style="text-align: center; background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'StartTime'|get_lang }}
+        </th>
+        <th style="text-align: center; background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'EndTime'|get_lang }}
+        </th>
+    </tr>
+    <tr>
+        <td style="text-align: center; height: 50px; background: #d9d9d9; padding: 5px; display: block; border: 2px solid #FFFFFF;">
+            {{ data.attempt }}
+        </td>
+        <td style="text-align: center; background: #d9d9d9; padding: 5px; display: block; border: 2px solid #FFFFFF;">
+            {{ data.score }}
+        </td>
+        <td style="text-align: center; background: #d9d9d9; padding: 5px; display: block; border: 2px solid #FFFFFF;">
+            {{ data.duration }}
+        </td>
+        <td style="text-align: center; background: #d9d9d9; padding: 5px; display: block; border: 2px solid #FFFFFF;">
+            {{ data.start_time }}
+        </td>
+        <td style="text-align: center; background: #d9d9d9; padding: 5px; display: block; border: 2px solid #FFFFFF;">
+            {{ data.end_time }}
+        </td>
+    </tr>
+</table>
+<br>
+
+<table style="width: 100%; font-size: 12px;">
+    <tr>
+        <th style="text-align: center; height: 25px;  background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'Domains'|get_lang }}
+        </th>
+        <th style="text-align: center;  background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'Score'|get_lang }}
+        </th>
+        <th style="text-align: center;  background: #222222; color: #FFFFFF; border: 2px solid #FFFFFF;">
+            {{ 'Percentage'|get_lang }}
+        </th>
+    </tr>
+
+    {% for item in categories %}
+    <tr>
+        <td style="height:40px; width: 40%; text-align: left; padding: 5px; display: block; border-bottom: 1px solid #cdcdcd;">
+            {{ item.name }}
+        </td>
+        <td style="text-align: center; padding: 5px; display: block; border-bottom: 1px solid #cdcdcd;">
+            {% if item.score_numeric == 0 %}
+                <span style="color: red;">{{ item.score }}</span>
+            {% else %}
+                <span>{{ item.score }}</span>
+            {% endif %}
+        </td>
+        <td style="text-align: left; padding: 5px; display: block; border-bottom: 1px solid #cdcdcd;">
+            <img src="{{ "bar_progress.png"|icon(22) }}" width="{{ item.score_numeric }}px" height="16px" alt="{{ "Percentage"|get_lang }}"/>
+            {% if item.score_numeric == 0 %}
+                <span style="color: red;">{{ item.score_percentage }}</span>
+            {% else %}
+                <span>{{ item.score_percentage }}</span>
+            {% endif %}
+        </td>
+    </tr>
+    {% endfor %}
+
+    {% for item in general_score %}
+        <tr>
+            <td style="text-align: left; padding: 5px; height:30px; background: #222222; color: #FFFFFF; display: block;">
+                <b>{{ 'GeneralTotal' | get_lang }}</b>
+            </td>
+            <td style="text-align: center; padding: 5px; display: block;background-color: #f6ffe2;">
+                {{ item.score }}
+            </td>
+            <td style="text-align: left; padding: 5px; display: block; background-color: #f6ffe2;">
+                <img src="{{ "bar_progress.png"|icon(22) }}" width="{{ item.score_numeric }}px" height="16px" alt="{{ "Percentage"|get_lang }}"/>
+                {% if item.score_numeric == 0 %}
+                    <span style="color: red;">{{ item.score_percentage }}</span>
+                {% else %}
+                    <span>{{ item.score_percentage }}</span>
+                {% endif %}
+            </td>
+        </tr>
+{#        {% if global_total %}#}
+{#            <tr>#}
+{#                <td>#}
+
+{#                </td>#}
+{#                <td>#}
+{#                    {{ 'GlobalTotal'|get_lang }}#}
+{#                </td>#}
+{#                <td>#}
+{#                    {{ global_total }}#}
+{#                </td>#}
+{#            </tr>#}
+{#        {% endif %}#}
+    {% endfor %}
+</table>

+ 199 - 0
main/template/default/my_space/user_details.tpl

@@ -0,0 +1,199 @@
+{% import 'default/macro/macro.tpl' as display %}
+
+{% if title %}
+    <h2 class="details-title"><img src="{{ 'course.png'|icon(32) }}"> {{ title }}</h2>
+{% endif %}
+
+<div class="page-header">
+    <h3>{{ user.complete_name }}</h3>
+</div>
+<!-- NO DETAILS -->
+{% if details != true %}
+    <div class="no-details">
+        <div class="panel panel-default">
+            <div class="panel-body">
+                <div class="row">
+                    <div class="col-md-4">
+                        <div class="user text-center">
+                            <div class="avatar">
+                                <img width="128px" src="{{ user.avatar }}" class="img-responsive">
+                            </div>
+                            <div class="name">
+                                <h3>{{ user.complete_name_link }}</h3>
+                                <p class="email">{{ user.email }}</p>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-md-4">
+                        {{ display.reporting_user_details(user) }}
+                    </div>
+                    <div class="col-md-4">
+                        {{ display.card_widget('FirstLoginInPlatform'|get_lang, user.first_connection, 'calendar') }}
+                        {{ display.card_widget('LatestLoginInPlatform'|get_lang, user.last_connection, 'calendar') }}
+
+                        {% if user.legal %}
+                            {{ display.card_widget('LegalAccepted'|get_lang, user.legal.datetime, 'gavel', user.legal.icon) }}
+                        {% endif %}
+                    </div>
+                </div>
+            </div>
+        </div>
+
+    </div>
+    <!-- DETAILS -->
+{% else %}
+    <div class="details">
+        <div class="row">
+            <div class="col-md-4">
+                {{ display.panel('', display.reporting_user_box(user), '') }}
+            </div>
+
+            <div class="col-md-8">
+                <div class="row">
+                    <div class="col-md-8">
+
+                        <div class="row">
+                            <div class="col-md-6">
+                                <div class="easy-donut">
+                                    <div id="easypiechart-blue" title="{{ 'Progress'|get_lang }}" class="easypiechart"
+                                         data-percent="{{ user.student_progress }}">
+                                        <span class="percent">{{ user.student_progress }}%</span>
+                                    </div>
+                                    <div class="easypiechart-legend">
+                                        {{ 'ScormAndLPProgressTotalAverage'|get_lang }}
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="col-md-6">
+                                <div class="easy-donut">
+                                    <div id="easypiechart-red" title="{{ 'Score'|get_lang }}" class="easypiechart"
+                                         data-percent="{{ user.student_score }}">
+                                        <span class="percent">{{ user.student_score }} </span>
+                                    </div>
+                                    <div class="easypiechart-legend">
+                                        {{ 'ScormAndLPTestTotalAverage'|get_lang }}
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="row">
+                            <div class="col-md-6">
+
+                                <div class="card box-widget">
+                                    <div class="card-body">
+                                        <div class="stat-widget-five">
+                                            <i class="fa fa-globe" aria-hidden="true"></i>
+                                            {{ user.tools.links }}
+                                            <div class="box-name">
+                                                {{ 'LinksDetails'|get_lang }}
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="card box-widget">
+                                    <div class="card-body">
+                                        <div class="stat-widget-five">
+                                            <i class="fa fa-download" aria-hidden="true"></i>
+                                            {{ user.tools.documents }}
+                                            <div class="box-name">
+                                                {{ 'DocumentsDetails'|get_lang }}
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="card box-widget">
+                                    <div class="card-body">
+                                        <div class="stat-widget-five">
+                                            <i class="fa fa-pencil" aria-hidden="true"></i>
+                                            {{ user.tools.tasks }}
+                                            <div class="box-name">
+                                                {{ 'Student_publication'|get_lang }}
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                            </div>
+                            <div class="col-md-6">
+
+                                <div class="card box-widget">
+                                    <div class="card-body">
+                                        <div class="stat-widget-five">
+                                            <i class="fa fa-comments-o" aria-hidden="true"></i>
+                                            {{ user.tools.messages }}
+                                            <div class="box-name">
+                                                {{ 'NumberOfPostsForThisUser'|get_lang }}
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="card box-widget">
+                                    <div class="card-body">
+                                        <div class="stat-widget-five">
+                                            <i class="fa fa-paper-plane" aria-hidden="true"></i>
+                                            {{ user.tools.upload_documents }}
+                                            <div class="box-name">
+                                                {{ 'UploadedDocuments'|get_lang }}
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="card box-widget">
+                                    <div class="card-body">
+                                        <div class="stat-widget-five">
+                                            <i class="fa fa-plug" aria-hidden="true"></i>
+                                            <span class="date" title="{{ user.tools.chat_connection }}">
+                                        {% if user.tools.chat_connection != '' %}
+                                            {{ user.tools.chat_connection }}
+                                        {% else %}
+                                            {{ 'NotRegistered'|get_lang }}
+                                        {% endif %}
+                                        </span>
+                                            <div class="box-name">
+                                                {{ 'ChatLastConnection'|get_lang }}
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                            </div>
+                        </div>
+
+                    </div>
+                    <div class="col-md-4">
+                        {{ display.card_widget('FirstLoginInPlatform'|get_lang, user.first_connection, 'calendar') }}
+                        {{ display.card_widget('LatestLoginInPlatform'|get_lang, user.last_connection, 'calendar') }}
+                        {% if(user.time_spent_course) %}
+                            {{ display.card_widget('TimeSpentInTheCourse'|get_lang, user.time_spent_course, 'clock-o') }}
+                        {% endif %}
+                        {% if user.legal %}
+                            {{ display.card_widget('LegalAccepted'|get_lang, user.legal.datetime, 'gavel', user.legal.icon) }}
+                        {% endif %}
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endif %}
+
+<script>
+    $(function () {
+        $('#easypiechart-blue').easyPieChart({
+            scaleColor: false,
+            barColor: '#30a5ff',
+            lineWidth: 8,
+            trackColor: '#f2f2f2'
+        });
+
+        $('#easypiechart-red').easyPieChart({
+            scaleColor: false,
+            barColor: '#f9243f',
+            lineWidth: 8,
+            trackColor: '#f2f2f2'
+        });
+    });
+</script>

+ 46 - 0
main/template/default/my_space/user_summary.tpl

@@ -0,0 +1,46 @@
+<script>
+    $(function(){
+        $('[data-toggle="tooltip"]').tooltip();
+    });
+</script>
+<div class="summary-legend">
+    <ul class="list-legend">
+        <li>
+            <span class="cube student-progress">
+            </span>
+            {{ 'AvgStudentsProgress'|get_lang }}
+        </li>
+        <li>
+            <span class="cube student-score">
+            </span>
+            {{ 'AvgCourseScore'|get_lang }}
+        </li>
+        <li>
+            <span class="cube student-message">
+            </span>
+            {{ 'TotalNumberOfMessages'|get_lang }}
+        </li>
+        <li>
+            <span class="cube student-assignments">
+            </span>
+            {{ 'TotalNumberOfAssignments'|get_lang }}
+        </li>
+        <li>
+            <span class="cube student-exercises">
+            </span>
+            {{ 'TotalExercisesScoreObtained'|get_lang }}
+        </li>
+        <li>
+            <span class="cube questions-answered">
+            </span>
+            {{ 'TotalExercisesAnswered'|get_lang }}
+        </li>
+        <li>
+            <span class="cube last-connection">
+            </span>
+            {{ 'LatestLogin'|get_lang }}
+        </li>
+    </ul>
+</div>
+
+{{ table }}

+ 519 - 0
main/webservices/gradebook.php

@@ -0,0 +1,519 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Skill as SkillManager;
+
+require_once __DIR__.'/../inc/global.inc.php';
+
+ini_set('memory_limit', -1);
+
+/*
+ini_set('upload_max_filesize', '4000M');
+ini_set('post_max_size', '4000M');
+ini_set('max_execution_time', '80000');
+ini_set('max_input_time', '80000');
+*/
+
+$debug = true;
+
+define('WS_ERROR_SECRET_KEY', 1);
+
+function return_error($code)
+{
+    $fault = null;
+    switch ($code) {
+        case WS_ERROR_SECRET_KEY:
+            $fault = new soap_fault('Server', '', 'Secret key is not correct or params are not correctly set');
+            break;
+    }
+
+    return $fault;
+}
+
+function WSHelperVerifyKey($params)
+{
+    global $_configuration, $debug;
+    if (is_array($params)) {
+        $secret_key = $params['secret_key'];
+    } else {
+        $secret_key = $params;
+    }
+    //error_log(print_r($params,1));
+    $check_ip = false;
+    $ip_matches = false;
+    $ip = trim($_SERVER['REMOTE_ADDR']);
+    // if we are behind a reverse proxy, assume it will send the
+    // HTTP_X_FORWARDED_FOR header and use this IP instead
+    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+        list($ip1) = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+        $ip = trim($ip1);
+    }
+    if ($debug) {
+        error_log("ip: $ip");
+    }
+    // Check if a file that limits access from webservices exists and contains
+    // the restraining check
+    if (is_file('webservice-auth-ip.conf.php')) {
+        include 'webservice-auth-ip.conf.php';
+        if ($debug) {
+            error_log("webservice-auth-ip.conf.php file included");
+        }
+        if (!empty($ws_auth_ip)) {
+            $check_ip = true;
+            $ip_matches = api_check_ip_in_range($ip, $ws_auth_ip);
+            if ($debug) {
+                error_log("ip_matches: $ip_matches");
+            }
+        }
+    }
+
+    if ($debug) {
+        error_log("checkip ".intval($check_ip));
+    }
+
+    if ($check_ip) {
+        $security_key = $_configuration['security_key'];
+    } else {
+        $security_key = $ip.$_configuration['security_key'];
+        //error_log($secret_key.'-'.$security_key);
+    }
+    $result = api_is_valid_secret_key($secret_key, $security_key);
+    //error_log($secret_key.'-'.$security_key);
+    if ($debug) {
+        error_log('WSHelperVerifyKey result: '.intval($result));
+    }
+
+    return $result;
+}
+
+// Create the server instance
+$server = new soap_server();
+//$server->soap_defencoding = 'UTF-8';
+
+// Initialize WSDL support
+$server->configureWSDL('WSGradebook', 'urn:WSGradebook');
+
+$server->wsdl->addComplexType(
+    'WSGradebookScoreParams',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'item_id' => [
+            'name' => 'item_id',
+            'type' => 'xsd:string',
+        ],
+        'item_type' => [
+            'name' => 'item_type',
+            'type' => 'xsd:string',
+        ],
+        'email' => [
+            'name' => 'email',
+            'type' => 'xsd:string',
+        ],
+        'secret_key' => ['name' => 'secret_key', 'type' => 'xsd:string'],
+    ]
+);
+
+$server->wsdl->addComplexType(
+    'returnItemScore',
+    'complexType',
+    'struct',
+    'sequence',
+    '',
+    [
+        'score' => ['name' => 'score', 'type' => 'xsd:string'],
+        'date' => ['name' => 'date', 'type' => 'xsd:string'],
+        'counter' => ['name' => 'counter', 'type' => 'xsd:string'],
+    ]
+);
+
+// Register the method to expose
+$server->register(
+    'WSGetGradebookUserItemScore', // method name
+    ['params' => 'tns:WSGradebookScoreParams'], // input parameters
+    ['return' => 'tns:returnItemScore'], // output parameters
+    'urn:WSGradebook', // namespace
+    'urn:WSGradebook#WSGetGradebookUserItemScore', // soapaction
+    'rpc', // style
+    'encoded', // use
+    'get gradebook item user result'
+);
+
+/**
+ * @param array $params
+ *
+ * @return int|string
+ */
+function WSGetGradebookUserItemScore($params)
+{
+    if (!WSHelperVerifyKey($params)) {
+        return return_error(WS_ERROR_SECRET_KEY);
+    }
+
+    $itemId = $params['item_id'];
+    $itemType = $params['item_type'];
+    $email = $params['email'];
+    $userInfo = api_get_user_info_from_email($email);
+
+    if (empty($userInfo)) {
+        return new soap_fault('Server', '', 'User not found');
+    }
+
+    $em = Database::getManager();
+
+    $score = [];
+    switch ($itemType) {
+        case 'link':
+            /** @var \Chamilo\CoreBundle\Entity\GradebookLink $link */
+            $link = $em->getRepository('ChamiloCoreBundle:GradebookLink')->find($itemId);
+            if (empty($link)) {
+                return new soap_fault('Server', '', 'gradebook link not found');
+            }
+
+            $links = AbstractLink::load($link->getId());
+            switch ($link->getType()) {
+                case LINK_EXERCISE:
+                    /** @var ExerciseLink $link */
+                    foreach ($links as $link) {
+                        $link->set_session_id($link->getCategory()->get_session_id());
+                        $score = $link->calc_score($userInfo['user_id']);
+                        break;
+                    }
+                    break;
+                case LINK_STUDENTPUBLICATION:
+                    /** @var StudentPublicationLink $link */
+                    foreach ($links as $link) {
+                        $link->set_session_id($link->getCategory()->get_session_id());
+                        $score = $link->calc_score($userInfo['user_id']);
+                        break;
+                    }
+                    break;
+            }
+            break;
+        case 'evaluation':
+            //$evaluation = $em->getRepository('ChamiloCoreBundle:GradebookEvaluation')->find($itemId);
+            break;
+    }
+
+    if (!empty($score)) {
+        $result = ExerciseLib::show_score($score[0], $score[1], false);
+        $result = strip_tags($result);
+
+        return ['score' => $result, 'date' => $score[2], 'counter' => $score[3]];
+    }
+
+    return new soap_fault('Server', '', 'Score not found');
+}
+
+$server->wsdl->addComplexType(
+    'WSGradebookCategoryScoreParams',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_code' => [
+            'name' => 'course_code',
+            'type' => 'xsd:string',
+        ],
+        'session_id' => [
+            'name' => 'session_id',
+            'type' => 'xsd:string',
+        ],
+        'email' => [
+            'name' => 'email',
+            'type' => 'xsd:string',
+        ],
+        'secret_key' => ['name' => 'secret_key', 'type' => 'xsd:string'],
+    ]
+);
+
+// Register the method to expose
+$server->register(
+    'WSGetGradebookCategoryUserScore', // method name
+    ['params' => 'tns:WSGradebookCategoryScoreParams'], // input parameters
+    ['return' => 'xsd:string'], // output parameters
+    'urn:WSGradebook', // namespace
+    'urn:WSGradebook#WSGetGradebookCategoryUserScore', // soapaction
+    'rpc', // style
+    'encoded'
+);
+
+/**
+ * @param array $params
+ *
+ * @return int|string
+ */
+function WSGetGradebookCategoryUserScore($params)
+{
+    if (!WSHelperVerifyKey($params)) {
+        return return_error(WS_ERROR_SECRET_KEY);
+    }
+    $courseCode = $params['course_code'];
+    $sessionId = (int) $params['session_id'];
+    if (!empty($sessionId)) {
+        $sessionInfo = api_get_session_info($sessionId);
+        if (empty($sessionInfo)) {
+            return new soap_fault('Server', '', 'Session not found');
+        }
+    }
+
+    $email = $params['email'];
+    $userInfo = api_get_user_info_from_email($email);
+
+    if (empty($userInfo)) {
+        return new soap_fault('Server', '', 'User not found');
+    }
+    $userId = $userInfo['user_id'];
+    $courseInfo = api_get_course_info($courseCode);
+    if (empty($courseInfo)) {
+        return new soap_fault('Server', '', 'Course not found');
+    }
+
+    $cats = Category::load(null,
+        null,
+        $courseCode,
+        null,
+        null,
+        $sessionId
+    );
+
+    /** @var Category $category */
+    $category = isset($cats[0]) ? $cats[0] : null;
+    $scorecourse_display = null;
+
+    if (!empty($category)) {
+        $categoryCourse = Category::load($category->get_id());
+        $category = isset($categoryCourse[0]) ? $categoryCourse[0] : null;
+        $allevals = $category->get_evaluations($userId, true);
+        $alllinks = $category->get_links($userId, true);
+
+        $allEvalsLinks = array_merge($allevals, $alllinks);
+        $main_weight = $category->get_weight();
+        $scoredisplay = ScoreDisplay::instance();
+        $item_value_total = 0;
+        /** @var AbstractLink $item */
+        foreach ($allEvalsLinks as $item) {
+            $item->set_session_id($sessionId);
+            $item->set_course_code($courseCode);
+            $score = $item->calc_score($userId);
+            if (!empty($score)) {
+                $divide = $score[1] == 0 ? 1 : $score[1];
+                $item_value = $score[0] / $divide * $item->get_weight();
+                $item_value_total += $item_value;
+            }
+        }
+
+        $item_total = $main_weight;
+        $total_score = [$item_value_total, $item_total];
+        $score = $scoredisplay->display_score($total_score, SCORE_DIV_PERCENT);
+        $score = strip_tags($score);
+
+        return $score;
+    }
+
+    if (empty($category)) {
+        return new soap_fault('Server', '', 'Gradebook category not found');
+    }
+
+    return new soap_fault('Server', '', 'Score not found');
+}
+
+$server->wsdl->addComplexType(
+    'WSLpProgressParams',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'course_code' => [
+            'name' => 'course_code',
+            'type' => 'xsd:string',
+        ],
+        'session_id' => [
+            'name' => 'session_id',
+            'type' => 'xsd:string',
+        ],
+        'lp_id' => [
+            'name' => 'lp_id',
+            'type' => 'xsd:string',
+        ],
+        'email' => [
+            'name' => 'email',
+            'type' => 'xsd:string',
+        ],
+        'secret_key' => ['name' => 'secret_key', 'type' => 'xsd:string'],
+    ]
+);
+
+// Register the method to expose
+$server->register(
+    'WSGetLpProgress', // method name
+    ['params' => 'tns:WSLpProgressParams'], // input parameters
+    ['return' => 'xsd:string'], // output parameters
+    'urn:WSGradebook', // namespace
+    'urn:WSGradebook#WSGetLpProgress', // soapaction
+    'rpc', // style
+    'encoded'
+);
+
+/**
+ * @param array $params
+ *
+ * @return int|string
+ */
+function WSGetLpProgress($params)
+{
+    if (!WSHelperVerifyKey($params)) {
+        return return_error(WS_ERROR_SECRET_KEY);
+    }
+
+    $courseCode = $params['course_code'];
+    $courseInfo = api_get_course_info($courseCode);
+    if (empty($courseInfo)) {
+        return new soap_fault('Server', '', 'Course not found');
+    }
+
+    $sessionId = (int) $params['session_id'];
+    if (!empty($sessionId)) {
+        $sessionInfo = api_get_session_info($sessionId);
+        if (empty($sessionInfo)) {
+            return new soap_fault('Server', '', 'Session not found');
+        }
+    }
+
+    $email = $params['email'];
+    $userInfo = api_get_user_info_from_email($email);
+    $userId = $userInfo['user_id'];
+
+    if (empty($userInfo)) {
+        return new soap_fault('Server', '', 'User not found');
+    }
+
+    $lpId = $params['lp_id'];
+    $lp = new learnpath($courseCode, $lpId, $userId);
+
+    if (empty($lp)) {
+        return new soap_fault('Server', '', 'LP not found');
+    }
+
+    return $lp->progress_db;
+}
+
+$server->wsdl->addComplexType(
+    'WSAssignSkillParams',
+    'complexType',
+    'struct',
+    'all',
+    '',
+    [
+        'skill_id' => [
+            'name' => 'skill_id',
+            'type' => 'xsd:string',
+        ],
+        'level' => [
+            'name' => 'level',
+            'type' => 'xsd:string',
+        ],
+        'justification' => [
+            'name' => 'justification',
+            'type' => 'xsd:string',
+        ],
+        'email' => [
+            'name' => 'email',
+            'type' => 'xsd:string',
+        ],
+        'author_email' => [
+            'name' => 'author_email',
+            'type' => 'xsd:string',
+        ],
+        'secret_key' => ['name' => 'secret_key', 'type' => 'xsd:string'],
+    ]
+);
+
+// Register the method to expose
+$server->register(
+    'WSAssignSkill', // method name
+    ['params' => 'tns:WSAssignSkillParams'], // input parameters
+    ['return' => 'xsd:string'], // output parameters
+    'urn:WSGradebook', // namespace
+    'urn:WSGradebook:WSAssignSkill', // soapaction
+    'rpc', // style
+    'encoded'
+);
+
+/**
+ * @param array $params
+ *
+ * @return int|string
+ */
+function WSAssignSkill($params)
+{
+    if (!WSHelperVerifyKey($params)) {
+        return return_error(WS_ERROR_SECRET_KEY);
+    }
+
+    $em = Database::getManager();
+    $skillManager = new SkillManager();
+
+    $skillId = isset($params['skill_id']) ? $params['skill_id'] : 0;
+    $skillRepo = $em->getRepository('ChamiloCoreBundle:Skill');
+    $skill = $skillRepo->find($skillId);
+
+    if (empty($skill)) {
+        return new soap_fault('Server', '', 'Skill not found');
+    }
+
+    $justification = $params['justification'];
+
+    if (strlen($justification) < 10) {
+        return new soap_fault('Server', '', 'Justification smaller than 10 chars');
+    }
+
+    $level = (int) $params['level'];
+
+    $email = $params['email'];
+    $userInfo = api_get_user_info_from_email($email);
+
+    if (empty($userInfo)) {
+        return new soap_fault('Server', '', 'User not found');
+    }
+
+    $email = $params['author_email'];
+    $authorInfo = api_get_user_info_from_email($email);
+
+    if (empty($authorInfo)) {
+        return new soap_fault('Server', '', 'Author not found');
+    }
+
+    $userId = $userInfo['user_id'];
+    $user = api_get_user_entity($userId);
+    $skillUser = $skillManager->addSkillToUserBadge(
+        $user,
+        $skill,
+        $level,
+        $justification,
+        $authorInfo['id']
+    );
+
+    if (!empty($skillUser)) {
+        return 1;
+    }
+
+    return 0;
+}
+
+// Use the request to (try to) invoke the service
+$GLOBALS['HTTP_RAW_POST_DATA'] = file_get_contents('php://input');
+$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
+
+// If you send your data in utf8 then this value must be false.
+$decodeUTF8 = api_get_setting('registration.soap.php.decode_utf8');
+if ($decodeUTF8 === 'true') {
+    $server->decode_utf8 = true;
+} else {
+    $server->decode_utf8 = false;
+}
+$server->service($HTTP_RAW_POST_DATA);

+ 148 - 0
plugin/advanced_subscription/lang/french.php

@@ -0,0 +1,148 @@
+<?php
+
+/* Strings for settings */
+$strings['plugin_title'] = 'Inscriptions avancées';
+$strings['plugin_comment'] = 'Plugin qui permet de gérer des listes d\'attente pour l\'inscription aux sessions, avec communications avec un portail extérieur';
+$strings['ws_url'] = 'URL du Service Web';
+$strings['ws_url_help'] = 'L\'URL depuis laquelle l\'information est requise pour le processus d\'inscription avancée';
+$strings['check_induction'] = 'Activer le cours d\'induction comme pré-requis';
+$strings['check_induction_help'] = 'Décidez s\'il est nécessaire de compléter les cours d\'induction';
+$strings['yearly_cost_limit'] = 'Límite d\'unités de taxe';
+$strings['yearly_cost_limit_help'] = "La limite d\'unités de taxe à utiliser pour des cours dans l\'année calendrier actuelle.";
+$strings['yearly_hours_limit'] = 'Límite d\'heures académiques';
+$strings['yearly_hours_limit_help'] = "La límite d\'heures académiques de cours qui peuvent être suivies en une année calendrier.";
+$strings['yearly_cost_unit_converter'] = 'Valeur d\'une unité de taxe';
+$strings['yearly_cost_unit_converter_help'] = "La valeur en devise locale d\'une unité de taxe de l\'année actuelle.";
+$strings['courses_count_limit'] = 'Límite de sessions';
+$strings['courses_count_limit_help'] = "La límite de nombre de cours (sessions) qui peuvent être suivis durant une année calendrier et qui <strong>ne sont pas</strong> le cours d'induction";
+$strings['course_session_credit_year_start_date'] = 'Date de début';
+$strings['course_session_credit_year_start_date_help'] = "Date de début de l'année (jour/mois)";
+$strings['min_profile_percentage'] = 'Pourcentage du profil complété mínimum requis';
+$strings['min_profile_percentage_help'] = 'Numéro pourcentage ( > 0.00 et < 100.00)';
+$strings['secret_key'] = 'Clef secrète';
+$strings['terms_and_conditions'] = 'Conditions d\'utilisation';
+
+/* String for error message about requirements */
+$strings['AdvancedSubscriptionNotConnected'] = "Vous n'êtes pas connecté à la plateforme. Merci d'introduire votre nom d'utilisateur / mot de passe afin de vous inscrire";
+$strings['AdvancedSubscriptionProfileIncomplete'] = "Vous devez d'abord compléter votre profil <strong>à %d pourcents</strong> ou plus. Pour l'instant vous n'avez complété que <strong>%d pourcents</strong>";
+$strings['AdvancedSubscriptionIncompleteInduction'] = "Vous n'avez pas encore passé le cours d'induction. Merci de commencer par cette étape.";
+$strings['AdvancedSubscriptionCostXLimitReached'] = "Désolé, vous avez déjà atteint la limite de %s unités de taxe pour les cours que vous avez suivi cette année";
+$strings['AdvancedSubscriptionTimeXLimitReached'] = "Désolé, vous avez déjà atteint la limite annuelle du nombre de %s heures pour les cours que vous avez suivi cette année";
+$strings['AdvancedSubscriptionCourseXLimitReached'] = "Désolé, vous avez déjà atteint la limite annuelle du nombre de cours (%s) à suivre cette année";
+$strings['AdvancedSubscriptionNotMoreAble'] = "Désolé, vous ne répondez plus aux conditions d'utilisation minimum pour l'inscription à un cours";
+$strings['AdvancedSubscriptionIncompleteParams'] = "Les paramètres envoyés ne sont pas complets ou sont incorrects.";
+$strings['AdvancedSubscriptionIsNotEnabled'] = "L'inscription avancée n'est pas activée";
+$strings['AdvancedSubscriptionNoQueue'] = "Vous n'êtes pas inscrit dans ce cours";
+$strings['AdvancedSubscriptionNoQueueIsAble'] = "Vous n'êtes pas inscrit mais vous qualifiez pour ce cours";
+$strings['AdvancedSubscriptionQueueStart'] = "Votre demande d'inscription est en attente de l'approbation de votre supérieur(e). Merci de patienter.";
+$strings['AdvancedSubscriptionQueueBossDisapproved'] = "Désolé, votre inscription a été déclinée par votre supérieur(e).";
+$strings['AdvancedSubscriptionQueueBossApproved'] = "Votre demande d'inscription a été acceptée par votre supérieur(e), mais est en attente de places libres.";
+$strings['AdvancedSubscriptionQueueAdminDisapproved'] = "Désolé, votre inscription a été déclinée par l'administrateur.";
+$strings['AdvancedSubscriptionQueueAdminApproved'] = "Félicitations! Votre inscription a été acceptée par l'administrateur.";
+$strings['AdvancedSubscriptionQueueDefaultX'] = "Une erreur est survenue: l'état de la file d'attente %s n'est pas défini dans le système.";
+
+// Mail translations
+$strings['MailStudentRequest'] = 'Demange d\'inscription d\'un(e) apprenant(e)';
+$strings['MailBossAccept'] = 'Demande d\'inscription acceptée par votre supérieur(e)';
+$strings['MailBossReject'] = 'Demande d\'inscription déclinée par votre supérieur(e)';
+$strings['MailStudentRequestSelect'] = 'Sélection des demandes d\'inscriptions d\'apprenants';
+$strings['MailAdminAccept'] = 'Demande d\'inscription acceptée par l\'administrateur';
+$strings['MailAdminReject'] = 'Demande d\'inscription déclinée par l\'administrateur';
+$strings['MailStudentRequestNoBoss'] = 'Demande d\'inscription d\'apprenant sans supérieur(e)';
+$strings['MailRemindStudent'] = 'Rappel de demande d\'inscription';
+$strings['MailRemindSuperior'] = 'Demandes d\'inscription en attente de votre approbation';
+$strings['MailRemindAdmin'] = 'Inscriptions en attente de votre approbation';
+
+// TPL translations
+$strings['SessionXWithoutVacancies'] = "Le cours \"%s\" ne dispose plus de places libres.";
+$strings['SuccessSubscriptionToSessionX'] = "<h4>Félicitations!</h4> Votre inscription au cours \"%s\" est en ordre.";
+$strings['SubscriptionToOpenSession'] = "Inscription à cours ouvert";
+$strings['GoToSessionX'] = "Aller dans le cours \"%s\"";
+$strings['YouAreAlreadySubscribedToSessionX'] = "Vous êtes déjà inscrit(e) au cours \"%s\".";
+
+// Admin view
+$strings['SelectASession'] = 'Sélectionnez une session de formation';
+$strings['SessionName'] = 'Nom de la session';
+$strings['Target'] = 'Public cible';
+$strings['Vacancies'] = 'Places libres';
+$strings['RecommendedNumberOfParticipants'] = 'Nombre recommandé de participants par département';
+$strings['PublicationEndDate'] = 'Date de fin de publication';
+$strings['Mode'] = 'Modalité';
+$strings['Postulant'] = 'Candidats';
+$strings['Area'] = 'Département';
+$strings['Institution'] = 'Institution';
+$strings['InscriptionDate'] = 'Date d\'inscription';
+$strings['BossValidation'] = 'Validation du supérieur';
+$strings['Decision'] = 'Décision';
+$strings['AdvancedSubscriptionAdminViewTitle'] = 'Résultat de confirmation de demande d\'inscription';
+
+$strings['AcceptInfinitive'] = 'Accepter';
+$strings['RejectInfinitive'] = 'Refuser';
+$strings['AreYouSureYouWantToAcceptSubscriptionOfX'] = 'Êtes-vous certain de vouloir accepter l\'inscription de %s?';
+$strings['AreYouSureYouWantToRejectSubscriptionOfX'] = 'Êtes-vous certain de vouloir refuser l\'inscription de %s?';
+
+$strings['MailTitle'] = 'Demande reçue pour le cours %s';
+$strings['MailDear'] = 'Cher/Chère';
+$strings['MailThankYou'] = 'Merci.';
+$strings['MailThankYouCollaboration'] = 'Merci de votre collaboration.';
+
+// Admin Accept
+$strings['MailTitleAdminAcceptToAdmin'] = 'Information: Validation d\'inscription reçue';
+$strings['MailContentAdminAcceptToAdmin'] = 'Nous avons bien reçu et enregistré votre validation de l\'inscription de <strong>%s</strong> au cours <strong>%s</strong>';
+$strings['MailTitleAdminAcceptToStudent'] = 'Approuvé(e): Votre inscription au cours %s a été confirmée!';
+$strings['MailContentAdminAcceptToStudent'] = 'C\'est avec plaisir que nous vous informons que votre inscription au cours <strong>%s</strong> démarrant le <strong>%s</strong> a été validée par les administrateurs. Nous espérons que votre motivation s\'est maintenue à 100% et que vous participerez à d\'autres cours ou répétiez ce cours à l\'avenir.';
+$strings['MailTitleAdminAcceptToSuperior'] = 'Information: Validation de l\'inscription de %s au cours %s';
+$strings['MailContentAdminAcceptToSuperior'] = 'L\'inscription de <strong>%s</strong> au cours <strong>%s</strong> qui démarre le <strong>%s</strong>, qui était en attente de validation par les organisateurs du cours, vient d\'être validée. Nous espérons que vous nous donnerez un coup de main pour assurer la disponibilité complète de votre collaborateur pour toute la durée du cours';
+
+// Admin Reject
+$strings['MailTitleAdminRejectToAdmin'] = 'Information: refus d\'inscription reçu';
+$strings['MailContentAdminRejectToAdmin'] = 'Nous avons bien reçu et enregistré votre refus pour l\'inscription de <strong>%s</strong> au cours <strong>%s</strong>';
+$strings['MailTitleAdminRejectToStudent'] = 'Votre demande d\'inscription au cours %s a été refusée';
+$strings['MailContentAdminRejectToStudent'] = 'Nous déplorons le besoin de vous informer que vote demande d\'inscription au cours <strong>%s</strong> démarrant le <strong>%s</strong> a été refusée pour manque de place. Nous espérons que vous maintiendrez votre motivation et que vous pourrez participer au même ou à un autre cours lors d\'une prochaine occasion.';
+$strings['MailTitleAdminRejectToSuperior'] = 'Information: Refus d\'inscription de %s au cours %s';
+$strings['MailContentAdminRejectToSuperior'] = 'L\'inscription de <strong>%s</strong> au cours <strong>%s</strong>, qui avait été approuvée antérieurement, a été refusée par manque de place. Nous vous présentons nos excuses sincères.';
+
+// Superior Accept
+$strings['MailTitleSuperiorAcceptToAdmin'] = 'Aprobación de %s al curso %s ';
+$strings['MailContentSuperiorAcceptToAdmin'] = 'La inscripción del alumno <strong>%s</strong> al curso <strong>%s</strong> ha sido aprobada por su superior. Puede gestionar las inscripciones al curso <a href="%s"><strong>aquí</strong></a>';
+$strings['MailTitleSuperiorAcceptToSuperior'] = 'Confirmación: Aprobación recibida para %s';
+$strings['MailContentSuperiorAcceptToSuperior'] = 'Hemos recibido y registrado su decisión de aprobar el curso <strong>%s</strong> para su colaborador <strong>%s</strong>';
+$strings['MailContentSuperiorAcceptToSuperiorSecond'] = 'Ahora la inscripción al curso está pendiente de la disponibilidad de cupos. Le mantendremos informado sobre el resultado de esta etapa';
+$strings['MailTitleSuperiorAcceptToStudent'] = 'Aprobado: Su inscripción al curso %s ha sido aprobada por su superior ';
+$strings['MailContentSuperiorAcceptToStudent'] = 'Nos complace informarle que su inscripción al curso <strong>%s</strong> ha sido aprobada por su superior. Su inscripción ahora solo se encuentra pendiente de disponibilidad de cupos. Le avisaremos tan pronto como se confirme este último paso.';
+
+// Superior Reject
+$strings['MailTitleSuperiorRejectToStudent'] = 'Información: Su inscripción al curso %s ha sido rechazada ';
+$strings['MailContentSuperiorRejectToStudent'] = 'Lamentamos informarle que, en esta oportunidad, su inscripción al curso <strong>%s</strong> NO ha sido aprobada. Esperamos mantenga todo su ánimo y participe en otro curso o, en otra oportunidad, a este mismo curso.';
+$strings['MailTitleSuperiorRejectToSuperior'] = 'Confirmación: Desaprobación recibida para %s';
+$strings['MailContentSuperiorRejectToSuperior'] = 'Hemos recibido y registrado su decisión de desaprobar el curso <strong>%s</strong> para su colaborador <strong>%s</strong>';
+
+// Student Request
+$strings['MailTitleStudentRequestToStudent'] = 'Información: Validación de inscripción recibida';
+$strings['MailContentStudentRequestToStudent'] = 'Hemos recibido y registrado su solicitud de inscripción al curso <strong>%s</strong> para iniciarse el <strong>%s</strong>.';
+$strings['MailContentStudentRequestToStudentSecond'] = 'Su inscripción es pendiente primero de la aprobación de su superior, y luego de la disponibilidad de cupos. Un correo ha sido enviado a su superior para revisión y aprobación de su solicitud.';
+$strings['MailTitleStudentRequestToSuperior'] = 'Solicitud de consideración de curso para un colaborador';
+$strings['MailContentStudentRequestToSuperior'] = 'Hemos recibido una solicitud de inscripción de <strong>%s</strong> al curso <strong>%s</strong>, por iniciarse el <strong>%s</strong>. Detalles del curso: <strong>%s</strong>.';
+$strings['MailContentStudentRequestToSuperiorSecond'] = 'Le invitamos a aprobar o desaprobar esta inscripción, dando clic en el botón correspondiente a continuación.';
+
+// Student Request No Boss
+$strings['MailTitleStudentRequestNoSuperiorToStudent'] = 'Solicitud recibida para el curso %s';
+$strings['MailContentStudentRequestNoSuperiorToStudent'] = 'Hemos recibido y registrado su solicitud de inscripción al curso <strong>%s</strong> para iniciarse el <strong>%s</strong>.';
+$strings['MailContentStudentRequestNoSuperiorToStudentSecond'] = 'Su inscripción es pendiente de la disponibilidad de cupos. Pronto recibirá los resultados de su aprobación de su solicitud.';
+$strings['MailTitleStudentRequestNoSuperiorToAdmin'] = 'Solicitud de inscripción de %s para el curso %s';
+$strings['MailContentStudentRequestNoSuperiorToAdmin'] = 'La inscripción del alumno <strong>%s</strong> al curso <strong>%s</strong> ha sido aprobada por defecto, a falta de superior. Puede gestionar las inscripciones al curso <a href="%s"><strong>aquí</strong></a>';
+
+// Reminders
+$strings['MailTitleReminderAdmin'] = 'Inscripciones a %s pendiente de confirmación';
+$strings['MailContentReminderAdmin'] = 'Las inscripciones siguientes al curso <strong>%s</strong> están pendientes de validación para ser efectivas. Por favor, dirigese a la <a href="%s">página de administración</a> para validarlos.';
+$strings['MailTitleReminderStudent'] = 'Información: Solicitud pendiente de aprobación para el curso %s';
+$strings['MailContentReminderStudent'] = 'Este correo es para confirmar que hemos recibido y registrado su solicitud de inscripción al  curso <strong>%s</strong>, por iniciarse el <strong>%s</strong>.';
+$strings['MailContentReminderStudentSecond'] = 'Su inscripción todavía no ha sido aprobada por su superior, por lo que hemos vuelto a enviarle un correo electrónico de recordatorio.';
+$strings['MailTitleReminderSuperior'] = 'Solicitud de consideración de curso para un colaborador';
+$strings['MailContentReminderSuperior'] = 'Le recordamos que hemos recibido las siguientes solicitudes de suscripción para el curso <strong>%s</strong> de parte de sus colaboradores. El curso se iniciará el <strong>%s</strong>. Detalles del curso: <strong>%s</strong>.';
+$strings['MailContentReminderSuperiorSecond'] = 'Le invitamos a aprobar o desaprobar las suscripciones, dando clic en el botón correspondiente a continuación para cada colaborador.';
+$strings['MailTitleReminderMaxSuperior'] = 'Recordatorio: Solicitud de consideración de curso para colaborador(es)';
+$strings['MailContentReminderMaxSuperior'] = 'Le recordamos que hemos recibido las siguientes solicitudes de suscripción al curso <strong>%s</strong> de parte de sus colaboradores. El curso se iniciará el <strong>%s</strong>. Detalles del curso: <strong>%s</strong>.';
+$strings['MailContentReminderMaxSuperiorSecond'] = 'Este curso tiene una cantidad de cupos limitados y ha recibido una alta tasa de solicitudes de inscripción, por lo que recomendamos que cada área apruebe un máximo de <strong>%s</strong> candidatos. Le invitamos a aprobar o desaprobar las suscripciones, dando clic en el botón correspondiente a continuación para cada colaborador.';
+
+$strings['YouMustAcceptTermsAndConditions'] = 'Para inscribirse al curso <strong>%s</strong>, debe aceptar estos términos y condiciones.';

+ 8 - 0
plugin/azure_active_directory/install.php

@@ -0,0 +1,8 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+if (!api_is_platform_admin()) {
+    die('You must have admin permissions to install plugins');
+}
+
+AzureActiveDirectory::create()->install();

+ 44 - 0
plugin/azure_active_directory/layout/login_form.tpl

@@ -0,0 +1,44 @@
+{% if _u.logged  == 0 %}
+    {% if login_form %}
+        <div id="login-block" class="panel panel-default">
+            <div class="panel-body">
+                {{ login_language_form }}
+                {% if plugin_login_top is not null %}
+                    <div id="plugin_login_top">
+                        {{ plugin_login_top }}
+                    </div>
+                {% endif %}
+
+                {{ login_failed }}
+
+                {% set azure_plugin_enabled = 'azure_active_directory'|api_get_plugin_setting('enable') %}
+                {% set azure_plugin_manage_login = 'azure_active_directory'|api_get_plugin_setting('manage_login_enable') %}
+
+                {% if 'false' == azure_plugin_enabled or 'false' == azure_plugin_manage_login %}
+                    {{ login_form }}
+
+                    {% if "allow_lostpassword" | api_get_setting == 'true' or "allow_registration"|api_get_setting == 'true' %}
+                        <ul class="nav nav-pills nav-stacked">
+                            {% if "allow_registration"|api_get_setting != 'false' %}
+                                <li><a href="{{ _p.web_main }}auth/inscription.php"> {{ 'SignUp'|get_lang }} </a></li>
+                            {% endif %}
+
+                            {% if "allow_lostpassword"|api_get_setting == 'true' %}
+                                <li>
+                                    <a href="{{ _p.web_main }}auth/lostPassword.php">{{ 'LostPassword'|get_lang }}</a>
+                                </li>
+                            {% endif %}
+                        </ul>
+                    {% endif %}
+
+                {% endif %}
+
+                {% if plugin_login_bottom is not null %}
+                    <div id="plugin_login_bottom">
+                        {{ plugin_login_bottom }}
+                    </div>
+                {% endif %}
+            </div>
+        </div>
+    {% endif %}
+{% endif %}

+ 35 - 0
plugin/azure_active_directory/login.php

@@ -0,0 +1,35 @@
+<?php
+/* For license terms, see /license.txt */
+
+require __DIR__.'/../../main/inc/global.inc.php';
+
+$plugin = AzureActiveDirectory::create();
+
+$pluginEnabled = $plugin->get(AzureActiveDirectory::SETTING_ENABLE);
+$managementLoginEnabled = $plugin->get(AzureActiveDirectory::SETTING_MANAGEMENT_LOGIN_ENABLE);
+
+if ('true' !== $pluginEnabled || 'true' !== $managementLoginEnabled) {
+    header('Location: '.api_get_path(WEB_PATH));
+
+    exit;
+}
+
+$userId = api_get_user_id();
+
+if (!($userId) || api_is_anonymous($userId)) {
+    $managementLoginName = $plugin->get(AzureActiveDirectory::SETTING_MANAGEMENT_LOGIN_NAME);
+
+    if (empty($managementLoginName)) {
+        $managementLoginName = $plugin->get_lang('ManagementLogin');
+    }
+
+    $template = new Template($managementLoginName);
+    // Only display if the user isn't logged in.
+    $template->assign('login_language_form', api_display_language_form(true, true));
+    $template->assign('login_form', $template->displayLoginForm());
+
+    $content = $template->fetch('azure_active_directory/view/login.tpl');
+
+    $template->assign('content', $content);
+    $template->display_one_col_template();
+}

+ 19 - 0
plugin/azure_active_directory/view/login.tpl

@@ -0,0 +1,19 @@
+<div class="row">
+    <div class="col-sm-4 col-sm-offset-4">
+        {{ login_language_form }}
+
+        {{ login_form }}
+
+        {% if "allow_lostpassword"|api_get_setting == 'true' or "allow_registration"|api_get_setting == 'true' %}
+            <ul class="nav nav-pills nav-stacked">
+                {% if "allow_registration"|api_get_setting != 'false' %}
+                    <li><a href="{{ _p.web_main }}auth/inscription.php">{{ 'SignUp'|get_lang }}</a></li>
+                {% endif %}
+
+                {% if "allow_lostpassword"|api_get_setting == 'true' %}
+                    <li><a href="{{ _p.web_main }}auth/lostPassword.php">{{ 'LostPassword'|get_lang }}</a></li>
+                {% endif %}
+            </ul>
+        {% endif %}
+    </div>
+</div>

+ 3 - 0
plugin/buycourses/admin.php

@@ -0,0 +1,3 @@
+<?php
+// Redirect to buycourses/index.php
+header('location: index.php');

+ 14 - 0
plugin/buycourses/update.php

@@ -0,0 +1,14 @@
+<?php
+/* For licensing terms, see /license.txt */
+/**
+ * Update the plugin.
+ *
+ * @package chamilo.plugin.buycourses
+ */
+require_once __DIR__.'/config.php';
+
+if (!api_is_platform_admin()) {
+    die('You must have admin permissions to install plugins');
+}
+
+BuyCoursesPlugin::create()->update();

+ 36 - 0
plugin/buycourses/view/service_message_transfer.tpl

@@ -0,0 +1,36 @@
+<div>
+    <p>{{ 'DearUser'|get_lang }}</p>
+    <p>{{ 'PurchaseDetailsIntro'|get_plugin_lang('BuyCoursesPlugin') }}</p>
+    <dl>
+        <dt>{{ 'OrderDate'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.buy_date|api_convert_and_format_date(constant('DATE_TIME_FORMAT_LONG_24H')) }}</dd>
+        <dt>{{ 'OrderReference'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.reference }}</dd>
+        <dt>{{ 'UserName'|get_lang }}</dt>
+        <dd>{{ service_sale.buyer }}</dd>
+        <dt>{{ 'Service'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.name }}</dd>
+        <dt>{{ 'SalePrice'|get_plugin_lang('BuyCoursesPlugin') }}</dt>
+        <dd>{{ service_sale.currency ~ ' ' ~ service_sale.price }}</dd>
+    </dl>
+    <p>{{ 'BankAccountIntro'|get_plugin_lang('BuyCoursesPlugin')|format(service_sale.name) }}</p>
+    <table>
+        <thead>
+        <tr>
+            <th>{{ 'Name'|get_lang }}</th>
+            <th>{{ 'BankAccount'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+            <th>{{ 'SWIFT'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+        </tr>
+        </thead>
+        <tbody>
+        {% for account in transfer_accounts %}
+            <tr>
+                <td>{{ account.name }}</td>
+                <td>{{ account.account }}</td>
+                <td>{{ account.swift }}</td>
+            </tr>
+        {% endfor %}
+        </tbody>
+    </table>
+    <p>{{ 'PurchaseDetailsEnd'|get_plugin_lang('BuyCoursesPlugin') }}</p>
+</div>

+ 228 - 0
plugin/coursehomenotify/CourseHomeNotifyPlugin.php

@@ -0,0 +1,228 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\PluginBundle\Entity\CourseHomeNotify\Notification;
+use Chamilo\PluginBundle\Entity\CourseHomeNotify\NotificationRelUser;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * Class CourseHomeNotifyPlugin.
+ */
+class CourseHomeNotifyPlugin extends Plugin
+{
+    const SETTING_ENABLED = 'enabled';
+
+    /**
+     * CourseHomeNotifyPlugin constructor.
+     */
+    protected function __construct()
+    {
+        $settings = [
+            self::SETTING_ENABLED => 'boolean',
+        ];
+
+        parent::__construct('0.1', 'Angel Fernando Quiroz Campos', $settings);
+
+        $this->isCoursePlugin = true;
+        $this->addCourseTool = false;
+        $this->setCourseSettings();
+    }
+
+    /**
+     * @return CourseHomeNotifyPlugin|null
+     */
+    public static function create()
+    {
+        static $result = null;
+
+        return $result ? $result : $result = new self();
+    }
+
+    /**
+     * Install process.
+     * Create table in database. And setup Doctirne entity.
+     */
+    public function install()
+    {
+        $pluginEntityPath = $this->getEntityPath();
+
+        if (!is_dir($pluginEntityPath)) {
+            if (!is_writable(dirname($pluginEntityPath))) {
+                $message = get_lang('ErrorCreatingDir').': '.$pluginEntityPath;
+                Display::addFlash(Display::return_message($message, 'error'));
+
+                return;
+            }
+
+            mkdir($pluginEntityPath, api_get_permissions_for_new_directories());
+        }
+
+        $fs = new Filesystem();
+        $fs->mirror(__DIR__.'/Entity/', $pluginEntityPath, null, ['override']);
+
+        $schema = Database::getManager()->getConnection()->getSchemaManager();
+
+        if (false === $schema->tablesExist('course_home_notify_notification')) {
+            $sql = "CREATE TABLE course_home_notify_notification_rel_user (id INT AUTO_INCREMENT NOT NULL, notification_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_13E723DDEF1A9D84 (notification_id), INDEX IDX_13E723DDA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB";
+            Database::query($sql);
+
+            $sql = "CREATE TABLE course_home_notify_notification (id INT AUTO_INCREMENT NOT NULL, c_id INT NOT NULL, content LONGTEXT NOT NULL, expiration_link VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, INDEX IDX_7C6C1B0191D79BD3 (c_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB";
+            Database::query($sql);
+
+            $sql = "ALTER TABLE course_home_notify_notification_rel_user ADD CONSTRAINT FK_13E723DDEF1A9D84 FOREIGN KEY (notification_id) REFERENCES course_home_notify_notification (id) ON DELETE CASCADE";
+            Database::query($sql);
+
+            $sql = "ALTER TABLE course_home_notify_notification_rel_user ADD CONSTRAINT FK_13E723DDA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE";
+            Database::query($sql);
+
+            $sql = "ALTER TABLE course_home_notify_notification ADD CONSTRAINT FK_7C6C1B0191D79BD3 FOREIGN KEY (c_id) REFERENCES course (id) ON DELETE CASCADE";
+            Database::query($sql);
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function getEntityPath()
+    {
+        return api_get_path(SYS_PATH).'src/Chamilo/PluginBundle/Entity/'.$this->getCamelCaseName();
+    }
+
+    /**
+     * Uninstall process.
+     * Remove Doctrine entity. And drop table in database.
+     */
+    public function uninstall()
+    {
+        $pluginEntityPath = $this->getEntityPath();
+
+        $fs = new Filesystem();
+
+        if ($fs->exists($pluginEntityPath)) {
+            $fs->remove($pluginEntityPath);
+        }
+
+        $table = Database::get_main_table('course_home_notify_notification_rel_user');
+        Database::query("DROP TABLE IF EXISTS $table");
+        $table = Database::get_main_table('course_home_notify_notification');
+        Database::query("DROP TABLE IF EXISTS $table");
+    }
+
+    /**
+     * @param string $region
+     *
+     * @return string
+     */
+    public function renderRegion($region)
+    {
+        if (
+            'main_bottom' !== $region
+            || strpos($_SERVER['SCRIPT_NAME'], 'course_home/course_home.php') === false
+        ) {
+            return '';
+        }
+
+        $courseId = api_get_course_int_id();
+        $userId = api_get_user_id();
+
+        if (empty($courseId) || empty($userId)) {
+            return '';
+        }
+
+        $course = api_get_course_entity($courseId);
+        $user = api_get_user_entity($userId);
+
+        $em = Database::getManager();
+        /** @var Notification $notification */
+        $notification = $em
+            ->getRepository('ChamiloPluginBundle:CourseHomeNotify\Notification')
+            ->findOneBy(['course' => $course]);
+
+        if (!$notification) {
+            return '';
+        }
+
+        $modalFooter = '';
+        $modalConfig = ['show' => true];
+
+        if ($notification->getExpirationLink()) {
+            /** @var NotificationRelUser $notificationUser */
+            $notificationUser = $em
+                ->getRepository('ChamiloPluginBundle:CourseHomeNotify\NotificationRelUser')
+                ->findOneBy(['notification' => $notification, 'user' => $user]);
+
+            if ($notificationUser) {
+                return '';
+            }
+
+            $contentUrl = api_get_path(WEB_PLUGIN_PATH).$this->get_name().'/content.php?hash='.$notification->getHash();
+            $link = Display::toolbarButton(
+                $this->get_lang('PleaseFollowThisLink'),
+                $contentUrl,
+                'external-link',
+                'link',
+                ['id' => 'course-home-notify-link', 'target' => '_blank']
+            );
+
+            $modalConfig['keyboard'] = false;
+            $modalConfig['backdrop'] = 'static';
+
+            $modalFooter = '<div class="modal-footer">'.$link.'</div>';
+        }
+
+        $modal = '<div id="course-home-notify-modal" class="modal" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal" aria-label="'.get_lang('Close').'">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                        <h4 class="modal-title">'.$this->get_lang('CourseNotice').'</h4>
+                    </div>
+                    <div class="modal-body">
+                        '.$notification->getContent().'
+                    </div>
+                    '.$modalFooter.'
+                </div>
+            </div>
+        </div>';
+
+        $modal .= "<script>
+            $(document).ready(function () {
+                \$('#course-home-notify-modal').modal(".json_encode($modalConfig).");
+                
+                \$('#course-home-notify-link').on('click', function () {
+                    $('#course-home-notify-modal').modal('hide');
+                });
+            });
+        </script>";
+
+        return $modal;
+    }
+
+    /**
+     * Set the course settings.
+     */
+    private function setCourseSettings()
+    {
+        if ('true' !== $this->get(self::SETTING_ENABLED)) {
+            return;
+        }
+
+        $name = $this->get_name();
+
+        $button = Display::toolbarButton(
+            $this->get_lang('SetNotification'),
+            api_get_path(WEB_PLUGIN_PATH).$name.'/configure.php?'.api_get_cidreq(),
+            'cog',
+            'primary'
+        );
+
+        $this->course_settings = [
+            [
+                'name' => '<p>'.$this->get_comment().'</p>'.$button.'<hr>',
+                'type' => 'html',
+            ],
+        ];
+    }
+}

+ 152 - 0
plugin/coursehomenotify/Entity/Notification.php

@@ -0,0 +1,152 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\PluginBundle\Entity\CourseHomeNotify;
+
+use Chamilo\CoreBundle\Entity\Course;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Class Notification.
+ *
+ * @package Chamilo\PluginBundle\Entity\CourseHomeNotify
+ *
+ * @ORM\Table(name="course_home_notify_notification")
+ * @ORM\Entity()
+ */
+class Notification
+{
+    /**
+     * @var int
+     *
+     * @ORM\Column(name="id", type="integer")
+     * @ORM\Id()
+     * @ORM\GeneratedValue()
+     */
+    private $id = 0;
+    /**
+     * @var string
+     *
+     * @ORM\Column(name="content", type="text")
+     */
+    private $content;
+    /**
+     * @var string
+     *
+     * @ORM\Column(name="expiration_link", type="string")
+     */
+    private $expirationLink;
+    /**
+     * @var string
+     *
+     * @ORM\Column(name="hash", type="string")
+     */
+    private $hash;
+    /**
+     * @var Course
+     *
+     * @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\Course")
+     * @ORM\JoinColumn(name="c_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
+     */
+    private $course;
+
+    /**
+     * @return int
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * @param int $id
+     *
+     * @return Notification
+     */
+    public function setId($id)
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getContent()
+    {
+        return $this->content;
+    }
+
+    /**
+     * @param string $content
+     *
+     * @return Notification
+     */
+    public function setContent($content)
+    {
+        $this->content = $content;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getExpirationLink()
+    {
+        return $this->expirationLink;
+    }
+
+    /**
+     * @param string $expirationLink
+     *
+     * @return Notification
+     */
+    public function setExpirationLink($expirationLink)
+    {
+        $this->expirationLink = $expirationLink;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getHash()
+    {
+        return $this->hash;
+    }
+
+    /**
+     * @param string $hash
+     *
+     * @return Notification
+     */
+    public function setHash($hash)
+    {
+        $this->hash = $hash;
+
+        return $this;
+    }
+
+    /**
+     * @return Course
+     */
+    public function getCourse()
+    {
+        return $this->course;
+    }
+
+    /**
+     * @param Course $course
+     *
+     * @return Notification
+     */
+    public function setCourse($course)
+    {
+        $this->course = $course;
+
+        return $this;
+    }
+}

+ 101 - 0
plugin/coursehomenotify/Entity/NotificationRelUser.php

@@ -0,0 +1,101 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\PluginBundle\Entity\CourseHomeNotify;
+
+use Chamilo\UserBundle\Entity\User;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Class NotificationRelUser.
+ *
+ * @package Chamilo\PluginBundle\Entity\CourseHomeNotify
+ *
+ * @ORM\Table(name="course_home_notify_notification_rel_user")
+ * @ORM\Entity()
+ */
+class NotificationRelUser
+{
+    /**
+     * @var int
+     *
+     * @ORM\Column(name="id", type="integer")
+     * @ORM\Id()
+     * @ORM\GeneratedValue()
+     */
+    private $id = 0;
+    /**
+     * @var Notification
+     *
+     * @ORM\ManyToOne(targetEntity="Chamilo\PluginBundle\Entity\CourseHomeNotify\Notification")
+     * @ORM\JoinColumn(name="notification_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
+     */
+    private $notification;
+    /**
+     * @var User
+     *
+     * @ORM\ManyToOne(targetEntity="Chamilo\UserBundle\Entity\User")
+     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
+     */
+    private $user;
+
+    /**
+     * @return int
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * @param int $id
+     *
+     * @return NotificationRelUser
+     */
+    public function setId($id)
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    /**
+     * @return Notification
+     */
+    public function getNotification()
+    {
+        return $this->notification;
+    }
+
+    /**
+     * @param Notification $notification
+     *
+     * @return NotificationRelUser
+     */
+    public function setNotification(Notification $notification)
+    {
+        $this->notification = $notification;
+
+        return $this;
+    }
+
+    /**
+     * @return User
+     */
+    public function getUser()
+    {
+        return $this->user;
+    }
+
+    /**
+     * @param User $user
+     *
+     * @return NotificationRelUser
+     */
+    public function setUser($user)
+    {
+        $this->user = $user;
+
+        return $this;
+    }
+}

+ 13 - 0
plugin/coursehomenotify/README.md

@@ -0,0 +1,13 @@
+# Notify in course home
+
+Show notifications when a user enter in course home page.
+
+## Set up
+* Install the plugin and enable.
+* Go to Settings tool in course base.
+* Display the _Notify in course home_ section. And set the notification.
+
+## Adding a notification
+The notification has a HTML content and an optional _Expiration link_ field.
+If this field is set, then the notification will be displayed until the user visualizes it.
+Otherwise the notification will be displayed every time the user enter to the course home page.

+ 100 - 0
plugin/coursehomenotify/configure.php

@@ -0,0 +1,100 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+require_once __DIR__.'/../../main/inc/global.inc.php';
+
+use Chamilo\PluginBundle\Entity\CourseHomeNotify\Notification;
+
+$plugin = CourseHomeNotifyPlugin::create();
+$courseId = api_get_course_int_id();
+
+if (
+    empty($courseId) ||
+    'true' !== $plugin->get(CourseHomeNotifyPlugin::SETTING_ENABLED)
+) {
+    api_not_allowed(true);
+}
+
+$action = isset($_GET['action']) ? $_GET['action'] : '';
+
+$course = api_get_course_entity($courseId);
+
+$em = Database::getManager();
+/** @var Notification $notification */
+$notification = $em
+    ->getRepository('ChamiloPluginBundle:CourseHomeNotify\Notification')
+    ->findOneBy(['course' => $course]);
+
+$actionLinks = '';
+
+if ($notification) {
+    $actionLinks = Display::url(
+        Display::return_icon('delete.png', $plugin->get_lang('DeleteNotification'), [], ICON_SIZE_MEDIUM),
+        api_get_self().'?'.api_get_cidreq().'&action=delete'
+    );
+
+    if ('delete' === $action) {
+        $em->remove($notification);
+        $em->flush();
+
+        Display::addFlash(
+            Display::return_message($plugin->get_lang('NotificationDeleted'), 'success')
+        );
+
+        header('Location: '.api_get_course_url());
+        exit;
+    }
+} else {
+    $notification = new Notification();
+}
+
+$form = new FormValidator('frm_course_home_notify');
+$form->addHeader($plugin->get_lang('AddNotification'));
+$form->applyFilter('title', 'trim');
+$form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'Minimal']);
+$form->addUrl(
+    'expiration_link',
+    [$plugin->get_lang('ExpirationLink'), $plugin->get_lang('ExpirationLinkHelp')],
+    false,
+    ['placeholder' => 'https://']
+);
+$form->addButtonSave(get_lang('Save'));
+
+if ($form->validate()) {
+    $values = $form->exportValues();
+
+    $notification
+        ->setContent($values['content'])
+        ->setExpirationLink($values['expiration_link'])
+        ->setCourse($course)
+        ->setHash(md5(uniqid()));
+
+    $em->persist($notification);
+    $em->flush();
+
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('NotificationAdded'), 'success')
+    );
+
+    header('Location: '.api_get_course_url());
+    exit;
+}
+
+if ($notification) {
+    $form->setDefaults(
+        [
+            'content' => $notification->getContent(),
+            'expiration_link' => $notification->getExpirationLink(),
+        ]
+    );
+}
+
+$template = new Template($plugin->get_title());
+$template->assign('header', $plugin->get_title());
+
+if ($actionLinks) {
+    $template->assign('actions', Display::toolbarAction('course-home-notify-actions', ['', $actionLinks]));
+}
+
+$template->assign('content', $form->returnForm());
+$template->display_one_col_template();

+ 52 - 0
plugin/coursehomenotify/content.php

@@ -0,0 +1,52 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+require_once __DIR__.'/../../main/inc/global.inc.php';
+
+use Chamilo\PluginBundle\Entity\CourseHomeNotify\Notification;
+use Chamilo\PluginBundle\Entity\CourseHomeNotify\NotificationRelUser;
+
+api_block_anonymous_users(true);
+api_protect_course_script(true);
+
+$plugin = CourseHomeNotifyPlugin::create();
+$userId = api_get_user_id();
+$courseId = api_get_course_int_id();
+
+if (
+    empty($courseId) ||
+    empty($userId) ||
+    'true' !== $plugin->get(CourseHomeNotifyPlugin::SETTING_ENABLED)
+) {
+    api_not_allowed(true);
+}
+
+$user = api_get_user_entity($userId);
+$course = api_get_course_entity($courseId);
+$hash = isset($_GET['hash']) ? Security::remove_XSS($_GET['hash']) : null;
+
+$em = Database::getManager();
+/** @var Notification $notification */
+$notification = $em
+    ->getRepository('ChamiloPluginBundle:CourseHomeNotify\Notification')
+    ->findOneBy(['course' => $course, 'hash' => $hash]);
+
+if (!$notification) {
+    api_not_allowed(true);
+}
+
+$notificationUser = $em
+    ->getRepository('ChamiloPluginBundle:CourseHomeNotify\NotificationRelUser')
+    ->findOneBy(['notification' => $notification, 'user' => $user]);
+
+if (!$notificationUser) {
+    $notificationUser = new NotificationRelUser();
+    $notificationUser
+        ->setUser($user)
+        ->setNotification($notification);
+
+    $em->persist($notificationUser);
+    $em->flush();
+}
+
+header('Location: '.$notification->getExpirationLink());

+ 8 - 0
plugin/coursehomenotify/install.php

@@ -0,0 +1,8 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+if (!api_is_platform_admin()) {
+    api_not_allowed(true);
+}
+
+CourseHomeNotifyPlugin::create()->install();

+ 17 - 0
plugin/coursehomenotify/lang/english.php

@@ -0,0 +1,17 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+$strings['plugin_title'] = "Notify in course home";
+$strings['plugin_comment'] = "Show notifications when a user enter in course home page.";
+
+$strings['enabled'] = 'Enabled';
+
+$strings['SetNotification'] = 'Set one notification on home page';
+$strings['DeleteNotification'] = 'Delete notification on course home page';
+$strings['AddNotification'] = 'Add notification';
+$strings['ExpirationLink'] = 'Expiration link';
+$strings['CourseNotice'] = 'Course notice';
+$strings['PleaseFollowThisLink'] = 'Please, follow this link to continue';
+$strings['ExpirationLinkHelp'] = 'The notification will be displayed until the user visualizes it.';
+$strings['NotificationAdded'] = 'Course notification added: It will be displayed in course home page.';
+$strings['NotificationDeleted'] = 'Course notification deleted';

+ 17 - 0
plugin/coursehomenotify/lang/spanish.php

@@ -0,0 +1,17 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+$strings['plugin_title'] = "Notificar en página de inicio del curso";
+$strings['plugin_comment'] = "Mostrar notificaciones cuando un usuario ingresa en la página principal del curso.";
+
+$strings['enabled'] = 'Habilitado';
+
+$strings['SetNotification'] = 'Establecer una notificación en la página de inicio';
+$strings['DeleteNotification'] = 'Eliminar notificación en la página de inicio del curso';
+$strings['AddNotification'] = 'Añadir notificación';
+$strings['ExpirationLink'] = 'Enlace de caducidad';
+$strings['CourseNotice'] = 'Aviso del curso';
+$strings['PleaseFollowThisLink'] = 'Por favor, siga este enlace para continuar.';
+$strings['ExpirationLinkHelp'] = 'La notificación se mostrará hasta que el usuario la visualice.';
+$strings['NotificationAdded'] = 'Notificación del curso agregada: se mostrará en la página de inicio del curso.';
+$strings['NotificationDeleted'] = 'Notificación del curso borrada';

+ 4 - 0
plugin/coursehomenotify/plugin.php

@@ -0,0 +1,4 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+$plugin_info = CourseHomeNotifyPlugin::create()->get_info();

+ 8 - 0
plugin/coursehomenotify/uninstall.php

@@ -0,0 +1,8 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+if (!api_is_platform_admin()) {
+    api_not_allowed(true);
+}
+
+CourseHomeNotifyPlugin::create()->uninstall();

+ 379 - 53
plugin/ims_lti/ImsLtiPlugin.php

@@ -1,13 +1,17 @@
 <?php
 /* For license terms, see /license.txt */
 
-use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\CoreBundle\Entity\CourseRelUser;
+use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
+use Chamilo\CourseBundle\Entity\CTool;
+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
@@ -25,7 +29,7 @@ class ImsLtiPlugin extends Plugin
      */
     protected function __construct()
     {
-        $version = '1.0 (beta)';
+        $version = '1.5.1 (beta)';
         $author = 'Angel Fernando Quiroz Campos';
 
         parent::__construct($version, $author, ['enabled' => 'boolean']);
@@ -101,36 +105,47 @@ class ImsLtiPlugin extends Plugin
      * Creates the plugin tables on database
      *
      * @return boolean
+     * @throws DBALException
      */
     private function createPluginTables()
     {
         $entityManager = Database::getManager();
         $connection = $entityManager->getConnection();
-        $pluginSchema = new Schema();
-        $platform = $connection->getDatabasePlatform();
-        if (!$connection->getSchemaManager()->tablesExist(self::TABLE_TOOL)) {
-            $toolTable = $pluginSchema->createTable(self::TABLE_TOOL);
-            $toolTable->addColumn(
-                'id',
-                \Doctrine\DBAL\Types\Type::INTEGER,
-                ['autoincrement' => true, 'unsigned' => true]
-            );
-            $toolTable->addColumn('name', Type::STRING);
-            $toolTable->addColumn('description', Type::TEXT)->setNotnull(false);
-            $toolTable->addColumn('launch_url', Type::TEXT);
-            $toolTable->addColumn('consumer_key', Type::STRING);
-            $toolTable->addColumn('shared_secret', Type::STRING);
-            $toolTable->addColumn('custom_params', Type::TEXT)->setNotnull(false);
-            $toolTable->addColumn('is_global', Type::BOOLEAN);
-            $toolTable->setPrimaryKey(['id']);
-
-            $queries = $pluginSchema->toSql($platform);
-
-            foreach ($queries as $query) {
-                Database::query($query);
-            }
+
+        if ($connection->getSchemaManager()->tablesExist(self::TABLE_TOOL)) {
+            return true;
         }
 
+        $queries = [
+            'CREATE TABLE '.self::TABLE_TOOL.' (
+                id INT AUTO_INCREMENT NOT NULL,
+                c_id INT DEFAULT NULL,
+                gradebook_eval_id INT DEFAULT NULL,
+                parent_id INT DEFAULT NULL,
+                name VARCHAR(255) NOT NULL,
+                description LONGTEXT DEFAULT NULL,
+                launch_url VARCHAR(255) NOT NULL,
+                consumer_key VARCHAR(255) DEFAULT NULL,
+                shared_secret VARCHAR(255) DEFAULT NULL,
+                custom_params LONGTEXT DEFAULT NULL,
+                active_deep_linking TINYINT(1) DEFAULT \'0\' NOT NULL,
+                privacy LONGTEXT DEFAULT NULL,
+                INDEX IDX_C5E47F7C91D79BD3 (c_id),
+                INDEX IDX_C5E47F7C82F80D8B (gradebook_eval_id),
+                INDEX IDX_C5E47F7C727ACA70 (parent_id),
+                PRIMARY KEY(id)
+            ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB',
+            'ALTER TABLE '.self::TABLE_TOOL.' ADD CONSTRAINT FK_C5E47F7C91D79BD3
+                FOREIGN KEY (c_id) REFERENCES course (id)',
+            'ALTER TABLE '.self::TABLE_TOOL.' ADD CONSTRAINT FK_C5E47F7C82F80D8B
+                FOREIGN KEY (gradebook_eval_id) REFERENCES gradebook_evaluation (id) ON DELETE SET NULL',
+            'ALTER TABLE '.self::TABLE_TOOL.' ADD CONSTRAINT FK_C5E47F7C727ACA70
+                FOREIGN KEY (parent_id) REFERENCES '.self::TABLE_TOOL.' (id) ON DELETE CASCADE;',
+        ];
+
+        foreach ($queries as $query) {
+            Database::query($query);
+        }
 
         return true;
     }
@@ -171,8 +186,8 @@ class ImsLtiPlugin extends Plugin
     private function setCourseSettings()
     {
         $button = Display::toolbarButton(
-            $this->get_lang('AddExternalTool'),
-            api_get_path(WEB_PLUGIN_PATH).'ims_lti/add.php?'.api_get_cidreq(),
+            $this->get_lang('ConfigureExternalTool'),
+            api_get_path(WEB_PLUGIN_PATH).'ims_lti/configure.php?'.api_get_cidreq(),
             'cog',
             'primary'
         );
@@ -180,43 +195,75 @@ class ImsLtiPlugin extends Plugin
         $this->course_settings = [
             [
                 'name' => $this->get_lang('ImsLtiDescription').$button.'<hr>',
-                'type' => 'html'
-            ]
+                'type' => 'html',
+            ],
         ];
     }
 
     /**
-     * Add the course tool
-     * @param Course $course
-     * @param ImsLtiTool $tool
+     * @param Course     $course
+     * @param ImsLtiTool $ltiTool
+     *
+     * @return CTool
+     */
+    public function findCourseToolByLink(Course $course, ImsLtiTool $ltiTool)
+    {
+        $em = Database::getManager();
+        $toolRepo = $em->getRepository('ChamiloCourseBundle:CTool');
+
+        /** @var CTool $cTool */
+        $cTool = $toolRepo->findOneBy(
+            [
+                'cId' => $course,
+                'link' => self::generateToolLink($ltiTool),
+            ]
+        );
+
+        return $cTool;
+    }
+
+    /**
+     * @param CTool      $courseTool
+     * @param ImsLtiTool $ltiTool
+     *
      * @throws \Doctrine\ORM\OptimisticLockException
      */
-    public function addCourseTool(Course $course, ImsLtiTool $tool)
+    public function updateCourseTool(CTool $courseTool, ImsLtiTool $ltiTool)
     {
         $em = Database::getManager();
-        $cTool = new CTool();
-        $cTool
-            ->setCId($course->getId())
-            ->setName($tool->getName())
-            ->setLink($this->get_name().'/start.php?'.http_build_query(['id' => $tool->getId()]))
-            ->setImage($this->get_name().'.png')
-            ->setVisibility(1)
-            ->setAdmin(0)
-            ->setAddress('squaregray.gif')
-            ->setAddedTool('NO')
-            ->setTarget('_self')
-            ->setCategory('plugin')
-            ->setSessionId(0);
-
-        $em->persist($cTool);
-        $em->flush();
 
-        $cTool->setId($cTool->getIid());
+        $courseTool->setName($ltiTool->getName());
 
-        $em->persist($cTool);
+        $em->persist($courseTool);
         $em->flush();
     }
 
+    /**
+     * @param ImsLtiTool $tool
+     *
+     * @return string
+     */
+    private static function generateToolLink(ImsLtiTool $tool)
+    {
+        return  'ims_lti/start.php?id='.$tool->getId();
+    }
+
+    /**
+     * Add the course tool
+     *
+     * @param Course     $course
+     * @param ImsLtiTool $tool
+     */
+    public function addCourseTool(Course $course, ImsLtiTool $tool)
+    {
+        $this->createLinkToCourseTool(
+            $tool->getName(),
+            $course->getId(),
+            null,
+            self::generateToolLink($tool)
+        );
+    }
+
     /**
      * @return string
      */
@@ -238,4 +285,283 @@ class ImsLtiPlugin extends Plugin
     {
         return api_get_path(SYS_PATH).'src/Chamilo/PluginBundle/Entity/'.$this->getCamelCaseName();
     }
+
+    public static function isInstructor()
+    {
+        api_is_allowed_to_edit(false, true);
+    }
+
+    /**
+     * @param User         $user
+     *
+     * @return string
+     */
+    public static function getUserRoles(User $user)
+    {
+        if (DRH === $user->getStatus()) {
+            return 'urn:lti:role:ims/lis/Mentor';
+        }
+
+        if ($user->getStatus() === INVITEE) {
+            return 'Learner,urn:lti:role:ims/lis/Learner/GuestLearner';
+        }
+
+        if (!api_is_allowed_to_edit(false, true)) {
+            return 'Learner';
+        }
+
+        $roles = ['Instructor'];
+
+        if (api_is_platform_admin_by_id($user->getId())) {
+            $roles[] = 'urn:lti:role:ims/lis/Administrator';
+        }
+
+        return implode(',', $roles);
+    }
+
+    /**
+     * @param int $userId
+     *
+     * @return string
+     */
+    public static function generateToolUserId($userId)
+    {
+        $siteName = api_get_setting('siteName');
+        $institution = api_get_setting('Institution');
+        $toolUserId = "$siteName - $institution - $userId";
+        $toolUserId = api_replace_dangerous_char($toolUserId);
+
+        return $toolUserId;
+    }
+
+    /**
+     * @param User $currentUser
+     *
+     * @return string
+     */
+    public static function getRoleScopeMentor(User $currentUser)
+    {
+        if (DRH !== $currentUser->getStatus()) {
+            return '';
+        }
+
+        $followedUsers = UserManager::get_users_followed_by_drh($currentUser->getId());
+
+        $scope = [];
+
+        foreach ($followedUsers as $userInfo) {
+            $scope[] = self::generateToolUserId($userInfo['user_id']);
+        }
+
+        return implode(',', $scope);
+    }
+
+    /**
+     * @param array      $contentItem
+     * @param ImsLtiTool $baseLtiTool
+     * @param Course     $course
+     *
+     * @throws \Doctrine\ORM\OptimisticLockException
+     */
+    public function saveItemAsLtiLink(array $contentItem, ImsLtiTool $baseLtiTool, Course $course)
+    {
+        $em = Database::getManager();
+        $ltiToolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
+
+        $url = empty($contentItem['url']) ? $baseLtiTool->getLaunchUrl() : $contentItem['url'];
+
+        /** @var ImsLtiTool $newLtiTool */
+        $newLtiTool = $ltiToolRepo->findOneBy(['launchUrl' => $url, 'parent' => $baseLtiTool, 'course' => $course]);
+
+        if (null === $newLtiTool) {
+            $newLtiTool = new ImsLtiTool();
+            $newLtiTool
+                ->setLaunchUrl($url)
+                ->setParent(
+                    $baseLtiTool
+                )
+                ->setPrivacy(
+                    $baseLtiTool->isSharingName(),
+                    $baseLtiTool->isSharingEmail(),
+                    $baseLtiTool->isSharingPicture()
+                )
+                ->setCourse($course);
+        }
+
+        $newLtiTool
+            ->setName(
+                !empty($contentItem['title']) ? $contentItem['title'] : $baseLtiTool->getName()
+            )
+            ->setDescription(
+                !empty($contentItem['text']) ? $contentItem['text'] : null
+            );
+
+        if (!empty($contentItem['custom'])) {
+            $newLtiTool
+                ->setCustomParams(
+                    $newLtiTool->encodeCustomParams($contentItem['custom'])
+                );
+        }
+
+        $em->persist($newLtiTool);
+        $em->flush();
+
+        $courseTool = $this->findCourseToolByLink($course, $newLtiTool);
+
+        if ($courseTool) {
+            $this->updateCourseTool($courseTool, $newLtiTool);
+
+            return;
+        }
+
+        $this->addCourseTool($course, $newLtiTool);
+    }
+
+    /**
+     * @return null|SimpleXMLElement
+     */
+    private function getRequestXmlElement()
+    {
+        $request = file_get_contents("php://input");
+
+        if (empty($request)) {
+            return null;
+        }
+
+        $xml = new SimpleXMLElement($request);
+
+        return $xml;
+    }
+
+    /**
+     * @return ImsLtiServiceResponse|null
+     */
+    public function processServiceRequest()
+    {
+        $xml = $this->getRequestXmlElement();
+
+        if (empty($xml)) {
+            return null;
+        }
+
+        $request = ImsLtiServiceRequestFactory::create($xml);
+        $response = $request->process();
+
+        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, 'course' => $course]);
+
+        return !empty($tool);
+    }
+
+    /**
+     * @param string $configUrl
+     *
+     * @return string
+     * @throws Exception
+     */
+    public function getLaunchUrlFromCartridge($configUrl)
+    {
+        $options = [
+            CURLOPT_CUSTOMREQUEST => 'GET',
+            CURLOPT_POST => false,
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_HEADER => false,
+            CURLOPT_FOLLOWLOCATION => true,
+            CURLOPT_ENCODING => '',
+            CURLOPT_SSL_VERIFYPEER => false,
+        ];
+
+        $ch = curl_init($configUrl);
+        curl_setopt_array($ch, $options);
+        $content = curl_exec($ch);
+        $errno = curl_errno($ch);
+        curl_close($ch);
+
+        if ($errno !== 0) {
+            throw new Exception($this->get_lang('NoAccessToUrl'));
+        }
+
+        $xml = new SimpleXMLElement($content);
+        $result = $xml->xpath('blti:launch_url');
+
+        if (empty($result)) {
+            throw new Exception($this->get_lang('LaunchUrlNotFound'));
+        }
+
+        $launchUrl = $result[0];
+
+        return (string) $launchUrl;
+    }
+
+    /**
+     * @param array $params
+     */
+    public function trimParams(array &$params)
+    {
+        foreach ($params as $key => $value) {
+            $newValue = preg_replace('/\s+/', ' ', $value);
+
+            $params[$key] = trim($newValue);
+        }
+    }
+
+    /**
+     * @param ImsLtiTool $tool
+     * @param array      $params
+     *
+     * @return array
+     */
+    public function removeUrlParamsFromLaunchParams(ImsLtiTool $tool, array &$params)
+    {
+        $urlQuery = parse_url($tool->getLaunchUrl(), PHP_URL_QUERY);
+
+        if (empty($urlQuery)) {
+            return $params;
+        }
+
+        $queryParams = [];
+        parse_str($urlQuery, $queryParams);
+        $queryKeys = array_keys($queryParams);
+
+        foreach ($queryKeys as $key) {
+            if (isset($params[$key])) {
+                unset($params[$key]);
+            }
+        }
+    }
+
+    /**
+     * Avoid conflict with foreign key when deleting a course
+     *
+     * @param int $courseId
+     */
+    public function doWhenDeletingCourse($courseId)
+    {
+        $em = Database::getManager();
+
+        $q = $em
+            ->createQuery(
+                'DELETE FROM ChamiloPluginBundle:ImsLti\ImsLtiTool tool
+                    WHERE tool.course = :c_id and tool.parent IS NOT NULL'
+            );
+        error_log($q->getSQL());
+        $q->execute(['c_id' => (int) $courseId]);
+
+        $em->createQuery('DELETE FROM ChamiloPluginBundle:ImsLti\ImsLtiTool tool WHERE tool.course = :c_id')
+            ->execute(['c_id' => (int) $courseId]);
+    }
 }

+ 214 - 0
plugin/ims_lti/configure.php

@@ -0,0 +1,214 @@
+<?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(['parent' => null, 'course' => null]);
+
+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 FrmAdd('ims_lti_add_tool', [], $baseTool);
+        $form->build();
+
+        if ($baseTool) {
+            $form->addHidden('type', $baseTool->getId());
+        }
+
+        if ($form->validate()) {
+            $formValues = $form->getSubmitValues();
+
+            $tool = new ImsLtiTool();
+            $tool
+                ->setName($formValues['name'])
+                ->setDescription(
+                    empty($formValues['description']) ? null : $formValues['description']
+                )
+                ->setCustomParams(
+                    empty($formValues['custom_params']) ? null : $formValues['custom_params']
+                )
+                ->setCourse($course)
+                ->setActiveDeepLinking(!empty($formValues['deep_linking']))
+                ->setPrivacy(
+                    !empty($formValues['share_name']),
+                    !empty($formValues['share_email']),
+                    !empty($formValues['share_picture'])
+                );
+
+            if ($baseTool) {
+                $tool
+                    ->setLaunchUrl($baseTool->getLaunchUrl())
+                    ->setConsumerKey($baseTool->getConsumerKey())
+                    ->setSharedSecret($baseTool->getSharedSecret());
+            } else {
+                if (empty($formValues['consumer_key']) && empty($formValues['shared_secret'])) {
+                    try {
+                        $launchUrl = $plugin->getLaunchUrlFromCartridge($formValues['launch_url']);
+                    } catch (Exception $e) {
+                        Display::addFlash(
+                            Display::return_message($e->getMessage(), 'error')
+                        );
+
+                        header('Location: '.api_get_self().'?'.api_get_cidreq());
+                        exit;
+                    }
+
+                    $tool->setLaunchUrl($launchUrl);
+                } else {
+                    $tool
+                        ->setLaunchUrl($formValues['launch_url'])
+                        ->setConsumerKey($formValues['consumer_key'])
+                        ->setSharedSecret($formValues['shared_secret']);
+                }
+            }
+
+            if (null === $baseTool ||
+                ($baseTool && !$baseTool->isActiveDeepLinking())
+            ) {
+                $tool
+                    ->setActiveDeepLinking(
+                        !empty($formValues['deep_linking'])
+                    );
+            }
+
+            if ($baseTool) {
+                $tool->setParent($baseTool);
+            }
+
+            $em->persist($tool);
+            $em->flush();
+
+            if (!$tool->isActiveDeepLinking()) {
+                $plugin->addCourseTool($course, $tool);
+            }
+
+            Display::addFlash(
+                Display::return_message($plugin->get_lang('ToolAdded'), 'success')
+            );
+
+            header('Location: '.api_get_self().'?'.api_get_cidreq());
+            exit;
+        }
+
+        $form->setDefaultValues();
+        break;
+    case 'edit':
+        /** @var ImsLtiTool|null $tool */
+        $tool = null;
+
+        if (!empty($_REQUEST['id'])) {
+            $tool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', (int) $_REQUEST['id']);
+        }
+
+        if (empty($tool) ||
+            !ImsLtiPlugin::existsToolInCourse($tool->getId(), $course)
+        ) {
+            api_not_allowed(
+                true,
+                Display::return_message($plugin->get_lang('ToolNotAvailable'), 'error')
+            );
+
+            break;
+        }
+
+        $form = new FrmEdit('ims_lti_edit_tool', [], $tool);
+        $form->build(false);
+
+        if ($form->validate()) {
+            $formValues = $form->getSubmitValues();
+
+            $tool
+                ->setName($formValues['name'])
+                ->setDescription(
+                    empty($formValues['description']) ? null : $formValues['description']
+                )
+                ->setActiveDeepLinking(
+                    !empty($formValues['deep_linking'])
+                )
+                ->setCustomParams(
+                    empty($formValues['custom_params']) ? null : $formValues['custom_params']
+                )
+                ->setPrivacy(
+                    !empty($formValues['share_name']),
+                    !empty($formValues['share_email']),
+                    !empty($formValues['share_picture'])
+                );
+
+            if (null === $tool->getParent()) {
+                $tool
+                    ->setLaunchUrl($formValues['launch_url'])
+                    ->setConsumerKey($formValues['consumer_key'])
+                    ->setSharedSecret($formValues['shared_secret']);
+            }
+
+            $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->setDefaultValues();
+        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', get_lang('MakeQualifiable'), [], 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();

+ 193 - 0
plugin/ims_lti/gradebook/OutcomeForm.php

@@ -0,0 +1,193 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+class OutcomeForm extends EvalForm
+{
+    /**
+     * Builds a form containing form items based on a given parameter.
+     *
+     * @param int        $form_type         1=add, 2=edit,3=move,4=result_add
+     * @param Evaluation $evaluation_object the category object
+     * @param obj        $result_object     the result object
+     * @param string     $form_name
+     * @param string     $method
+     * @param string     $action
+     */
+    public function __construct(
+        $evaluation_object,
+        $result_object,
+        $form_name,
+        $method = 'post',
+        $action = null,
+        $extra1 = null,
+        $extra2 = null
+    ) {
+        parent::__construct(
+            -1,
+            $evaluation_object,
+            $result_object,
+            $form_name,
+            $method,
+            $action,
+            $extra1,
+            $extra2
+        );
+
+        $this->build_add_form();
+        $this->setDefaults();
+    }
+
+    /**
+     * Builds a basic form that is used in add and edit.
+     *
+     * @param int $edit
+     *
+     * @throws Exception
+     */
+    private function build_basic_form($edit = 0)
+    {
+        $this->addElement('header', get_plugin_lang('NewOutcomeFormTitle'));
+        $this->addElement('hidden', 'hid_user_id');
+        $this->addElement('hidden', 'hid_course_code');
+
+        $this->addText(
+            'name',
+            get_lang('EvaluationName'),
+            true,
+            [
+                'maxlength' => '50',
+                'id' => 'evaluation_title',
+            ]
+        );
+
+        $cat_id = $this->evaluation_object->get_category_id();
+
+        $session_id = api_get_session_id();
+        $course_code = api_get_course_id();
+        $all_categories = Category:: load(null, null, $course_code, null, null, $session_id, false);
+
+        if (count($all_categories) == 1) {
+            $this->addElement('hidden', 'hid_category_id', $cat_id);
+        } else {
+            $select_gradebook = $this->addElement(
+                'select',
+                'hid_category_id',
+                get_lang('SelectGradebook'),
+                [],
+                ['id' => 'hid_category_id']
+            );
+            $this->addRule('hid_category_id', get_lang('ThisFieldIsRequired'), 'nonzero');
+            $default_weight = 0;
+            if (!empty($all_categories)) {
+                foreach ($all_categories as $my_cat) {
+                    if ($my_cat->get_course_code() == api_get_course_id()) {
+                        $grade_model_id = $my_cat->get_grade_model_id();
+                        if (empty($grade_model_id)) {
+                            if ($my_cat->get_parent_id() == 0) {
+                                $default_weight = $my_cat->get_weight();
+                                $select_gradebook->addoption(get_lang('Default'), $my_cat->get_id());
+                                $cats_added[] = $my_cat->get_id();
+                            } else {
+                                $select_gradebook->addoption($my_cat->get_name(), $my_cat->get_id());
+                                $cats_added[] = $my_cat->get_id();
+                            }
+                        } else {
+                            $select_gradebook->addoption(get_lang('Select'), 0);
+                        }
+                        if ($this->evaluation_object->get_category_id() == $my_cat->get_id()) {
+                            $default_weight = $my_cat->get_weight();
+                        }
+                    }
+                }
+            }
+        }
+
+        $this->addFloat(
+            'weight_mask',
+            [
+                get_lang('Weight'),
+                null,
+                ' [0 .. <span id="max_weight">'.$all_categories[0]->get_weight().'</span>] ',
+            ],
+            true,
+            [
+                'size' => '4',
+                'maxlength' => '5',
+            ]
+        );
+
+        if ($edit) {
+            if (!$this->evaluation_object->has_results()) {
+                $this->addText(
+                    'max',
+                    get_lang('QualificationNumeric'),
+                    true,
+                    [
+                        'maxlength' => '5',
+                    ]
+                );
+            } else {
+                $this->addText(
+                    'max',
+                    [get_lang('QualificationNumeric'), get_lang('CannotChangeTheMaxNote')],
+                    false,
+                    [
+                        'maxlength' => '5',
+                        'disabled' => 'disabled',
+                    ]
+                );
+            }
+        } else {
+            $this->addText(
+                'max',
+                get_lang('QualificationNumeric'),
+                true,
+                [
+                    'maxlength' => '5',
+                ]
+            );
+            $default_max = api_get_setting('gradebook_default_weight');
+            $defaults['max'] = isset($default_max) ? $default_max : 100;
+            $this->setDefaults($defaults);
+        }
+
+        $this->addElement('textarea', 'description', get_lang('Description'));
+        $this->addRule('hid_category_id', get_lang('ThisFieldIsRequired'), 'required');
+        $this->addElement('checkbox', 'visible', null, get_lang('Visible'));
+        $this->addRule('max', get_lang('OnlyNumbers'), 'numeric');
+        $this->addRule(
+            'max',
+            get_lang('NegativeValue'),
+            'compare',
+            '>=',
+            'server',
+            false,
+            false,
+            0
+        );
+        $setting = api_get_setting('tool_visible_by_default_at_creation');
+        $visibility_default = 1;
+        if (isset($setting['gradebook']) && $setting['gradebook'] == 'false') {
+            $visibility_default = 0;
+        }
+        $this->setDefaults(['visible' => $visibility_default]);
+    }
+
+    /**
+     *
+     */
+    protected function build_add_form()
+    {
+        $this->setDefaults(
+            [
+                'hid_user_id' => $this->evaluation_object->get_user_id(),
+                'hid_category_id' => $this->evaluation_object->get_category_id(),
+                'hid_course_code' => $this->evaluation_object->get_course_code(),
+                'created_at' => api_get_utc_datetime(),
+            ]
+        );
+        $this->build_basic_form();
+
+        $this->addButtonCreate(get_lang('AddAssessment'), 'submit');
+    }
+}

+ 144 - 0
plugin/ims_lti/gradebook/add_eval.php

@@ -0,0 +1,144 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * @package plugin.ims_lti
+ */
+
+use Chamilo\CoreBundle\Entity\GradebookEvaluation;
+use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
+
+require_once __DIR__.'/../../../main/inc/global.inc.php';
+
+$current_course_tool = TOOL_GRADEBOOK;
+
+api_protect_course_script(true);
+api_block_anonymous_users();
+GradebookUtils::block_students();
+
+$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']);
+
+if (!empty($select_cat)) {
+    $evaladd->set_category_id($_GET['selectcat']);
+    $cat = Category::load($_GET['selectcat']);
+    $evaladd->set_course_code($cat[0]->get_course_code());
+} else {
+    $evaladd->set_category_id(0);
+}
+
+$form = new EvalForm(
+    EvalForm::TYPE_ADD,
+    $evaladd,
+    null,
+    'add_eval_form',
+    null,
+    api_get_self().'?selectcat='.$select_cat.'&'.api_get_cidreq()
+);
+$form->removeElement('name');
+$form->removeElement('addresult');
+$slcLtiTools = $form->createElement('select', 'name', get_lang('Tool'));
+$form->insertElementBefore($slcLtiTools, 'hid_category_id');
+$form->addRule('name', get_lang('ThisFieldIsRequired'), 'required');
+
+$ltiTools = $ltiToolRepo->findBy(['course' => $course, 'gradebookEval' => null]);
+
+/** @var ImsLtiTool $ltiTool */
+foreach ($ltiTools as $ltiTool) {
+    $slcLtiTools->addOption($ltiTool->getName(), $ltiTool->getId());
+}
+
+if ($form->validate()) {
+    $values = $form->exportValues();
+
+    /** @var ImsLtiTool $ltiTool */
+    $ltiTool = $ltiToolRepo->find($values['name']);
+
+    if (!$ltiTool) {
+        api_not_allowed();
+    }
+
+    $eval = new Evaluation();
+    $eval->set_name($ltiTool->getName());
+    $eval->set_description($values['description']);
+    $eval->set_user_id($values['hid_user_id']);
+
+    if (!empty($values['hid_course_code'])) {
+        $eval->set_course_code($values['hid_course_code']);
+    }
+
+    //Always add the gradebook to the course
+    $eval->set_course_code(api_get_course_id());
+    $eval->set_category_id($values['hid_category_id']);
+
+    $parent_cat = Category::load($values['hid_category_id']);
+    $global_weight = $cat[0]->get_weight();
+    //$values['weight'] = $values['weight_mask']/$global_weight*$parent_cat[0]->get_weight();
+    $values['weight'] = $values['weight_mask'];
+
+    $eval->set_weight($values['weight']);
+    $eval->set_max($values['max']);
+    $eval->set_visible(empty($values['visible']) ? 0 : 1);
+    $eval->add();
+
+    /** @var GradebookEvaluation $gradebookEval */
+    $gradebookEval = $em->find('ChamiloCoreBundle:GradebookEvaluation', $eval->get_id());
+    $ltiTool->setGradebookEval($gradebookEval);
+
+    $em->persist($ltiTool);
+    $em->flush();
+
+    header('Location: '.Category::getUrl().'selectcat='.$eval->get_category_id());
+
+    exit;
+}
+
+$interbreadcrumb[] = [
+    'url' => Category::getUrl().'selectcat='.$select_cat,
+    'name' => get_lang('Gradebook'),
+];
+$this_section = SECTION_COURSES;
+
+$htmlHeadXtra[] = '<script>
+$(document).ready( function() {
+    $("#hid_category_id").change(function() {
+       $("#hid_category_id option:selected").each(function () {
+           var cat_id = $(this).val();
+            $.ajax({
+                url: "'.api_get_path(WEB_AJAX_PATH).'gradebook.ajax.php?a=get_gradebook_weight",
+                data: "cat_id="+cat_id,
+                success: function(return_value) {
+                    if (return_value != 0 ) {
+                        $("#max_weight").html(return_value);
+                    }
+                }
+            });
+       });
+    });
+});
+</script>';
+
+Display::display_header(get_lang('NewEvaluation'));
+
+$form->display();
+
+Display::display_footer();

+ 74 - 0
plugin/ims_lti/item_return.php

@@ -0,0 +1,74 @@
+<?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(false);
+api_block_anonymous_users(false);
+
+if (empty($_POST['content_items']) || empty($_POST['data'])) {
+    api_not_allowed(false);
+}
+
+$toolId = str_replace('tool:', '', $_POST['data']);
+
+$plugin = ImsLtiPlugin::create();
+$em = Database::getManager();
+/** @var Course $course */
+$course = $em->find('ChamiloCoreBundle:Course', api_get_course_int_id());
+/** @var ImsLtiTool|null $ltiTool */
+$ltiTool = $em->find('ChamiloPluginBundle:ImsLti\ImsLtiTool', $toolId);
+
+if (!$ltiTool) {
+    api_not_allowed();
+}
+
+$consumer = new OAuthConsumer(
+    $_POST['oauth_consumer_key'],
+    $ltiTool->getSharedSecret()
+);
+$hmacMethod = new OAuthSignatureMethod_HMAC_SHA1();
+
+$request = OAuthRequest::from_request('POST', api_get_path(WEB_PLUGIN_PATH).'ims_lti/item_return.php');
+$request->sign_request($hmacMethod, $consumer, '');
+$signature = $request->get_parameter('oauth_signature');
+
+if ($signature !== $_POST['oauth_signature']) {
+    api_not_allowed();
+}
+
+$contentItems = json_decode($_POST['content_items'], true);
+$contentItems = $contentItems['@graph'];
+
+foreach ($contentItems as $contentItem) {
+    if ('LtiLinkItem' === $contentItem['@type']) {
+        if ('application/vnd.ims.lti.v1.ltilink' === $contentItem['mediaType']) {
+            $plugin->saveItemAsLtiLink($contentItem, $ltiTool, $course);
+
+            Display::addFlash(
+                Display::return_message($plugin->get_lang('ToolAdded'), 'success')
+            );
+        }
+    }
+}
+
+$currentUrl = api_get_path(WEB_PLUGIN_PATH).'ims_lti/start.php?id='.$ltiTool->getId();
+?>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport"
+          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>Document</title>
+</head>
+<body>
+    <script>
+        window.parent.location.href = '<?php echo $currentUrl ?>';
+    </script>
+</body>
+</html>

+ 69 - 0
plugin/ims_lti/outcome_service.php

@@ -0,0 +1,69 @@
+<?php
+/* For license terms, see /license.txt */
+
+use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
+
+require_once __DIR__.'/../../main/inc/global.inc.php';
+
+header('Content-Type: application/xml');
+
+$url = api_get_path(WEB_PATH).'lti/os';
+
+$em = Database::getManager();
+$toolRepo = $em->getRepository('ChamiloPluginBundle:ImsLti\ImsLtiTool');
+
+$headers = OAuthUtil::get_headers();
+
+if (empty($headers['Authorization'])) {
+    error_log('Authorization header missed');
+
+    exit;
+}
+
+$authParams = OAuthUtil::split_header($headers['Authorization']);
+
+if (empty($authParams) || empty($authParams['oauth_consumer_key']) || empty($authParams['oauth_signature'])) {
+    error_log('Authorization params not found');
+
+    exit;
+}
+
+$tools = $toolRepo->findBy(['consumerKey' => $authParams['oauth_consumer_key']]);
+$toolIsFound = false;
+
+/** @var ImsLtiTool $tool */
+foreach ($tools as $tool) {
+    $consumer = new OAuthConsumer($tool->getConsumerKey(), $tool->getSharedSecret());
+    $hmacMethod = new OAuthSignatureMethod_HMAC_SHA1();
+
+    $request = OAuthRequest::from_request('POST', $url);
+    $request->sign_request($hmacMethod, $consumer, '');
+    $signature = $request->get_parameter('oauth_signature');
+
+    if ($signature === $authParams['oauth_signature']) {
+        $toolIsFound = true;
+
+        break;
+    }
+}
+
+if (false === $toolIsFound) {
+    error_log('Tool not found. Signature is not valid');
+
+    exit;
+}
+
+$body = file_get_contents('php://input');
+$bodyHash = base64_encode(sha1($body, true));
+
+if ($bodyHash !== $authParams['oauth_body_hash']) {
+    error_log('Authorization request not valid');
+
+    exit;
+}
+
+$plugin = ImsLtiPlugin::create();
+
+$process = $plugin->processServiceRequest();
+
+echo $process;

+ 93 - 0
plugin/ims_lti/src/Form/FrmAdd.php

@@ -0,0 +1,93 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
+
+/**
+ * Class FrmAdd.
+ */
+class FrmAdd extends FormValidator
+{
+    /**
+     * @var ImsLtiTool|null
+     */
+    private $baseTool;
+
+    /**
+     * FrmAdd constructor.
+     *
+     * @param string          $name
+     * @param array           $attributes
+     * @param ImsLtiTool|null $tool
+     */
+    public function __construct(
+        $name,
+        $attributes = [],
+        ImsLtiTool $tool = null
+    ) {
+        parent::__construct($name, 'POST', '', '', $attributes, self::LAYOUT_HORIZONTAL, true);
+
+        $this->baseTool = $tool;
+    }
+
+    /**
+     * Build the form
+     */
+    public function build()
+    {
+        $plugin = ImsLtiPlugin::create();
+
+        $this->addHeader($plugin->get_lang('ToolSettings'));
+        $this->addText('name', get_lang('Name'));
+        $this->addTextarea('description', get_lang('Description'));
+
+        if (null === $this->baseTool) {
+            $this->addUrl('launch_url', $plugin->get_lang('LaunchUrl'), true);
+            $this->addText('consumer_key', $plugin->get_lang('ConsumerKey'), false);
+            $this->addText('shared_secret', $plugin->get_lang('SharedSecret'), false);
+        }
+
+        $this->addButtonAdvancedSettings('lti_adv');
+        $this->addHtml('<div id="lti_adv_options" style="display:none;">');
+        $this->addTextarea(
+            'custom_params',
+            [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]
+        );
+
+        if (null === $this->baseTool ||
+            ($this->baseTool && !$this->baseTool->isActiveDeepLinking())
+        ) {
+            $this->addCheckBox(
+                'deep_linking',
+                [null, $plugin->get_lang('SupportDeppLinkingHelp'), null],
+                $plugin->get_lang('SupportDeepLinking')
+            );
+        }
+
+        $this->addHtml('</div>');
+        $this->addButtonAdvancedSettings('lti_privacy', get_lang('Privacy'));
+        $this->addHtml('<div id="lti_privacy_options" style="display:none;">');
+        $this->addCheckBox('share_name', null, $plugin->get_lang('ShareLauncherName'));
+        $this->addCheckBox('share_email', null, $plugin->get_lang('ShareLauncherEmail'));
+        $this->addCheckBox('share_picture', null, $plugin->get_lang('ShareLauncherPicture'));
+        $this->addHtml('</div>');
+        $this->addButtonCreate($plugin->get_lang('AddExternalTool'));
+        $this->applyFilter('__ALL__', 'trim');
+    }
+
+    public function setDefaultValues()
+    {
+        if (null !== $this->baseTool) {
+            $this->setDefaults(
+                [
+                    'name' => $this->baseTool->getName(),
+                    'description' => $this->baseTool->getDescription(),
+                    'custom_params' => $this->baseTool->getCustomParams(),
+                    'share_name' => $this->baseTool->isSharingName(),
+                    'share_email' => $this->baseTool->isSharingEmail(),
+                    'share_picture' => $this->baseTool->isSharingPicture(),
+                ]
+            );
+        }
+    }
+}

+ 114 - 0
plugin/ims_lti/src/Form/FrmEdit.php

@@ -0,0 +1,114 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\PluginBundle\Entity\ImsLti\ImsLtiTool;
+
+/**
+ * Class FrmAdd.
+ */
+class FrmEdit extends FormValidator
+{
+    /**
+     * @var ImsLtiTool|null
+     */
+    private $tool;
+
+    /**
+     * FrmAdd constructor.
+     *
+     * @param string          $name
+     * @param array           $attributes
+     * @param ImsLtiTool|null $tool
+     */
+    public function __construct(
+        $name,
+        $attributes = [],
+        ImsLtiTool $tool = null
+    ) {
+        parent::__construct($name, 'POST', '', '', $attributes, self::LAYOUT_HORIZONTAL, true);
+
+        $this->tool = $tool;
+    }
+
+    /**
+     * Build the form.
+     *
+     * @param bool $globalMode
+     *
+     * @throws Exception
+     */
+    public function build($globalMode = true)
+    {
+        $plugin = ImsLtiPlugin::create();
+        $course = $this->tool->getCourse();
+        $parent = $this->tool->getParent();
+
+        $this->addHeader($plugin->get_lang('ToolSettings'));
+
+        if (null !== $course && $globalMode) {
+            $this->addHtml(
+                Display::return_message(
+                    sprintf($plugin->get_lang('ToolAddedOnCourseX'), $course->getTitle()),
+                    'normal',
+                    false
+                )
+            );
+        }
+
+        $this->addText('name', get_lang('Name'));
+        $this->addTextarea('description', get_lang('Description'));
+
+        if (null === $parent) {
+            $this->addUrl('launch_url', $plugin->get_lang('LaunchUrl'), true);
+            $this->addText('consumer_key', $plugin->get_lang('ConsumerKey'), false);
+            $this->addText('shared_secret', $plugin->get_lang('SharedSecret'), false);
+        }
+
+        $this->addButtonAdvancedSettings('lti_adv');
+        $this->addHtml('<div id="lti_adv_options" style="display:none;">');
+        $this->addTextarea(
+            'custom_params',
+            [$plugin->get_lang('CustomParams'), $plugin->get_lang('CustomParamsHelp')]
+        );
+
+        if (null === $parent ||
+            (null !== $parent && !$parent->isActiveDeepLinking())
+        ) {
+            $this->addCheckBox(
+                'deep_linking',
+                [null, $plugin->get_lang('SupportDeppLinkingHelp'), null],
+                $plugin->get_lang('SupportDeepLinking')
+            );
+        }
+
+        $this->addHtml('</div>');
+        $this->addButtonAdvancedSettings('lti_privacy', get_lang('Privacy'));
+        $this->addHtml('<div id="lti_privacy_options" style="display:none;">');
+        $this->addCheckBox('share_name', null, $plugin->get_lang('ShareLauncherName'));
+        $this->addCheckBox('share_email', null, $plugin->get_lang('ShareLauncherEmail'));
+        $this->addCheckBox('share_picture', null, $plugin->get_lang('ShareLauncherPicture'));
+        $this->addHtml('</div>');
+        $this->addButtonUpdate($plugin->get_lang('EditExternalTool'));
+        $this->addHidden('id', $this->tool->getId());
+        $this->addHidden('action', 'edit');
+        $this->applyFilter('__ALL__', 'trim');
+    }
+
+    public function setDefaultValues()
+    {
+        $this->setDefaults(
+            [
+                'name' => $this->tool->getName(),
+                'description' => $this->tool->getDescription(),
+                'launch_url' => $this->tool->getLaunchUrl(),
+                'consumer_key' => $this->tool->getConsumerKey(),
+                'shared_secret' => $this->tool->getSharedSecret(),
+                'custom_params' => $this->tool->getCustomParams(),
+                'deep_linking' => $this->tool->isActiveDeepLinking(),
+                'share_name' => $this->tool->isSharingName(),
+                'share_email' => $this->tool->isSharingEmail(),
+                'share_picture' => $this->tool->isSharingPicture(),
+            ]
+        );
+    }
+}

+ 132 - 0
plugin/ims_lti/src/ImsLti.php

@@ -0,0 +1,132 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\UserBundle\Entity\User;
+
+/**
+ * Class ImsLti.
+ */
+class ImsLti
+{
+    /**
+     * @param User         $user
+     * @param Course       $course
+     * @param Session|null $session
+     *
+     * @return array
+     */
+    public static function getSubstitutableParams(User $user, Course $course, Session $session = null)
+    {
+        return [
+            '$User.id' => $user->getId(),
+            '$User.image' => ['user_image'],
+            '$User.username' => $user->getUsername(),
+
+            '$Person.sourcedId' => false,
+            '$Person.name.full' => $user->getFullname(),
+            '$Person.name.family' => $user->getLastname(),
+            '$Person.name.given' => $user->getFirstname(),
+            '$Person.name.middle' => false,
+            '$Person.name.prefix' => false,
+            '$Person.name.suffix' => false,
+            '$Person.address.street1' => $user->getAddress(),
+            '$Person.address.street2' => false,
+            '$Person.address.street3' => false,
+            '$Person.address.street4' => false,
+            '$Person.address.locality' => false,
+            '$Person.address.statepr' => false,
+            '$Person.address.country' => false,
+            '$Person.address.postcode' => false,
+            '$Person.address.timezone' => false, //$user->getTimezone(),
+            '$Person.phone.mobile' => false,
+            '$Person.phone.primary' => $user->getPhone(),
+            '$Person.phone.home' => false,
+            '$Person.phone.work' => false,
+            '$Person.email.primary' => $user->getEmail(),
+            '$Person.email.personal' => false,
+            '$Person.webaddress' => false, //$user->getWebsite(),
+            '$Person.sms' => false,
+
+            '$CourseTemplate.sourcedId' => false,
+            '$CourseTemplate.label' => false,
+            '$CourseTemplate.title' => false,
+            '$CourseTemplate.shortDescription' => false,
+            '$CourseTemplate.longDescription' => false,
+            '$CourseTemplate.courseNumber' => false,
+            '$CourseTemplate.credits' => false,
+
+            '$CourseOffering.sourcedId' => false,
+            '$CourseOffering.label' => false,
+            '$CourseOffering.title' => false,
+            '$CourseOffering.shortDescription' => false,
+            '$CourseOffering.longDescription' => false,
+            '$CourseOffering.courseNumber' => false,
+            '$CourseOffering.credits' => false,
+            '$CourseOffering.academicSession' => false,
+
+            '$CourseSection.sourcedId' => ['lis_course_section_sourcedid'],
+            '$CourseSection.label' => $course->getCode(),
+            '$CourseSection.title' => $course->getTitle(),
+            '$CourseSection.shortDescription' => false,
+            '$CourseSection.longDescription' => $session && $session->getShowDescription()
+                ? $session->getDescription()
+                : false,
+            '$CourseSection.courseNumber' => false,
+            '$CourseSection.credits' => false,
+            '$CourseSection.maxNumberofStudents' => false,
+            '$CourseSection.numberofStudents' => false,
+            '$CourseSection.dept' => false,
+            '$CourseSection.timeFrame.begin' => $session && $session->getDisplayStartDate()
+                ? $session->getDisplayStartDate()->format(DateTime::ATOM)
+                : false,
+            '$CourseSection.timeFrame.end' => $session && $session->getDisplayEndDate()
+                ? $session->getDisplayEndDate()->format(DateTime::ATOM)
+                : false,
+            '$CourseSection.enrollControl.accept' => false,
+            '$CourseSection.enrollControl.allowed' => false,
+            '$CourseSection.dataSource' => false,
+            '$CourseSection.sourceSectionId' => false,
+
+            '$Group.sourcedId' => false,
+            '$Group.grouptype.scheme' => false,
+            '$Group.grouptype.typevalue' => false,
+            '$Group.grouptype.level' => false,
+            '$Group.email' => false,
+            '$Group.url' => false,
+            '$Group.timeFrame.begin' => false,
+            '$Group.timeFrame.end' => false,
+            '$Group.enrollControl.accept' => false,
+            '$Group.enrollControl.allowed' => false,
+            '$Group.shortDescription' => false,
+            '$Group.longDescription' => false,
+            '$Group.parentId' => false,
+
+            '$Membership.sourcedId' => false,
+            '$Membership.collectionSourcedId' => false,
+            '$Membership.personSourcedId' => false,
+            '$Membership.status' => false,
+            '$Membership.role' => ['roles'],
+            '$Membership.createdTimestamp' => false,
+            '$Membership.dataSource' => false,
+
+            '$LineItem.sourcedId' => false,
+            '$LineItem.type' => false,
+            '$LineItem.type.displayName' => false,
+            '$LineItem.resultValue.max' => false,
+            '$LineItem.resultValue.list' => false,
+            '$LineItem.dataSource' => false,
+
+            '$Result.sourcedGUID' => ['lis_result_sourcedid'],
+            '$Result.sourcedId' => ['lis_result_sourcedid'],
+            '$Result.createdTimestamp' => false,
+            '$Result.status' => false,
+            '$Result.resultScore' => false,
+            '$Result.dataSource' => false,
+
+            '$ResourceLink.title' => ['resource_link_title'],
+            '$ResourceLink.description' => ['resource_link_description'],
+        ];
+    }
+}

+ 74 - 0
plugin/ims_lti/src/ImsLtiServiceDeleteRequest.php

@@ -0,0 +1,74 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\CoreBundle\Entity\GradebookEvaluation;
+use Chamilo\UserBundle\Entity\User;
+
+/**
+ * Class ImsLtiDeleteServiceRequest.
+ */
+class ImsLtiServiceDeleteRequest extends ImsLtiServiceRequest
+{
+    /**
+     * ImsLtiDeleteServiceRequest constructor.
+     *
+     * @param SimpleXMLElement $xml
+     */
+    public function __construct(SimpleXMLElement $xml)
+    {
+        parent::__construct($xml);
+
+        $this->responseType = ImsLtiServiceResponse::TYPE_DELETE;
+        $this->xmlRequest = $this->xmlRequest->deleteResultRequest;
+    }
+
+    protected function processBody()
+    {
+        $resultRecord = $this->xmlRequest->resultRecord;
+        $sourcedId = (string) $resultRecord->sourcedGUID->sourcedId;
+        $sourcedId = htmlspecialchars_decode($sourcedId);
+
+        $sourcedParts = json_decode($sourcedId, true);
+
+        if (empty($sourcedParts)) {
+            $this->statusInfo
+                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR)
+                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
+
+            return;
+        }
+
+        $em = Database::getManager();
+        /** @var GradebookEvaluation $evaluation */
+        $evaluation = $em->find('ChamiloCoreBundle:GradebookEvaluation', $sourcedParts['e']);
+        /** @var User $user */
+        $user = $em->find('ChamiloUserBundle:User', $sourcedParts['u']);
+
+        if (empty($evaluation) || empty($user)) {
+            $this->statusInfo
+                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
+                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
+
+            return;
+        }
+
+        $results = Result::load(null, $user->getId(), $evaluation->getId());
+
+        if (empty($results)) {
+            $this->statusInfo
+                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
+                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
+
+            return;
+        }
+
+        /** @var Result $result */
+        $result = $results[0];
+        $result->addResultLog($user->getId(), $evaluation->getId());
+        $result->delete();
+
+        $this->statusInfo
+            ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
+            ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_SUCCESS);
+    }
+}

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

@@ -0,0 +1,29 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiServiceDeleteResponse.
+ */
+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
+     */
+    protected function generateBody(SimpleXMLElement $xmlBody)
+    {
+        $xmlBody->addChild('deleteResultResponse');
+    }
+}

+ 83 - 0
plugin/ims_lti/src/ImsLtiServiceReadRequest.php

@@ -0,0 +1,83 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\CoreBundle\Entity\GradebookEvaluation;
+use Chamilo\UserBundle\Entity\User;
+
+/**
+ * Class ImsLtiServiceReadRequest.
+ */
+class ImsLtiServiceReadRequest extends ImsLtiServiceRequest
+{
+    /**
+     * ImsLtiServiceReadRequest constructor.
+     *
+     * @param SimpleXMLElement $xml
+     */
+    public function __construct(SimpleXMLElement $xml)
+    {
+        parent::__construct($xml);
+
+        $this->responseType = ImsLtiServiceResponse::TYPE_READ;
+        $this->xmlRequest = $this->xmlRequest->readResultRequest;
+    }
+
+    protected function processBody()
+    {
+        $resultRecord = $this->xmlRequest->resultRecord;
+        $sourcedId = (string) $resultRecord->sourcedGUID->sourcedId;
+        $sourcedId = htmlspecialchars_decode($sourcedId);
+
+        $sourcedParts = json_decode($sourcedId, true);
+
+        if (empty($sourcedParts)) {
+            $this->statusInfo
+                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR)
+                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
+
+            return;
+        }
+
+        $em = Database::getManager();
+        /** @var GradebookEvaluation $evaluation */
+        $evaluation = $em->find('ChamiloCoreBundle:GradebookEvaluation', $sourcedParts['e']);
+        /** @var User $user */
+        $user = $em->find('ChamiloUserBundle:User', $sourcedParts['u']);
+
+        if (empty($evaluation) || empty($user)) {
+            $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 = 0;
+
+            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($responseDescription);
+
+        $this->responseBodyParam = (string) $ltiScore;
+    }
+}

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

@@ -0,0 +1,35 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiReadServiceResponse
+ */
+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
+     */
+    protected function generateBody(SimpleXMLElement $xmlBody)
+    {
+        $resultResponse = $xmlBody->addChild('readResultResponse');
+
+        $xmlResultScore = $resultResponse->addChild('result')
+            ->addChild('resultScore');
+
+        $xmlResultScore->addChild('language', 'en');
+        $xmlResultScore->addChild('textString', $this->bodyParams);
+    }
+}

+ 103 - 0
plugin/ims_lti/src/ImsLtiServiceReplaceRequest.php

@@ -0,0 +1,103 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+use Chamilo\CoreBundle\Entity\GradebookEvaluation;
+use Chamilo\UserBundle\Entity\User;
+
+/**
+ * Class ImsLtiReplaceServiceRequest.
+ */
+class ImsLtiServiceReplaceRequest extends ImsLtiServiceRequest
+{
+    /**
+     * ImsLtiReplaceServiceRequest constructor.
+     *
+     * @param SimpleXMLElement $xml
+     */
+    public function __construct(SimpleXMLElement $xml)
+    {
+        parent::__construct($xml);
+
+        $this->responseType = ImsLtiServiceResponse::TYPE_REPLACE;
+        $this->xmlRequest = $this->xmlRequest->replaceResultRequest;
+    }
+
+    protected function processBody()
+    {
+        $resultRecord = $this->xmlRequest->resultRecord;
+        $sourcedId = (string) $resultRecord->sourcedGUID->sourcedId;
+        $sourcedId = htmlspecialchars_decode($sourcedId);
+        $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
+                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_WARNING)
+                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
+
+            return;
+        }
+
+        $sourcedParts = json_decode($sourcedId, true);
+
+        if (empty($sourcedParts)) {
+            $this->statusInfo
+                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_ERROR)
+                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
+
+            return;
+        }
+
+        $em = Database::getManager();
+        /** @var GradebookEvaluation $evaluation */
+        $evaluation = $em->find('ChamiloCoreBundle:GradebookEvaluation', $sourcedParts['e']);
+        /** @var User $user */
+        $user = $em->find('ChamiloUserBundle:User', $sourcedParts['u']);
+
+        if (empty($evaluation) || empty($user)) {
+            $this->statusInfo
+                ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
+                ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_FAILURE);
+
+            return;
+        }
+
+        $score = $evaluation->getMax() * $resultScore;
+
+        $results = Result::load(null, $user->getId(), $evaluation->getId());
+
+        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();
+        }
+
+        $this->statusInfo
+            ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
+            ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_SUCCESS)
+            ->setDescription(
+                sprintf(
+                    get_plugin_lang('ScoreForXUserIsYScore', 'ImsLtiPlugin'),
+                    $user->getId(),
+                    $resultScore
+                )
+            );
+    }
+}

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

@@ -0,0 +1,29 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiReplaceServiceResponse.
+ */
+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
+     */
+    protected function generateBody(SimpleXMLElement $xmlBody)
+    {
+        $xmlBody->addChild('replaceResultResponse');
+    }
+}

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

@@ -0,0 +1,82 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiServiceRequest.
+ */
+abstract class ImsLtiServiceRequest
+{
+    /**
+     * @var string
+     */
+    protected $responseType;
+
+    /**
+     * @var SimpleXMLElement
+     */
+    protected $xmlHeaderInfo;
+
+    /**
+     * @var SimpleXMLElement
+     */
+    protected $xmlRequest;
+
+    /**
+     * @var ImsLtiServiceResponseStatus
+     */
+    protected $statusInfo;
+
+    /**
+     * @var mixed
+     */
+    protected $responseBodyParam;
+
+    /**
+     * ImsLtiServiceRequest constructor.
+     *
+     * @param SimpleXMLElement $xml
+     */
+    public function __construct(SimpleXMLElement $xml)
+    {
+        $this->statusInfo = new ImsLtiServiceResponseStatus();
+
+        $this->xmlHeaderInfo = $xml->imsx_POXHeader->imsx_POXRequestHeaderInfo;
+        $this->xmlRequest = $xml->imsx_POXBody->children();
+    }
+
+    protected function processHeader()
+    {
+        $info = $this->xmlHeaderInfo;
+
+        $this->statusInfo->setMessageRefIdentifier($info->imsx_messageIdentifier);
+
+        error_log("Service Request: tool version {$info->imsx_version} message ID {$info->imsx_messageIdentifier}");
+    }
+
+    abstract protected function processBody();
+
+    /**
+     * @return ImsLtiServiceResponse|null
+     */
+    private function generateResponse()
+    {
+        $response = ImsLtiServiceResponseFactory::create(
+            $this->responseType,
+            $this->statusInfo,
+            $this->responseBodyParam
+        );
+
+        return $response;
+    }
+
+    /**
+     * @return ImsLtiServiceResponse|null
+     */
+    public function process()
+    {
+        $this->processHeader();
+        $this->processBody();
+
+        return $this->generateResponse();
+    }
+}

+ 37 - 0
plugin/ims_lti/src/ImsLtiServiceRequestFactory.php

@@ -0,0 +1,37 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiServiceRequestFactory.
+ */
+class ImsLtiServiceRequestFactory
+{
+    /**
+     * @param SimpleXMLElement $xml
+     *
+     * @return ImsLtiServiceRequest|null
+     */
+    public static function create(SimpleXMLElement $xml)
+    {
+        $bodyChildren = $xml->imsx_POXBody->children();
+
+        if (!empty($bodyChildren)) {
+            $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);
+            }
+        }
+
+        return null;
+    }
+}

+ 64 - 0
plugin/ims_lti/src/ImsLtiServiceResponse.php

@@ -0,0 +1,64 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiServiceResponse.
+ */
+abstract class ImsLtiServiceResponse
+{
+    const TYPE_REPLACE = 'replace';
+    const TYPE_READ = 'read';
+    const TYPE_DELETE = 'delete';
+
+    /**
+     * @var mixed
+     */
+    protected $bodyParams;
+    /**
+     * @var ImsLtiServiceResponseStatus
+     */
+    private $statusInfo;
+
+    /**
+     * ImsLtiServiceResponse constructor.
+     *
+     * @param ImsLtiServiceResponseStatus $statusInfo
+     * @param mixed|null                  $bodyParam
+     */
+    public function __construct(ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
+    {
+        $this->statusInfo = $statusInfo;
+        $this->bodyParams = $bodyParam;
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        $xml = new SimpleXMLElement('<imsx_POXEnvelopeResponse></imsx_POXEnvelopeResponse>');
+        $xml->addAttribute('xmlns', 'http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0');
+
+        $headerInfo = $xml->addChild('imsx_POXHeader')->addChild('imsx_POXResponseHeaderInfo');
+        $headerInfo->addChild('imsx_version', 'V1.0');
+        $headerInfo->addChild('imsx_messageIdentifier', time());
+
+        $statusInfo = $headerInfo->addChild('imsx_statusInfo');
+        $statusInfo->addChild('imsx_codeMajor', $this->statusInfo->getCodeMajor());
+        $statusInfo->addChild('imsx_severity', $this->statusInfo->getSeverity());
+        $statusInfo->addChild('imsx_description', $this->statusInfo->getDescription());
+        $statusInfo->addChild('imsx_messageRefIdentifier', $this->statusInfo->getMessageRefIdentifier());
+        $statusInfo->addChild('imsx_operationRefIdentifier', $this->statusInfo->getOperationRefIdentifier());
+
+        $body = $xml->addChild('imsx_POXBody');
+
+        $this->generateBody($body);
+
+        return $xml->asXML();
+    }
+
+    /**
+     * @param SimpleXMLElement $xmlBody
+     */
+    abstract protected function generateBody(SimpleXMLElement $xmlBody);
+}

+ 31 - 0
plugin/ims_lti/src/ImsLtiServiceResponseFactory.php

@@ -0,0 +1,31 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiServiceResponseFactory.
+ */
+class ImsLtiServiceResponseFactory
+{
+    /**
+     * @param string                      $type
+     * @param ImsLtiServiceResponseStatus $statusInfo
+     * @param mixed                       $bodyParam
+     *
+     * @return ImsLtiServiceResponse|null
+     */
+    public static function create($type, ImsLtiServiceResponseStatus $statusInfo, $bodyParam = null)
+    {
+        switch ($type) {
+            case ImsLtiServiceResponse::TYPE_REPLACE:
+                return new ImsLtiServiceReplaceResponse($statusInfo, $bodyParam);
+            case ImsLtiServiceResponse::TYPE_READ:
+                return new ImsLtiServiceReadResponse($statusInfo, $bodyParam);
+            case ImsLtiServiceResponse::TYPE_DELETE:
+                return new ImsLtiServiceDeleteResponse($statusInfo, $bodyParam);
+            default:
+                return new ImsLtiServiceUnsupportedResponse($statusInfo, $type);
+        }
+
+        return null;
+    }
+}

+ 162 - 0
plugin/ims_lti/src/ImsLtiServiceResponseStatus.php

@@ -0,0 +1,162 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiResponseStatus.
+ */
+class ImsLtiServiceResponseStatus
+{
+    const SEVERITY_STATUS = 'status';
+    const SEVERITY_WARNING = 'warning';
+    const SEVERITY_ERROR = 'error';
+
+    const CODEMAJOR_SUCCESS = 'success';
+    const CODEMAJOR_PROCESSING = 'processing';
+    const CODEMAJOR_FAILURE = 'failure';
+    const CODEMAJOR_UNSUPPORTED = 'unsupported';
+
+    /**
+     * @var string
+     */
+    private $codeMajor = '';
+
+    /**
+     * @var string
+     */
+    private $severity = '';
+
+    /**
+     * @var string
+     */
+    private $messageRefIdentifier = '';
+
+    /**
+     * @var string
+     */
+    private $operationRefIdentifier = '';
+
+    /**
+     * @var string
+     */
+    private $description = '';
+
+    /**
+     * Get codeMajor.
+     *
+     * @return string
+     */
+    public function getCodeMajor()
+    {
+        return $this->codeMajor;
+    }
+
+    /**
+     * Set codeMajor.
+     *
+     * @param string $codeMajor
+     *
+     * @return ImsLtiServiceResponseStatus
+     */
+    public function setCodeMajor($codeMajor)
+    {
+        $this->codeMajor = $codeMajor;
+
+        return $this;
+    }
+
+    /**
+     * Get severity.
+     *
+     * @return string
+     */
+    public function getSeverity()
+    {
+        return $this->severity;
+    }
+
+    /**
+     * Set severity.
+     *
+     * @param string $severity
+     *
+     * @return ImsLtiServiceResponseStatus
+     */
+    public function setSeverity($severity)
+    {
+        $this->severity = $severity;
+
+        return $this;
+    }
+
+    /**
+     * Get messageRefIdentifier.
+     *
+     * @return int
+     */
+    public function getMessageRefIdentifier()
+    {
+        return $this->messageRefIdentifier;
+    }
+
+    /**
+     * Set messageRefIdentifier.
+     *
+     * @param int $messageRefIdentifier
+     *
+     * @return ImsLtiServiceResponseStatus
+     */
+    public function setMessageRefIdentifier($messageRefIdentifier)
+    {
+        $this->messageRefIdentifier = $messageRefIdentifier;
+
+        return $this;
+    }
+
+    /**
+     * Get operationRefIdentifier.
+     *
+     * @return int
+     */
+    public function getOperationRefIdentifier()
+    {
+        return $this->operationRefIdentifier;
+    }
+
+    /**
+     * Set operationRefIdentifier.
+     *
+     * @param int $operationRefIdentifier
+     *
+     * @return ImsLtiServiceResponseStatus
+     */
+    public function setOperationRefIdentifier($operationRefIdentifier)
+    {
+        $this->operationRefIdentifier = $operationRefIdentifier;
+
+        return $this;
+    }
+
+    /**
+     * Get description.
+     *
+     * @return string
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Set description.
+     *
+     * @param string $description
+     *
+     * @return ImsLtiServiceResponseStatus
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+
+        return $this;
+    }
+}

+ 31 - 0
plugin/ims_lti/src/ImsLtiServiceUnsupportedRequest.php

@@ -0,0 +1,31 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiServiceUnsupportedRequest.
+ */
+class ImsLtiServiceUnsupportedRequest extends ImsLtiServiceRequest
+{
+    /**
+     * ImsLtiDeleteServiceRequest constructor.
+     *
+     * @param SimpleXMLElement $xml
+     * @param string           $name
+     */
+    public function __construct(SimpleXMLElement $xml, $name)
+    {
+        parent::__construct($xml);
+
+        $this->responseType = $name;
+    }
+
+    protected function processBody()
+    {
+        $this->statusInfo
+            ->setSeverity(ImsLtiServiceResponseStatus::SEVERITY_STATUS)
+            ->setCodeMajor(ImsLtiServiceResponseStatus::CODEMAJOR_UNSUPPORTED)
+            ->setDescription(
+                $this->responseType.' is not supported'
+            );
+    }
+}

+ 28 - 0
plugin/ims_lti/src/ImsLtiServiceUnsupportedResponse.php

@@ -0,0 +1,28 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Class ImsLtiServiceUnsupportedResponse.
+ */
+class ImsLtiServiceUnsupportedResponse extends ImsLtiServiceResponse
+{
+    /**
+     * ImsLtiServiceUnsupportedResponse constructor.
+     *
+     * @param ImsLtiServiceResponseStatus $statusInfo
+     * @param string                      $type
+     */
+    public function __construct(ImsLtiServiceResponseStatus $statusInfo, $type)
+    {
+        $statusInfo->setOperationRefIdentifier($type);
+
+        parent::__construct($statusInfo);
+    }
+
+    /**
+     * @param SimpleXMLElement $xmlBody
+     */
+    protected function generateBody(SimpleXMLElement $xmlBody)
+    {
+    }
+}

BIN
plugin/notebookteacher/resources/img/32/notebookteacher_na.png


+ 28 - 0
plugin/studentfollowup/demo_content.php

@@ -0,0 +1,28 @@
+<?php
+
+exit;
+
+require_once __DIR__.'/../../main/inc/global.inc.php';
+
+$em = Database::getManager();
+$insertUserInfo = api_get_user_entity(api_get_user_id());
+$userInfo = api_get_user_entity(16);
+
+$title = uniqid('title', true);
+
+$post = new \Chamilo\PluginBundle\Entity\StudentFollowUp\CarePost();
+$post
+    ->setTitle($title)
+    ->setContent($title)
+    ->setExternalCareId(2)
+    ->setCreatedAt(new DateTime())
+    ->setUpdatedAt(new DateTime())
+    ->setPrivate(false)
+    ->setInsertUser($insertUserInfo)
+    ->setExternalSource(0)
+    //->setParent($parent)
+    ->setTags(['php', 'react'])
+    ->setUser($userInfo)
+;
+$em->persist($post);
+$em->flush();

+ 219 - 0
src/Chamilo/CourseBundle/Entity/CExerciseCategory.php

@@ -0,0 +1,219 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\CourseBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use Gedmo\Mapping\Annotation as Gedmo;
+
+/**
+ * CExerciseCategory.
+ *
+ * @ORM\Table(name="c_exercise_category")
+ * ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
+ */
+class CExerciseCategory
+{
+    /**
+     * @var int
+     *
+     * @ORM\Column(name="id", type="bigint")
+     * @ORM\Id
+     * @ORM\GeneratedValue
+     */
+    protected $id;
+
+    /**
+     * @var int
+     *
+     * @Gedmo\SortableGroup
+     * @ORM\ManyToOne(targetEntity="Chamilo\CourseBundle\Entity\CExerciseCategory", inversedBy="children")
+     * @ORM\JoinColumn(referencedColumnName="id", onDelete="SET NULL")
+     *
+     * @ORM\Column(name="c_id", type="integer")
+     */
+    protected $cId;
+
+    /**
+     * @var string
+     *
+     * @ORM\Column(name="name", type="string", length=255, nullable=false)
+     */
+    protected $name;
+
+    /**
+     * @var string
+     *
+     * @ORM\Column(name="description", type="text", nullable=true)
+     */
+    protected $description;
+
+    /**
+     * @Gedmo\SortablePosition
+     * @ORM\Column(name="position", type="integer")
+     */
+    protected $position;
+
+    /**
+     * @var \DateTime
+     *
+     * @ORM\Column(name="created_at", type="datetime", nullable=false)
+     */
+    protected $createdAt;
+
+    /**
+     * @var \DateTime
+     *
+     * @ORM\Column(name="updated_at", type="datetime", nullable=false)
+     */
+    protected $updatedAt;
+
+    /**
+     * Project constructor.
+     */
+    public function __construct()
+    {
+        $this->createdAt = new \DateTime();
+        $this->updatedAt = new \DateTime();
+    }
+
+    /**
+     * @return int
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * @param int $id
+     *
+     * @return CExerciseCategory
+     */
+    public function setId($id)
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    /**
+     * @return int
+     */
+    public function getCId()
+    {
+        return $this->cId;
+    }
+
+    /**
+     * @param int $cId
+     *
+     * @return CExerciseCategory
+     */
+    public function setCId($cId)
+    {
+        $this->cId = $cId;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return CExerciseCategory
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * @param string $description
+     *
+     * @return CExerciseCategory
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+
+        return $this;
+    }
+
+    /**
+     * @return \DateTime
+     */
+    public function getCreatedAt()
+    {
+        return $this->createdAt;
+    }
+
+    /**
+     * @param \DateTime $createdAt
+     *
+     * @return CExerciseCategory
+     */
+    public function setCreatedAt($createdAt)
+    {
+        $this->createdAt = $createdAt;
+
+        return $this;
+    }
+
+    /**
+     * @return \DateTime
+     */
+    public function getUpdatedAt()
+    {
+        return $this->updatedAt;
+    }
+
+    /**
+     * @param \DateTime $updatedAt
+     *
+     * @return CExerciseCategory
+     */
+    public function setUpdatedAt($updatedAt)
+    {
+        $this->updatedAt = $updatedAt;
+
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getPosition()
+    {
+        return $this->position;
+    }
+
+    /**
+     * @param mixed $position
+     *
+     * @return CExerciseCategory
+     */
+    public function setPosition($position)
+    {
+        $this->position = $position;
+
+        return $this;
+    }
+}

+ 201 - 0
tests/datafiller/users_import_big_example.csv

@@ -0,0 +1,201 @@
+LastName;FirstName;Email;UserName;Password;AuthSource;OfficialCode;PhoneNumber;Status;Courses
+student1;student1;student1@example.com;student1;student1;platform;12345678;001-123-456-789;user;TEMP
+student2;student2;student2@example.com;student2;student2;platform;;;user;TEMP
+student3;student3;student3@example.com;student3;student3;platform;;;user;TEMP
+student4;student4;student4@example.com;student4;student4;platform;;;user;TEMP
+student5;student5;student5@example.com;student5;student5;platform;;;user;TEMP
+student6;student6;student6@example.com;student6;student6;platform;;;user;TEMP
+student7;student7;student7@example.com;student7;student7;platform;;;user;TEMP
+student8;student8;student8@example.com;student8;student8;platform;;;user;TEMP
+student9;student9;student9@example.com;student9;student9;platform;;;user;TEMP
+student10;student10;student10@example.com;student10;student10;platform;;;user;TEMP
+student11;student11;student11@example.com;student11;student11;platform;;;user;TEMP
+student12;student12;student12@example.com;student12;student12;platform;;;user;TEMP
+student13;student13;student13@example.com;student13;student13;platform;;;user;TEMP
+student14;student14;student14@example.com;student14;student14;platform;;;user;TEMP
+student15;student15;student15@example.com;student15;student15;platform;;;user;TEMP
+student16;student16;student16@example.com;student16;student16;platform;;;user;TEMP
+student17;student17;student17@example.com;student17;student17;platform;;;user;TEMP
+student18;student18;student18@example.com;student18;student18;platform;;;user;TEMP
+student19;student19;student19@example.com;student19;student19;platform;;;user;TEMP
+student20;student20;student20@example.com;student20;student20;platform;;;user;TEMP
+student21;student21;student21@example.com;student21;student21;platform;;;user;TEMP
+student22;student22;student22@example.com;student22;student22;platform;;;user;TEMP
+student23;student23;student23@example.com;student23;student23;platform;;;user;TEMP
+student24;student24;student24@example.com;student24;student24;platform;;;user;TEMP
+student25;student25;student25@example.com;student25;student25;platform;;;user;TEMP
+student26;student26;student26@example.com;student26;student26;platform;;;user;TEMP
+student27;student27;student27@example.com;student27;student27;platform;;;user;TEMP
+student28;student28;student28@example.com;student28;student28;platform;;;user;TEMP
+student29;student29;student29@example.com;student29;student29;platform;;;user;TEMP
+student30;student30;student30@example.com;student30;student30;platform;;;user;TEMP
+student31;student31;student31@example.com;student31;student31;platform;;;user;TEMP
+student32;student32;student32@example.com;student32;student32;platform;;;user;TEMP
+student33;student33;student33@example.com;student33;student33;platform;;;user;TEMP
+student34;student34;student34@example.com;student34;student34;platform;;;user;TEMP
+student35;student35;student35@example.com;student35;student35;platform;;;user;TEMP
+student36;student36;student36@example.com;student36;student36;platform;;;user;TEMP
+student37;student37;student37@example.com;student37;student37;platform;;;user;TEMP
+student38;student38;student38@example.com;student38;student38;platform;;;user;TEMP
+student39;student39;student39@example.com;student39;student39;platform;;;user;TEMP
+student40;student40;student40@example.com;student40;student40;platform;;;user;TEMP
+student41;student41;student41@example.com;student41;student41;platform;;;user;TEMP
+student42;student42;student42@example.com;student42;student42;platform;;;user;TEMP
+student43;student43;student43@example.com;student43;student43;platform;;;user;TEMP
+student44;student44;student44@example.com;student44;student44;platform;;;user;TEMP
+student45;student45;student45@example.com;student45;student45;platform;;;user;TEMP
+student46;student46;student46@example.com;student46;student46;platform;;;user;TEMP
+student47;student47;student47@example.com;student47;student47;platform;;;user;TEMP
+student48;student48;student48@example.com;student48;student48;platform;;;user;TEMP
+student49;student49;student49@example.com;student49;student49;platform;;;user;TEMP
+student50;student50;student50@example.com;student50;student50;platform;;;user;TEMP
+student51;student51;student51@example.com;student51;student51;platform;;;user;TEMP
+student52;student52;student52@example.com;student52;student52;platform;;;user;TEMP
+student53;student53;student53@example.com;student53;student53;platform;;;user;TEMP
+student54;student54;student54@example.com;student54;student54;platform;;;user;TEMP
+student55;student55;student55@example.com;student55;student55;platform;;;user;TEMP
+student56;student56;student56@example.com;student56;student56;platform;;;user;TEMP
+student57;student57;student57@example.com;student57;student57;platform;;;user;TEMP
+student58;student58;student58@example.com;student58;student58;platform;;;user;TEMP
+student59;student59;student59@example.com;student59;student59;platform;;;user;TEMP
+student60;student60;student60@example.com;student60;student60;platform;;;user;TEMP
+student61;student61;student61@example.com;student61;student61;platform;;;user;TEMP
+student62;student62;student62@example.com;student62;student62;platform;;;user;TEMP
+student63;student63;student63@example.com;student63;student63;platform;;;user;TEMP
+student64;student64;student64@example.com;student64;student64;platform;;;user;TEMP
+student65;student65;student65@example.com;student65;student65;platform;;;user;TEMP
+student66;student66;student66@example.com;student66;student66;platform;;;user;TEMP
+student67;student67;student67@example.com;student67;student67;platform;;;user;TEMP
+student68;student68;student68@example.com;student68;student68;platform;;;user;TEMP
+student69;student69;student69@example.com;student69;student69;platform;;;user;TEMP
+student70;student70;student70@example.com;student70;student70;platform;;;user;TEMP
+student71;student71;student71@example.com;student71;student71;platform;;;user;TEMP
+student72;student72;student72@example.com;student72;student72;platform;;;user;TEMP
+student73;student73;student73@example.com;student73;student73;platform;;;user;TEMP
+student74;student74;student74@example.com;student74;student74;platform;;;user;TEMP
+student75;student75;student75@example.com;student75;student75;platform;;;user;TEMP
+student76;student76;student76@example.com;student76;student76;platform;;;user;TEMP
+student77;student77;student77@example.com;student77;student77;platform;;;user;TEMP
+student78;student78;student78@example.com;student78;student78;platform;;;user;TEMP
+student79;student79;student79@example.com;student79;student79;platform;;;user;TEMP
+student80;student80;student80@example.com;student80;student80;platform;;;user;TEMP
+student81;student81;student81@example.com;student81;student81;platform;;;user;TEMP
+student82;student82;student82@example.com;student82;student82;platform;;;user;TEMP
+student83;student83;student83@example.com;student83;student83;platform;;;user;TEMP
+student84;student84;student84@example.com;student84;student84;platform;;;user;TEMP
+student85;student85;student85@example.com;student85;student85;platform;;;user;TEMP
+student86;student86;student86@example.com;student86;student86;platform;;;user;TEMP
+student87;student87;student87@example.com;student87;student87;platform;;;user;TEMP
+student88;student88;student88@example.com;student88;student88;platform;;;user;TEMP
+student89;student89;student89@example.com;student89;student89;platform;;;user;TEMP
+student90;student90;student90@example.com;student90;student90;platform;;;user;TEMP
+student91;student91;student91@example.com;student91;student91;platform;;;user;TEMP
+student92;student92;student92@example.com;student92;student92;platform;;;user;TEMP
+student93;student93;student93@example.com;student93;student93;platform;;;user;TEMP
+student94;student94;student94@example.com;student94;student94;platform;;;user;TEMP
+student95;student95;student95@example.com;student95;student95;platform;;;user;TEMP
+student96;student96;student96@example.com;student96;student96;platform;;;user;TEMP
+student97;student97;student97@example.com;student97;student97;platform;;;user;TEMP
+student98;student98;student98@example.com;student98;student98;platform;;;user;TEMP
+student99;student99;student99@example.com;student99;student99;platform;;;user;TEMP
+student100;student100;student100@example.com;student100;student100;platform;;;user;TEMP
+student101;student101;student101@example.com;student101;student101;platform;;;user;TEMP
+student102;student102;student102@example.com;student102;student102;platform;;;user;TEMP
+student103;student103;student103@example.com;student103;student103;platform;;;user;TEMP
+student104;student104;student104@example.com;student104;student104;platform;;;user;TEMP
+student105;student105;student105@example.com;student105;student105;platform;;;user;TEMP
+student106;student106;student106@example.com;student106;student106;platform;;;user;TEMP
+student107;student107;student107@example.com;student107;student107;platform;;;user;TEMP
+student108;student108;student108@example.com;student108;student108;platform;;;user;TEMP
+student109;student109;student109@example.com;student109;student109;platform;;;user;TEMP
+student110;student110;student110@example.com;student110;student110;platform;;;user;TEMP
+student111;student111;student111@example.com;student111;student111;platform;;;user;TEMP
+student112;student112;student112@example.com;student112;student112;platform;;;user;TEMP
+student113;student113;student113@example.com;student113;student113;platform;;;user;TEMP
+student114;student114;student114@example.com;student114;student114;platform;;;user;TEMP
+student115;student115;student115@example.com;student115;student115;platform;;;user;TEMP
+student116;student116;student116@example.com;student116;student116;platform;;;user;TEMP
+student117;student117;student117@example.com;student117;student117;platform;;;user;TEMP
+student118;student118;student118@example.com;student118;student118;platform;;;user;TEMP
+student119;student119;student119@example.com;student119;student119;platform;;;user;TEMP
+student120;student120;student120@example.com;student120;student120;platform;;;user;TEMP
+student121;student121;student121@example.com;student121;student121;platform;;;user;TEMP
+student122;student122;student122@example.com;student122;student122;platform;;;user;TEMP
+student123;student123;student123@example.com;student123;student123;platform;;;user;TEMP
+student124;student124;student124@example.com;student124;student124;platform;;;user;TEMP
+student125;student125;student125@example.com;student125;student125;platform;;;user;TEMP
+student126;student126;student126@example.com;student126;student126;platform;;;user;TEMP
+student127;student127;student127@example.com;student127;student127;platform;;;user;TEMP
+student128;student128;student128@example.com;student128;student128;platform;;;user;TEMP
+student129;student129;student129@example.com;student129;student129;platform;;;user;TEMP
+student130;student130;student130@example.com;student130;student130;platform;;;user;TEMP
+student131;student131;student131@example.com;student131;student131;platform;;;user;TEMP
+student132;student132;student132@example.com;student132;student132;platform;;;user;TEMP
+student133;student133;student133@example.com;student133;student133;platform;;;user;TEMP
+student134;student134;student134@example.com;student134;student134;platform;;;user;TEMP
+student135;student135;student135@example.com;student135;student135;platform;;;user;TEMP
+student136;student136;student136@example.com;student136;student136;platform;;;user;TEMP
+student137;student137;student137@example.com;student137;student137;platform;;;user;TEMP
+student138;student138;student138@example.com;student138;student138;platform;;;user;TEMP
+student139;student139;student139@example.com;student139;student139;platform;;;user;TEMP
+student140;student140;student140@example.com;student140;student140;platform;;;user;TEMP
+student141;student141;student141@example.com;student141;student141;platform;;;user;TEMP
+student142;student142;student142@example.com;student142;student142;platform;;;user;TEMP
+student143;student143;student143@example.com;student143;student143;platform;;;user;TEMP
+student144;student144;student144@example.com;student144;student144;platform;;;user;TEMP
+student145;student145;student145@example.com;student145;student145;platform;;;user;TEMP
+student146;student146;student146@example.com;student146;student146;platform;;;user;TEMP
+student147;student147;student147@example.com;student147;student147;platform;;;user;TEMP
+student148;student148;student148@example.com;student148;student148;platform;;;user;TEMP
+student149;student149;student149@example.com;student149;student149;platform;;;user;TEMP
+student150;student150;student150@example.com;student150;student150;platform;;;user;TEMP
+student151;student151;student151@example.com;student151;student151;platform;;;user;TEMP
+student152;student152;student152@example.com;student152;student152;platform;;;user;TEMP
+student153;student153;student153@example.com;student153;student153;platform;;;user;TEMP
+student154;student154;student154@example.com;student154;student154;platform;;;user;TEMP
+student155;student155;student155@example.com;student155;student155;platform;;;user;TEMP
+student156;student156;student156@example.com;student156;student156;platform;;;user;TEMP
+student157;student157;student157@example.com;student157;student157;platform;;;user;TEMP
+student158;student158;student158@example.com;student158;student158;platform;;;user;TEMP
+student159;student159;student159@example.com;student159;student159;platform;;;user;TEMP
+student160;student160;student160@example.com;student160;student160;platform;;;user;TEMP
+student161;student161;student161@example.com;student161;student161;platform;;;user;TEMP
+student162;student162;student162@example.com;student162;student162;platform;;;user;TEMP
+student163;student163;student163@example.com;student163;student163;platform;;;user;TEMP
+student164;student164;student164@example.com;student164;student164;platform;;;user;TEMP
+student165;student165;student165@example.com;student165;student165;platform;;;user;TEMP
+student166;student166;student166@example.com;student166;student166;platform;;;user;TEMP
+student167;student167;student167@example.com;student167;student167;platform;;;user;TEMP
+student168;student168;student168@example.com;student168;student168;platform;;;user;TEMP
+student169;student169;student169@example.com;student169;student169;platform;;;user;TEMP
+student170;student170;student170@example.com;student170;student170;platform;;;user;TEMP
+student171;student171;student171@example.com;student171;student171;platform;;;user;TEMP
+student172;student172;student172@example.com;student172;student172;platform;;;user;TEMP
+student173;student173;student173@example.com;student173;student173;platform;;;user;TEMP
+student174;student174;student174@example.com;student174;student174;platform;;;user;TEMP
+student175;student175;student175@example.com;student175;student175;platform;;;user;TEMP
+student176;student176;student176@example.com;student176;student176;platform;;;user;TEMP
+student177;student177;student177@example.com;student177;student177;platform;;;user;TEMP
+student178;student178;student178@example.com;student178;student178;platform;;;user;TEMP
+student179;student179;student179@example.com;student179;student179;platform;;;user;TEMP
+student180;student180;student180@example.com;student180;student180;platform;;;user;TEMP
+student181;student181;student181@example.com;student181;student181;platform;;;user;TEMP
+student182;student182;student182@example.com;student182;student182;platform;;;user;TEMP
+student183;student183;student183@example.com;student183;student183;platform;;;user;TEMP
+student184;student184;student184@example.com;student184;student184;platform;;;user;TEMP
+student185;student185;student185@example.com;student185;student185;platform;;;user;TEMP
+student186;student186;student186@example.com;student186;student186;platform;;;user;TEMP
+student187;student187;student187@example.com;student187;student187;platform;;;user;TEMP
+student188;student188;student188@example.com;student188;student188;platform;;;user;TEMP
+student189;student189;student189@example.com;student189;student189;platform;;;user;TEMP
+student190;student190;student190@example.com;student190;student190;platform;;;user;TEMP
+student191;student191;student191@example.com;student191;student191;platform;;;user;TEMP
+student192;student192;student192@example.com;student192;student192;platform;;;user;TEMP
+student193;student193;student193@example.com;student193;student193;platform;;;user;TEMP
+student194;student194;student194@example.com;student194;student194;platform;;;user;TEMP
+student195;student195;student195@example.com;student195;student195;platform;;;user;TEMP
+student196;student196;student196@example.com;student196;student196;platform;;;user;TEMP
+student197;student197;student197@example.com;student197;student197;platform;;;user;TEMP
+student198;student198;student198@example.com;student198;student198;platform;;;user;TEMP
+student199;student199;student199@example.com;student199;student199;platform;;;user;TEMP
+student200;student200;student200@example.com;student200;student200;platform;;;user;TEMP