From d456d1b8bcb0d7de4c9a08377ed0d9db256ff9b5 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Tue, 25 Sep 2018 13:59:40 -0500
Subject: [PATCH 01/32] GraphQL use mutation with graphql language #2644

---
 config/packages/graphql.yaml                  |   2 +
 src/ApiBundle/GraphQL/ApiGraphQLTrait.php     |  28 -----
 .../GraphQL/Map/MutationResolverMap.php       | 115 ++++++++++++++++++
 .../GraphQL/Mutation/RootMutation.php         |  49 --------
 .../GraphQL/Mutation/UserMutation.php         |  88 --------------
 .../Resources/config/Mutation.types.yaml      |  45 -------
 .../Resources/config/schema.types.graphql     |  25 ++++
 7 files changed, 142 insertions(+), 210 deletions(-)
 create mode 100644 src/ApiBundle/GraphQL/Map/MutationResolverMap.php
 delete mode 100644 src/ApiBundle/GraphQL/Mutation/RootMutation.php
 delete mode 100644 src/ApiBundle/GraphQL/Mutation/UserMutation.php
 delete mode 100644 src/ApiBundle/GraphQL/Resources/config/Mutation.types.yaml

diff --git a/config/packages/graphql.yaml b/config/packages/graphql.yaml
index dba0135782..e0cad8fe12 100644
--- a/config/packages/graphql.yaml
+++ b/config/packages/graphql.yaml
@@ -2,11 +2,13 @@ overblog_graphql:
     definitions:
         schema:
             query: Query
+            mutation: Mutation
             resolver_maps:
                 - Chamilo\ApiBundle\GraphQL\Map\RootResolverMap
                 - Chamilo\ApiBundle\GraphQL\Map\UserResolverMap
                 - Chamilo\ApiBundle\GraphQL\Map\EnumResolverMap
                 - Chamilo\ApiBundle\GraphQL\Map\ScalarResolverMap
+                - Chamilo\ApiBundle\GraphQL\Map\MutationResolverMap
         mappings:
             types:
                 -
diff --git a/src/ApiBundle/GraphQL/ApiGraphQLTrait.php b/src/ApiBundle/GraphQL/ApiGraphQLTrait.php
index cce7abe9a1..1cfa5be013 100644
--- a/src/ApiBundle/GraphQL/ApiGraphQLTrait.php
+++ b/src/ApiBundle/GraphQL/ApiGraphQLTrait.php
@@ -80,34 +80,6 @@ trait ApiGraphQLTrait
         $this->container->get('session')->set('_security_main', serialize($token));
     }
 
-    /**
-     * @param string $username
-     * @param string $password
-     *
-     * @return string
-     */
-    private function getUserToken($username, $password): string
-    {
-        /** @var User $user */
-        $user = $this->em->getRepository('ChamiloUserBundle:User')->findOneBy(['username' => $username]);
-
-        if (!$user) {
-            throw new UserError($this->translator->trans('NoUser'));
-        }
-
-        $encoder = $this->container->get('security.password_encoder');
-        $isValid = $encoder->isPasswordValid(
-            $user,
-            $password
-        );
-
-        if (!$isValid) {
-            throw new UserError($this->translator->trans('InvalidId'));
-        }
-
-        return self::encodeToken($user);
-    }
-
     /**
      * @param User $user
      *
diff --git a/src/ApiBundle/GraphQL/Map/MutationResolverMap.php b/src/ApiBundle/GraphQL/Map/MutationResolverMap.php
new file mode 100644
index 0000000000..eca09b52ca
--- /dev/null
+++ b/src/ApiBundle/GraphQL/Map/MutationResolverMap.php
@@ -0,0 +1,115 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ApiBundle\GraphQL\Map;
+
+use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
+use Chamilo\UserBundle\Entity\User;
+use GraphQL\Type\Definition\ResolveInfo;
+use Overblog\GraphQLBundle\Definition\Argument;
+use Overblog\GraphQLBundle\Error\UserError;
+use Overblog\GraphQLBundle\Resolver\ResolverMap;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+
+/**
+ * Class MutationResolverMap.
+ *
+ * @package Chamilo\ApiBundle\GraphQL\Map
+ */
+class MutationResolverMap extends ResolverMap implements ContainerAwareInterface
+{
+    use ApiGraphQLTrait;
+
+    /**
+     * @return array
+     */
+    protected function map()
+    {
+        return [
+            'Mutation' => [
+                self::RESOLVE_FIELD => function ($value, Argument $args, \ArrayObject $context, ResolveInfo $info) {
+                    $method = 'resolve'.ucfirst($info->fieldName);
+
+                    return $this->$method($args, $context);
+                },
+            ],
+        ];
+    }
+
+    /**
+     * @param Argument $args
+     *
+     * @return array
+     */
+    protected function resolveAuthenticate(Argument $args)
+    {
+        /** @var User $user */
+        $user = $this->em->getRepository('ChamiloUserBundle:User')->findOneBy(['username' => $args['username']]);
+
+        if (!$user) {
+            throw new UserError($this->translator->trans('User not found.'));
+        }
+
+        $encoder = $this->container->get('security.password_encoder');
+        $isValid = $encoder->isPasswordValid($user, $args['password']);
+
+        if (!$isValid) {
+            throw new UserError($this->translator->trans('Password is not valid.'));
+        }
+
+        return [
+            'token' => $this->encodeToken($user),
+        ];
+    }
+
+    /**
+     * @param Argument $args
+     *
+     * @return array
+     */
+    protected function resolveViewerSendMessage(Argument $args)
+    {
+        $this->checkAuthorization();
+
+        $currentUser = $this->getCurrentUser();
+        $usersRepo = $this->em->getRepository('ChamiloUserBundle:User');
+        $users = $usersRepo->findUsersToSendMessage($currentUser->getId());
+        $receivers = array_filter(
+            $args['receivers'],
+            function ($receiverId) use ($users) {
+                /** @var User $user */
+                foreach ($users as $user) {
+                    if ($user->getId() === (int) $receiverId) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        );
+
+        $result = [];
+
+        foreach ($receivers as $receiverId) {
+            $messageId = \MessageManager::send_message(
+                $receiverId,
+                $args['subject'],
+                $args['text'],
+                [],
+                [],
+                0,
+                0,
+                0,
+                0,
+                $currentUser->getId()
+            );
+
+            $result[] = [
+                'receiverId' => $receiverId,
+                'sent' => (bool) $messageId,
+            ];
+        }
+
+        return $result;
+    }
+}
diff --git a/src/ApiBundle/GraphQL/Mutation/RootMutation.php b/src/ApiBundle/GraphQL/Mutation/RootMutation.php
deleted file mode 100644
index 76ddbe125e..0000000000
--- a/src/ApiBundle/GraphQL/Mutation/RootMutation.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Mutation;
-
-use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
-use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
-use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-
-/**
- * Class RootMutation.
- *
- * @package Chamilo\ApiBundle\GraphQL\Mutation
- */
-class RootMutation implements MutationInterface, AliasedInterface, ContainerAwareInterface
-{
-    use ApiGraphQLTrait;
-
-    /**
-     * Returns methods aliases.
-     *
-     * For instance:
-     * array('myMethod' => 'myAlias')
-     *
-     * @return array
-     */
-    public static function getAliases()
-    {
-        return [
-            'mutationAuthenticate' => 'authenticate',
-        ];
-    }
-
-    /**
-     * @param Argument $args
-     *
-     * @return array
-     */
-    public function mutationAuthenticate(Argument $args)
-    {
-        $token = $this->getUserToken($args['username'], $args['password']);
-
-        return [
-            'token' => $token,
-        ];
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Mutation/UserMutation.php b/src/ApiBundle/GraphQL/Mutation/UserMutation.php
deleted file mode 100644
index 59a1707cde..0000000000
--- a/src/ApiBundle/GraphQL/Mutation/UserMutation.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Mutation;
-
-use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
-use Chamilo\UserBundle\Entity\User;
-use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
-use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-
-/**
- * Class UserMutation.
- *
- * @package Chamilo\ApiBundle\GraphQL\Mutation
- */
-class UserMutation implements MutationInterface, AliasedInterface, ContainerAwareInterface
-{
-    use ApiGraphQLTrait;
-
-    /**
-     * Returns methods aliases.
-     *
-     * For instance:
-     * array('myMethod' => 'myAlias')
-     *
-     * @return array
-     */
-    public static function getAliases()
-    {
-        return [
-            'mutateSendMessage' => 'user_send_message',
-        ];
-    }
-
-    /**
-     * @param Argument $args
-     *
-     * @return array
-     */
-    public function mutateSendMessage(Argument $args): array
-    {
-        $this->checkAuthorization();
-
-        $currentUser = $this->getCurrentUser();
-        $usersRepo = $this->em->getRepository('ChamiloUserBundle:User');
-        $users = $usersRepo->findUsersToSendMessage($currentUser->getId());
-        $result = [];
-
-        foreach ($args['receivers'] as $receiverId) {
-            $sentMessage = false;
-
-            /** @var User $user */
-            foreach ($users as $user) {
-                if ((int) $receiverId === $user->getId()) {
-                    $sentMessage = true;
-                }
-            }
-
-            $item = [
-                'receiverId' => $receiverId,
-                'sent' => false,
-            ];
-
-            if ($sentMessage) {
-                $messageId = \MessageManager::send_message(
-                    $receiverId,
-                    $args['subject'],
-                    $args['text'],
-                    [],
-                    [],
-                    0,
-                    0,
-                    0,
-                    0,
-                    $currentUser->getId()
-                );
-
-                $item['sent'] = (bool) $messageId;
-            }
-
-            $result[] = $item;
-        }
-
-        return $result;
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Resources/config/Mutation.types.yaml b/src/ApiBundle/GraphQL/Resources/config/Mutation.types.yaml
deleted file mode 100644
index 197d59e03f..0000000000
--- a/src/ApiBundle/GraphQL/Resources/config/Mutation.types.yaml
+++ /dev/null
@@ -1,45 +0,0 @@
-Mutation:
-    type: object
-    config:
-        fields:
-            authenticate:
-                description: "Authenticate user."
-                type: "AuthenticatePayload!"
-                args:
-                    username:
-                        type: "String!"
-                    password:
-                        type: "String!"
-                resolve: "@=mutation('authenticate', [args])"
-            viewerSendMessage:
-                description: 'Send messages to user contacts.'
-                type: '[ViewerSendMessagePayload]!'
-                args:
-                    receivers:
-                        description: 'Unique IDs of users who will receive the message.'
-                        type: '[Int]!'
-                    subject:
-                        type: 'String!'
-                    text:
-                        type: 'String!'
-                resolve: "@=mutation('user_send_message', [args, context])"
-
-AuthenticatePayload:
-    type: object
-    config:
-        fields:
-            token:
-                description: "Authorization token."
-                type: "String!"
-
-ViewerSendMessagePayload:
-    type: object
-    config:
-        description: 'The status for each message in queue.'
-        fields:
-            receiverId:
-                description: 'The unique ID for the receiver user.'
-                type: 'Int!'
-            sent:
-                description: "It indicates if the message was or wasn't sent."
-                type: 'Boolean!'
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index e60deddd3d..44e7a0a4d3 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -9,8 +9,33 @@ type Query {
     sessionCategory(id: Int!): SessionCategory!
 }
 
+type Mutation {
+    "Authenticate user."
+    authenticate(username: String!, password: String!): AuthenticatePayload!
+    "Send messages to user contacts."
+    viewerSendMessage(
+        "Unique IDs of users who will receive the message."
+        receivers: [Int!]!,
+        subject: String!,
+        content: String!
+    ): [ViewerSendMessagePayload!]
+}
+
 # Objects
 
+type AuthenticatePayload {
+    "Authorization token."
+    token: String!
+}
+
+"The status for each message in queue."
+type ViewerSendMessagePayload {
+    "The unique ID for the receiver user."
+    receiverId: Int!
+    "It indicates if the message was or wasn't sent."
+    sent: Boolean!
+}
+
 "A registered user on the platform."
 type User {
     "The unique ID of the user."

From cce857b58b1a7612c9cd8573422e2720ce92f7fd Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Tue, 25 Sep 2018 18:35:00 -0500
Subject: [PATCH 02/32] GraphQL WIP changing resolvers #2644

---
 config/packages/graphql.yaml                  |  16 +-
 config/services.yaml                          |   3 -
 .../Map/{EnumResolverMap.php => EnumMap.php}  |   6 +-
 ...utationResolverMap.php => MutationMap.php} |   2 +-
 .../Map/{RootResolverMap.php => RootMap.php}  | 166 +++++++++++-------
 .../{ScalarResolverMap.php => ScalarMap.php}  |   2 +-
 .../Map/{UserResolverMap.php => UserMap.php}  |   2 +-
 .../Resolver/CourseDescriptionResolver.php    |  54 ------
 .../GraphQL/Resolver/CourseResolver.php       |   2 +-
 .../GraphQL/Resolver/CourseToolResolver.php   |  59 -------
 .../Resolver/CourseToolResolverTrait.php      |  51 ------
 .../Resolver/ToolAnnouncementsResolver.php    |   9 +-
 .../Resolver/ToolDescriptionResolver.php      |  12 +-
 .../GraphQL/Resources/config/Enum.types.yaml  |   9 -
 .../GraphQL/Resources/config/Query.types.yaml |  89 ----------
 .../Resources/config/schema.types.graphql     |  50 ++++++
 src/CoreBundle/Entity/Course.php              |  18 +-
 17 files changed, 196 insertions(+), 354 deletions(-)
 rename src/ApiBundle/GraphQL/Map/{EnumResolverMap.php => EnumMap.php} (81%)
 rename src/ApiBundle/GraphQL/Map/{MutationResolverMap.php => MutationMap.php} (97%)
 rename src/ApiBundle/GraphQL/Map/{RootResolverMap.php => RootMap.php} (60%)
 rename src/ApiBundle/GraphQL/Map/{ScalarResolverMap.php => ScalarMap.php} (94%)
 rename src/ApiBundle/GraphQL/Map/{UserResolverMap.php => UserMap.php} (99%)
 delete mode 100644 src/ApiBundle/GraphQL/Resolver/CourseDescriptionResolver.php
 delete mode 100644 src/ApiBundle/GraphQL/Resolver/CourseToolResolver.php
 delete mode 100644 src/ApiBundle/GraphQL/Resolver/CourseToolResolverTrait.php
 delete mode 100644 src/ApiBundle/GraphQL/Resources/config/Enum.types.yaml
 delete mode 100644 src/ApiBundle/GraphQL/Resources/config/Query.types.yaml

diff --git a/config/packages/graphql.yaml b/config/packages/graphql.yaml
index e0cad8fe12..0af277df22 100644
--- a/config/packages/graphql.yaml
+++ b/config/packages/graphql.yaml
@@ -4,13 +4,19 @@ overblog_graphql:
             query: Query
             mutation: Mutation
             resolver_maps:
-                - Chamilo\ApiBundle\GraphQL\Map\RootResolverMap
-                - Chamilo\ApiBundle\GraphQL\Map\UserResolverMap
-                - Chamilo\ApiBundle\GraphQL\Map\EnumResolverMap
-                - Chamilo\ApiBundle\GraphQL\Map\ScalarResolverMap
-                - Chamilo\ApiBundle\GraphQL\Map\MutationResolverMap
+                - Chamilo\ApiBundle\GraphQL\Map\RootMap
+                - Chamilo\ApiBundle\GraphQL\Map\UserMap
+                - Chamilo\ApiBundle\GraphQL\Map\EnumMap
+                - Chamilo\ApiBundle\GraphQL\Map\ScalarMap
+                - Chamilo\ApiBundle\GraphQL\Map\MutationMap
         mappings:
             types:
                 -
                     type: graphql
                     dir: "%kernel.root_dir%/ApiBundle/GraphQL/Resources/config"
+
+services:
+    Chamilo\ApiBundle\GraphQL\:
+        resource: "../../src/ApiBundle/GraphQL/*"
+        arguments:
+            - "@service_container"
diff --git a/config/services.yaml b/config/services.yaml
index 44d76c1992..4bef19c1fa 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -41,9 +41,6 @@ services:
         arguments:
             $debug: '%kernel.debug%'
 
-    Chamilo\ApiBundle\GraphQL\:
-        resource: "../src/ApiBundle/GraphQL/*"
-
     Doctrine\ORM\EntityManager: "@doctrine.orm.default_entity_manager"
 
 sylius_settings:
diff --git a/src/ApiBundle/GraphQL/Map/EnumResolverMap.php b/src/ApiBundle/GraphQL/Map/EnumMap.php
similarity index 81%
rename from src/ApiBundle/GraphQL/Map/EnumResolverMap.php
rename to src/ApiBundle/GraphQL/Map/EnumMap.php
index d38b39e077..d683a3b5d4 100644
--- a/src/ApiBundle/GraphQL/Map/EnumResolverMap.php
+++ b/src/ApiBundle/GraphQL/Map/EnumMap.php
@@ -13,7 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  *
  * @package Chamilo\ApiBundle\GraphQL\Map
  */
-class EnumResolverMap extends ResolverMap implements ContainerAwareInterface
+class EnumMap extends ResolverMap implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
@@ -37,6 +37,10 @@ class EnumResolverMap extends ResolverMap implements ContainerAwareInterface
                 'SIZE_BIG' => ICON_SIZE_BIG,
                 'SIZE_HUGE' => ICON_SIZE_HUGE,
             ],
+            'CourseToolType' => [
+                'TOOL_COURSE_DESCRIPTION' => TOOL_COURSE_DESCRIPTION,
+                'TOOL_ANNOUNCEMENT' => TOOL_ANNOUNCEMENT,
+            ],
         ];
     }
 }
diff --git a/src/ApiBundle/GraphQL/Map/MutationResolverMap.php b/src/ApiBundle/GraphQL/Map/MutationMap.php
similarity index 97%
rename from src/ApiBundle/GraphQL/Map/MutationResolverMap.php
rename to src/ApiBundle/GraphQL/Map/MutationMap.php
index eca09b52ca..e38b3a1448 100644
--- a/src/ApiBundle/GraphQL/Map/MutationResolverMap.php
+++ b/src/ApiBundle/GraphQL/Map/MutationMap.php
@@ -16,7 +16,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  *
  * @package Chamilo\ApiBundle\GraphQL\Map
  */
-class MutationResolverMap extends ResolverMap implements ContainerAwareInterface
+class MutationMap extends ResolverMap implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
diff --git a/src/ApiBundle/GraphQL/Map/RootResolverMap.php b/src/ApiBundle/GraphQL/Map/RootMap.php
similarity index 60%
rename from src/ApiBundle/GraphQL/Map/RootResolverMap.php
rename to src/ApiBundle/GraphQL/Map/RootMap.php
index 7c7cc9cb9a..f17d87cb0d 100644
--- a/src/ApiBundle/GraphQL/Map/RootResolverMap.php
+++ b/src/ApiBundle/GraphQL/Map/RootMap.php
@@ -4,6 +4,7 @@
 namespace Chamilo\ApiBundle\GraphQL\Map;
 
 use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
+use Chamilo\ApiBundle\GraphQL\Resolver\ToolDescriptionResolver;
 use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\Message;
 use Chamilo\CoreBundle\Entity\Session;
@@ -12,6 +13,7 @@ use Chamilo\CoreBundle\Entity\SessionRelCourse;
 use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
 use Chamilo\CoreBundle\Security\Authorization\Voter\SessionVoter;
+use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\UserBundle\Entity\User;
 use GraphQL\Type\Definition\ResolveInfo;
 use Overblog\GraphQLBundle\Definition\Argument;
@@ -23,7 +25,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  * Class RootResolverMap
  * @package Chamilo\ApiBundle\GraphQL\Map
  */
-class RootResolverMap extends ResolverMap implements ContainerAwareInterface
+class RootMap extends ResolverMap implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
@@ -35,16 +37,12 @@ class RootResolverMap extends ResolverMap implements ContainerAwareInterface
         return [
             'Query' => [
                 self::RESOLVE_FIELD => function ($value, Argument $args, \ArrayObject $context, ResolveInfo $info) {
-                    switch ($info->fieldName) {
-                        case 'viewer':
-                            return $this->resolveViewer();
-                        case 'course':
-                            return $this->resolveCourse($args['id']);
-                        case 'session':
-                            $this->resolveSession($args['id']);
-                        case 'sessionCategory':
-                            return $this->resolveSessionCategory($args['id']);
-                    }
+                    $context->offsetSet('course', null);
+                    $context->offsetSet('session', null);
+
+                    $method = 'resolve'.ucfirst($info->fieldName);
+
+                    return $this->$method($args, $context);
                 },
             ],
             'UserMessage' => [
@@ -77,55 +75,86 @@ class RootResolverMap extends ResolverMap implements ContainerAwareInterface
                 },
             ],
             'Course' => [
-                self::RESOLVE_FIELD => function (
-                    Course $course,
-                    Argument $args,
-                    \ArrayObject $context,
-                    ResolveInfo $info
-                ) {
-                    $context->offsetSet('course', $course);
-
-                    switch ($info->fieldName) {
-                        case 'picture':
-                            return \CourseManager::getPicturePath($course, $args['fullSize']);
-                        case 'teachers':
-                            if ($context->offsetExists('session')) {
-                                /** @var Session $session */
-                                $session = $context->offsetGet('session');
-
-                                if ($session) {
-                                    $coaches = [];
-                                    $coachSubscriptions = $session->getUserCourseSubscriptionsByStatus(
-                                        $course,
-                                        Session::COACH
-                                    );
-
-                                    /** @var SessionRelCourseRelUser $coachSubscription */
-                                    foreach ($coachSubscriptions as $coachSubscription) {
-                                        $coaches[] = $coachSubscription->getUser();
-                                    }
-
-                                    return $coaches;
-                                }
+                'picture' => function (Course $course, Argument $args, \ArrayObject $context, ResolveInfo $info) {
+                    return \CourseManager::getPicturePath($course, $args['fullSize']);
+                },
+                'teachers' => function (Course $course, Argument $args, \ArrayObject $context, ResolveInfo $info) {
+                    if ($context->offsetExists('session')) {
+                        /** @var Session $session */
+                        $session = $context->offsetGet('session');
+
+                        if ($session) {
+                            $coaches = [];
+                            $coachSubscriptions = $session->getUserCourseSubscriptionsByStatus(
+                                $course,
+                                Session::COACH
+                            );
+
+                            /** @var SessionRelCourseRelUser $coachSubscription */
+                            foreach ($coachSubscriptions as $coachSubscription) {
+                                $coaches[] = $coachSubscription->getUser();
                             }
 
-                            $courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
-                            $teachers = $courseRepo
-                                ->getSubscribedTeachers($course)
-                                ->getQuery()
-                                ->getResult();
+                            return $coaches;
+                        }
+                    }
 
-                            return $teachers;
-                        default:
-                            $method = 'get'.ucfirst($info->fieldName);
+                    $courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
+                    $teachers = $courseRepo
+                        ->getSubscribedTeachers($course)
+                        ->getQuery()
+                        ->getResult();
 
-                            if (method_exists($course, $method)) {
-                                return $course->$method();
-                            }
+                    return $teachers;
+                },
+                'tools' => function (Course $course, Argument $args, \ArrayObject $context, ResolveInfo $info) {
+                    $session = null;
 
-                            return null;
+                    if ($context->offsetExists('session')) {
+                        /** @var Session $session */
+                        $session = $context->offsetGet('session');
                     }
-                }
+
+                    $tools = $course->getTools($session);
+
+                    if (!isset($args['type'])) {
+                        return $tools;
+                    }
+
+                    return $tools->filter(
+                        function (CTool $tool) use ($args) {
+                            if ($tool->getName() === $args['type']) {
+                                return true;
+                            }
+                        }
+                    );
+                },
+            ],
+            'CourseTool' => [
+                self::RESOLVE_TYPE => function (CTool $tool) {
+                    switch ($tool->getName()) {
+                        case TOOL_COURSE_DESCRIPTION:
+                            return 'ToolDescription';
+                        case TOOL_ANNOUNCEMENT:
+                        default:
+                            return 'ToolAnnouncements';
+                    }
+                },
+            ],
+            'ToolDescription' => [
+                'descriptions' => function (CTool $tool, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('Chamilo\ApiBundle\GraphQL\Resolver\ToolDescriptionResolver');
+
+                    return $resolver->getDescriptions($tool, $context);
+                },
+            ],
+            //'CourseDescription' => [],
+            'ToolAnnouncements' => [
+                'announcements' => function (CTool $tool, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('Chamilo\ApiBundle\GraphQL\Resolver\ToolAnnouncementsResolver');
+
+                    return $resolver ? $resolver->getAnnouncements($tool, $context) : [];
+                },
             ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
@@ -203,7 +232,7 @@ class RootResolverMap extends ResolverMap implements ContainerAwareInterface
     /**
      * @return User
      */
-    private function resolveViewer()
+    protected function resolveViewer()
     {
         $this->checkAuthorization();
 
@@ -211,16 +240,20 @@ class RootResolverMap extends ResolverMap implements ContainerAwareInterface
     }
 
     /**
-     * @param int $id
+     * @param Argument     $args
+     *
+     * @param \ArrayObject $context
      *
      * @return Course
      */
-    private function resolveCourse($id)
+    protected function resolveCourse(Argument $args, \ArrayObject $context)
     {
         $this->checkAuthorization();
 
+        $id = (int) $args['id'];
+
         $courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
-        $course = $courseRepo->find((int) $id);
+        $course = $courseRepo->find($id);
 
         if (!$course) {
             throw new UserError($this->translator->trans('Course not found.'));
@@ -232,41 +265,46 @@ class RootResolverMap extends ResolverMap implements ContainerAwareInterface
             throw new UserError($this->translator->trans('Not allowed'));
         }
 
+        $context->offsetSet('course', $course);
+
         return $course;
     }
 
     /**
-     * @param int $id
+     * @param Argument     $args
+     * @param \ArrayObject $context
      *
      * @return Session
      */
-    private function resolveSession($id)
+    protected function resolveSession(Argument $args, \ArrayObject $context)
     {
         $this->checkAuthorization();
 
         $sessionRepo = $this->em->getRepository('ChamiloCoreBundle:Session');
         /** @var Session $session */
-        $session = $sessionRepo->find((int) $id);
+        $session = $sessionRepo->find($args['id']);
 
         if (!$session) {
             throw new UserError($this->translator->trans('Session not found.'));
         }
 
+        $context->offsetSet('course', $session);
+
         return $session;
     }
 
     /**
-     * @param int $id
+     * @param Argument $args
      *
      * @return SessionCategory
      */
-    private function resolveSessionCategory($id)
+    protected function resolveSessionCategory(Argument $args)
     {
         $this->checkAuthorization();
 
         $repo = $this->em->getRepository('ChamiloCoreBundle:SessionCategory');
         /** @var SessionCategory $category */
-        $category = $repo->find((int) $id);
+        $category = $repo->find($args['id']);
 
         if (!$category) {
             throw new UserError($this->translator->trans('Session category not found.'));
diff --git a/src/ApiBundle/GraphQL/Map/ScalarResolverMap.php b/src/ApiBundle/GraphQL/Map/ScalarMap.php
similarity index 94%
rename from src/ApiBundle/GraphQL/Map/ScalarResolverMap.php
rename to src/ApiBundle/GraphQL/Map/ScalarMap.php
index df59b1d8bc..af00d7db4c 100644
--- a/src/ApiBundle/GraphQL/Map/ScalarResolverMap.php
+++ b/src/ApiBundle/GraphQL/Map/ScalarMap.php
@@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  *
  * @package Chamilo\ApiBundle\GraphQL\Map
  */
-class ScalarResolverMap extends ResolverMap implements ContainerAwareInterface
+class ScalarMap extends ResolverMap implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
diff --git a/src/ApiBundle/GraphQL/Map/UserResolverMap.php b/src/ApiBundle/GraphQL/Map/UserMap.php
similarity index 99%
rename from src/ApiBundle/GraphQL/Map/UserResolverMap.php
rename to src/ApiBundle/GraphQL/Map/UserMap.php
index 72baafaa57..aefd457ed0 100644
--- a/src/ApiBundle/GraphQL/Map/UserResolverMap.php
+++ b/src/ApiBundle/GraphQL/Map/UserMap.php
@@ -16,7 +16,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  *
  * @package Chamilo\ApiBundle\GraphQL\Map
  */
-class UserResolverMap extends ResolverMap implements ContainerAwareInterface
+class UserMap extends ResolverMap implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseDescriptionResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseDescriptionResolver.php
deleted file mode 100644
index a61ae4c479..0000000000
--- a/src/ApiBundle/GraphQL/Resolver/CourseDescriptionResolver.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Resolver;
-
-use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
-use Chamilo\CourseBundle\Entity\CCourseDescription;
-use GraphQL\Type\Definition\ResolveInfo;
-use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-
-/**
- * Class CourseDescriptionResolver.
- *
- * @package Chamilo\ApiBundle\GraphQL\Resolver
- */
-class CourseDescriptionResolver implements ResolverInterface, ContainerAwareInterface
-{
-    use ApiGraphQLTrait;
-
-    /**
-     * @param CCourseDescription $description
-     * @param Argument           $args
-     * @param ResolveInfo        $info
-     * @param \ArrayObject       $context
-     */
-    public function __invoke(CCourseDescription $description, Argument $args, ResolveInfo $info, \ArrayObject $context)
-    {
-        $method = 'resolve'.ucfirst($info->fieldName);
-
-        if (method_exists($this, $method)) {
-            return $this->$method($description, $args, $context);
-        }
-
-        $method = 'get'.ucfirst($info->fieldName);
-
-        if (method_exists($description, $method)) {
-            return $description->$method();
-        }
-
-        return null;
-    }
-
-    /**
-     * @param CCourseDescription $description
-     *
-     * @return int
-     */
-    public function resolveType(CCourseDescription $description)
-    {
-        return $description->getDescriptionType();
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index d1b8184cb8..567e59917b 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -28,7 +28,7 @@ class CourseResolver implements ResolverInterface, ContainerAwareInterface
      *
      * @return array
      */
-    public function resolveTools(Course $course, Argument $args, \ArrayObject $context)
+    public function getTools(Course $course, Argument $args, \ArrayObject $context)
     {
         $sessionId = 0;
 
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseToolResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseToolResolver.php
deleted file mode 100644
index 5dab76a8c0..0000000000
--- a/src/ApiBundle/GraphQL/Resolver/CourseToolResolver.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Resolver;
-
-use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
-use Chamilo\CourseBundle\Entity\CTool;
-use Doctrine\ORM\EntityManager;
-use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
-use Overblog\GraphQLBundle\Resolver\TypeResolver;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-use Symfony\Component\Translation\TranslatorInterface;
-
-/**
- * Class CourseToolResolver.
- *
- * @package Chamilo\ApiBundle\GraphQL\Resolver
- */
-class CourseToolResolver implements ResolverInterface, ContainerAwareInterface
-{
-    use ApiGraphQLTrait;
-
-    /**
-     * @var TypeResolver
-     */
-    private $typeResolver;
-
-    /**
-     * CourseToolResolver constructor.
-     *
-     * @param EntityManager       $entityManager
-     * @param TranslatorInterface $translator
-     * @param TypeResolver        $typeResolver
-     */
-    public function __construct(
-        EntityManager $entityManager,
-        TranslatorInterface $translator,
-        TypeResolver $typeResolver
-    ) {
-        $this->em = $entityManager;
-        $this->translator = $translator;
-        $this->typeResolver = $typeResolver;
-    }
-
-    /**
-     * @param CTool $tool
-     *
-     * @return \GraphQL\Type\Definition\Type
-     */
-    public function __invoke(CTool $tool)
-    {
-        switch ($tool->getName()) {
-            case TOOL_COURSE_DESCRIPTION:
-                return $this->typeResolver->resolve('ToolDescription');
-            case TOOL_ANNOUNCEMENT:
-                return $this->typeResolver->resolve('ToolAnnouncements');
-        }
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseToolResolverTrait.php b/src/ApiBundle/GraphQL/Resolver/CourseToolResolverTrait.php
deleted file mode 100644
index 2c85bd8cf0..0000000000
--- a/src/ApiBundle/GraphQL/Resolver/CourseToolResolverTrait.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Resolver;
-
-use Chamilo\CourseBundle\Entity\CTool;
-use GraphQL\Type\Definition\ResolveInfo;
-use Overblog\GraphQLBundle\Definition\Argument;
-
-/**
- * Trait CourseToolResolverTrait.
- *
- * @package Chamilo\ApiBundle\GraphQL\Resolver
- */
-trait CourseToolResolverTrait
-{
-    /**
-     * @param CTool        $tool
-     * @param Argument     $args
-     * @param ResolveInfo  $info
-     * @param \ArrayObject $context
-     *
-     * @return mixed
-     */
-    public function __invoke(CTool $tool, Argument $args, ResolveInfo $info, \ArrayObject $context)
-    {
-        $method = 'resolve'.ucfirst($info->fieldName);
-
-        if (method_exists($this, $method)) {
-            return $this->$method($tool, $args, $context);
-        }
-
-        $method = 'get'.ucfirst($info->fieldName);
-
-        if (method_exists($tool, $method)) {
-            return $tool->$method();
-        }
-
-        return null;
-    }
-
-    /**
-     * @param CTool $tool
-     *
-     * @return bool
-     */
-    public function resolveIsVisible(CTool $tool): bool
-    {
-        return (bool) $tool->getVisibility();
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php b/src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php
index 02f8c42f55..0a6fa6fd30 100644
--- a/src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php
@@ -8,28 +8,25 @@ use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CourseBundle\Entity\CTool;
 use GraphQL\Error\UserError;
-use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Class ToolAnnouncementsResolver.
  *
  * @package Chamilo\ApiBundle\GraphQL\Resolver
  */
-class ToolAnnouncementsResolver implements ResolverInterface, ContainerAwareInterface
+class ToolAnnouncementsResolver implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
-    use CourseToolResolverTrait;
 
     /**
      * @param CTool        $tool
-     * @param Argument     $args
      * @param \ArrayObject $context
      *
      * @return array
      */
-    public function resolveAnnouncements(CTool $tool, Argument $args, \ArrayObject $context): array
+    public function getAnnouncements(CTool $tool, \ArrayObject $context): array
     {
         /** @var Course $course */
         $course = $context->offsetGet('course');
diff --git a/src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php b/src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php
index af07b3ee52..2be65f7921 100644
--- a/src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php
@@ -6,8 +6,6 @@ namespace Chamilo\ApiBundle\GraphQL\Resolver;
 use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
 use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CourseBundle\Entity\CTool;
-use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 
 /**
@@ -15,19 +13,17 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  *
  * @package Chamilo\ApiBundle\GraphQL\Resolver
  */
-class ToolDescriptionResolver implements ResolverInterface, ContainerAwareInterface
+class ToolDescriptionResolver implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
-    use CourseToolResolverTrait;
 
     /**
      * @param CTool        $tool
-     * @param Argument     $args
      * @param \ArrayObject $context
      *
      * @return array
      */
-    public function resolveDescriptions(CTool $tool, Argument $args, \ArrayObject $context): array
+    public function getDescriptions(CTool $tool, \ArrayObject $context): array
     {
         /** @var Course $course */
         $course = $context->offsetGet('course');
@@ -38,7 +34,9 @@ class ToolDescriptionResolver implements ResolverInterface, ContainerAwareInterf
             /** @var Session $session */
             $session = $context->offsetGet('session');
 
-            $cd->set_session_id($session->getId());
+            if ($session) {
+                $cd->set_session_id($session->getId());
+            }
         }
 
         $descriptions = $cd->get_description_data();
diff --git a/src/ApiBundle/GraphQL/Resources/config/Enum.types.yaml b/src/ApiBundle/GraphQL/Resources/config/Enum.types.yaml
deleted file mode 100644
index 166a75a47f..0000000000
--- a/src/ApiBundle/GraphQL/Resources/config/Enum.types.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-CourseToolType:
-    type: enum
-    config:
-        description: 'One of the types of course tool'
-        values:
-            TOOL_DESCRIPTION:
-                value: '@=constant("TOOL_COURSE_DESCRIPTION")'
-            TOOL_ANNOUNCEMENT:
-                value: '@=constant("TOOL_ANNOUNCEMENT")'
diff --git a/src/ApiBundle/GraphQL/Resources/config/Query.types.yaml b/src/ApiBundle/GraphQL/Resources/config/Query.types.yaml
deleted file mode 100644
index e64d17d1bb..0000000000
--- a/src/ApiBundle/GraphQL/Resources/config/Query.types.yaml
+++ /dev/null
@@ -1,89 +0,0 @@
-Course:
-    type: object
-    config:
-        description: 'A course registered on the platform.'
-        fields:
-            tools:
-                description: 'List of available tools for student view.'
-                type: '[CourseTool]'
-                args:
-                    type:
-                        description: 'Filter by one type of tool'
-                        type: 'CourseToolType'
-
-CourseTool:
-    type: union
-    config:
-        description: 'Course tools'
-        resolveType: '@=resolver("Chamilo\\ApiBundle\\GraphQL\\Resolver\\CourseToolResolver", [value])'
-        types: [ToolDescription, ToolAnnouncements]
-
-ToolDescription:
-    type: object
-    config:
-        description: 'Global summary of the course.'
-        resolveField: '@=resolver("Chamilo\\ApiBundle\\GraphQL\\Resolver\\ToolDescriptionResolver", [value, args, info, context])'
-        fields:
-            name:
-                type: 'String'
-            category:
-                type: 'String'
-            image:
-                type: 'String'
-            customIcon:
-                type: 'String'
-            isVisible:
-                type: 'Boolean'
-            descriptions:
-                type: '[CourseDescription]!'
-
-CourseDescription:
-    type: object
-    config:
-        description: 'A section for the course description.'
-        resolveField: '@=resolver("Chamilo\\ApiBundle\\GraphQL\\Resolver\\CourseDescriptionResolver", [value, args, info, context])'
-        fields:
-            id:
-                type: 'Int'
-            title:
-                type: 'String'
-            content:
-                type: 'String'
-            type:
-                type: 'Int'
-
-ToolAnnouncements:
-    type: object
-    config:
-        description: 'Announcements related to the course.'
-        resolveField: '@=resolver("Chamilo\\ApiBundle\\GraphQL\\Resolver\\ToolAnnouncementsResolver", [value, args, info, context])'
-        fields:
-            name:
-                type: 'String'
-            category:
-                type: 'String'
-            image:
-                type: 'String'
-            customIcon:
-                type: 'String'
-            isVisible:
-                type: 'Boolean'
-            announcements:
-                type: '[CourseAnnouncement]!'
-
-CourseAnnouncement:
-    type: object
-    config:
-        description: 'Course announcement.'
-        resolveField: '@=resolver("Chamilo\\ApiBundle\\GraphQL\\Resolver\\CourseAnnouncementResolver", [value, args, info, context])'
-        fields:
-            id:
-                type: 'Int'
-            title:
-                type: 'String'
-            content:
-                type: 'String'
-            by:
-                type: 'User'
-            lastUpdateDate:
-                type: 'DateTime'
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index 44e7a0a4d3..32886c84b5 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -92,6 +92,46 @@ type Course {
     ): String
     "Teachers list in course. Or tutors list from course in session."
     teachers: [User!]
+    "List of available tools for student view."
+    tools(
+        "Filter by one type of tool"
+        type: CourseToolType
+    ): [CourseTool!]
+}
+
+"Global summary of the course."
+type ToolDescription {
+    name: String
+    category: String
+    image: String
+    customIcon: String
+    descriptions: [CourseDescription!]
+}
+
+"A section for the course description."
+type CourseDescription {
+    id: Int
+    title: String
+    content: String
+    descriptionType: Int
+}
+
+"Announcements related to the course."
+type ToolAnnouncements {
+    name: String
+    category: String
+    image: String
+    customIcon: String
+    announcements: [CourseAnnouncement!]
+}
+
+"A course announcement."
+type CourseAnnouncement {
+    id: Int
+    title: String
+    content: String
+    author: User!
+    lastUpdateDate: DateTime
 }
 
 "A session registered on the platform."
@@ -123,6 +163,10 @@ type SessionCategory {
     endDate: DateTime
 }
 
+# Unions
+
+union CourseTool = ToolDescription | ToolAnnouncements
+
 # Enums
 
 "One of the statuses for the user."
@@ -153,6 +197,12 @@ enum ImageSize {
     SIZE_HUGE
 }
 
+"One of the types of course tool"
+enum CourseToolType {
+    TOOL_COURSE_DESCRIPTION
+    TOOL_ANNOUNCEMENT
+}
+
 # Scalars
 
 scalar DateTime
diff --git a/src/CoreBundle/Entity/Course.php b/src/CoreBundle/Entity/Course.php
index e36fb52aed..aa322d5e08 100644
--- a/src/CoreBundle/Entity/Course.php
+++ b/src/CoreBundle/Entity/Course.php
@@ -92,6 +92,8 @@ class Course
     //protected $items;
 
     /**
+     * @var ArrayCollection
+     *
      * @ORM\OneToMany(targetEntity="Chamilo\CourseBundle\Entity\CTool", mappedBy="course", cascade={"persist", "remove"})
      */
     protected $tools;
@@ -383,11 +385,23 @@ class Course
     }
 
     /**
+     * @param Session $session
+     *
      * @return ArrayCollection
      */
-    public function getTools()
+    public function getTools(Session $session = null)
     {
-        return $this->tools;
+        $orWhere = Criteria::expr()->eq('sessionId', 0);
+
+        if ($session) {
+            $orWhere = Criteria::expr()->in('sessionId', [0, $session->getId()]);
+        }
+
+        $criteria = Criteria::create()
+            ->where(Criteria::expr()->isNull('sessionId'))
+            ->orWhere($orWhere);
+
+        return $this->tools->matching($criteria);
     }
 
     /**

From 08a22d8509e3d024452da2bab0c30487f08a8c4d Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Wed, 26 Sep 2018 14:00:41 -0500
Subject: [PATCH 03/32] Allow upload xlsx files to import exercise - refs
 BT#14750

---
 main/exercise/upload_exercise.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/main/exercise/upload_exercise.php b/main/exercise/upload_exercise.php
index ada84bf2da..8f075cde41 100755
--- a/main/exercise/upload_exercise.php
+++ b/main/exercise/upload_exercise.php
@@ -140,7 +140,7 @@ function lp_upload_quiz_action_handling()
     $path_info = pathinfo($_FILES['user_upload_quiz']['name']);
 
     // Check if the document is an Excel document
-    if ($path_info['extension'] != 'xls') {
+    if (!in_array($path_info['extension'], ['xls', 'xlsx'])) {
         return;
     }
 

From a93c9e9024760faf42569d34e1e0661ee1ba83aa Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Wed, 26 Sep 2018 15:48:34 -0500
Subject: [PATCH 04/32] GraphQL improving resolvers and mapping #2644

Continue from cce857b58b1a7612c9cd8573422e2720ce92f7fd
---
 config/packages/graphql.yaml                  |  30 +-
 src/ApiBundle/GraphQL/Map/EnumMap.php         |  12 +-
 src/ApiBundle/GraphQL/Map/RootMap.php         | 261 ++++++++----------
 src/ApiBundle/GraphQL/Map/UnionMap.php        |  39 +++
 .../Resolver/CourseAnnouncementResolver.php   |  88 ------
 .../GraphQL/Resolver/CourseResolver.php       | 151 ++++++++--
 .../GraphQL/Resolver/SessionResolver.php      |  78 ++++++
 .../Resolver/ToolAnnouncementsResolver.php    |  70 -----
 .../Resolver/ToolDescriptionResolver.php      |  58 ----
 .../UserMap.php => Resolver/UserResolver.php} |  99 ++-----
 .../Resources/config/schema.types.graphql     |  14 +-
 src/CoreBundle/Entity/Message.php             |  16 ++
 .../Entity/Manager/AnnouncementManager.php    |   2 -
 13 files changed, 438 insertions(+), 480 deletions(-)
 create mode 100644 src/ApiBundle/GraphQL/Map/UnionMap.php
 delete mode 100644 src/ApiBundle/GraphQL/Resolver/CourseAnnouncementResolver.php
 create mode 100644 src/ApiBundle/GraphQL/Resolver/SessionResolver.php
 delete mode 100644 src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php
 delete mode 100644 src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php
 rename src/ApiBundle/GraphQL/{Map/UserMap.php => Resolver/UserResolver.php} (74%)

diff --git a/config/packages/graphql.yaml b/config/packages/graphql.yaml
index 0af277df22..1003df0de9 100644
--- a/config/packages/graphql.yaml
+++ b/config/packages/graphql.yaml
@@ -4,18 +4,38 @@ overblog_graphql:
             query: Query
             mutation: Mutation
             resolver_maps:
-                - Chamilo\ApiBundle\GraphQL\Map\RootMap
-                - Chamilo\ApiBundle\GraphQL\Map\UserMap
-                - Chamilo\ApiBundle\GraphQL\Map\EnumMap
-                - Chamilo\ApiBundle\GraphQL\Map\ScalarMap
-                - Chamilo\ApiBundle\GraphQL\Map\MutationMap
+                - '%chamilo_api.graphql.resolver_map.root.class%'
+                - '%chamilo_api.graphql.resolver_map.enum.class%'
+                - '%chamilo_api.graphql.resolver_map.union.class%'
+                - '%chamilo_api.graphql.resolver_map.scalar.class%'
+                - '%chamilo_api.graphql.resolver_map.mutation.class%'
         mappings:
             types:
                 -
                     type: graphql
                     dir: "%kernel.root_dir%/ApiBundle/GraphQL/Resources/config"
 
+parameters:
+    chamilo_api.graphql.resolver_map.root.class: Chamilo\ApiBundle\GraphQL\Map\RootMap
+    chamilo_api.graphql.resolver_map.enum.class: Chamilo\ApiBundle\GraphQL\Map\EnumMap
+    chamilo_api.graphql.resolver_map.union.class: Chamilo\ApiBundle\GraphQL\Map\UnionMap
+    chamilo_api.graphql.resolver_map.scalar.class: Chamilo\ApiBundle\GraphQL\Map\ScalarMap
+    chamilo_api.graphql.resolver_map.mutation.class: Chamilo\ApiBundle\GraphQL\Map\MutationMap
+
 services:
+
+    chamilo_api.graphql.resolver.user:
+        class: Chamilo\ApiBundle\GraphQL\Resolver\UserResolver
+        arguments: [ '@service_container' ]
+
+    chamilo_api.graphql.resolver.course:
+        class: Chamilo\ApiBundle\GraphQL\Resolver\CourseResolver
+        arguments: [ '@service_container' ]
+
+    chamilo_api.graphql.resolver.session:
+        class: Chamilo\ApiBundle\GraphQL\Resolver\SessionResolver
+        arguments: [ '@service_container' ]
+
     Chamilo\ApiBundle\GraphQL\:
         resource: "../../src/ApiBundle/GraphQL/*"
         arguments:
diff --git a/src/ApiBundle/GraphQL/Map/EnumMap.php b/src/ApiBundle/GraphQL/Map/EnumMap.php
index d683a3b5d4..18edc97800 100644
--- a/src/ApiBundle/GraphQL/Map/EnumMap.php
+++ b/src/ApiBundle/GraphQL/Map/EnumMap.php
@@ -30,12 +30,12 @@ class EnumMap extends ResolverMap implements ContainerAwareInterface
                 'STUDENT' => User::STUDENT,
             ],
             'ImageSize' => [
-                'SIZE_TINY' => ICON_SIZE_TINY,
-                'SIZE_SMALL' => ICON_SIZE_SMALL,
-                'SIZE_MEDIUM' => ICON_SIZE_MEDIUM,
-                'SIZE_LARGE' => ICON_SIZE_LARGE,
-                'SIZE_BIG' => ICON_SIZE_BIG,
-                'SIZE_HUGE' => ICON_SIZE_HUGE,
+                'ICON_SIZE_TINY' => ICON_SIZE_TINY,
+                'ICON_SIZE_SMALL' => ICON_SIZE_SMALL,
+                'ICON_SIZE_MEDIUM' => ICON_SIZE_MEDIUM,
+                'ICON_SIZE_LARGE' => ICON_SIZE_LARGE,
+                'ICON_SIZE_BIG' => ICON_SIZE_BIG,
+                'ICON_SIZE_HUGE' => ICON_SIZE_HUGE,
             ],
             'CourseToolType' => [
                 'TOOL_COURSE_DESCRIPTION' => TOOL_COURSE_DESCRIPTION,
diff --git a/src/ApiBundle/GraphQL/Map/RootMap.php b/src/ApiBundle/GraphQL/Map/RootMap.php
index f17d87cb0d..d89c9cdc27 100644
--- a/src/ApiBundle/GraphQL/Map/RootMap.php
+++ b/src/ApiBundle/GraphQL/Map/RootMap.php
@@ -9,10 +9,7 @@ use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\Message;
 use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionCategory;
-use Chamilo\CoreBundle\Entity\SessionRelCourse;
-use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
-use Chamilo\CoreBundle\Security\Authorization\Voter\SessionVoter;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\UserBundle\Entity\User;
 use GraphQL\Type\Definition\ResolveInfo;
@@ -45,117 +42,100 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
                     return $this->$method($args, $context);
                 },
             ],
-            'UserMessage' => [
+            'User' => [
                 self::RESOLVE_FIELD => function (
-                    Message $message,
+                    User $user,
                     Argument $args,
                     \ArrayObject $context,
                     ResolveInfo $info
                 ) {
-                    switch ($info->fieldName) {
-                        case 'sender':
-                            return $message->getUserSender();
-                        case 'excerpt':
-                            $striped = strip_tags($message->getContent());
-                            $replaced = str_replace(["\r\n", "\n"], ' ', $striped);
-                            $trimmed = trim($replaced);
-
-                            return api_trunc_str($trimmed, $args['length']);
-                        case 'hasAttachments':
-                            return $message->getAttachments()->count() > 0;
-                        default:
-                            $method = 'get'.ucfirst($info->fieldName);
-
-                            if (method_exists($message, $method)) {
-                                return $message->$method();
-                            }
-
-                            return null;
-                    }
-                },
-            ],
-            'Course' => [
-                'picture' => function (Course $course, Argument $args, \ArrayObject $context, ResolveInfo $info) {
-                    return \CourseManager::getPicturePath($course, $args['fullSize']);
-                },
-                'teachers' => function (Course $course, Argument $args, \ArrayObject $context, ResolveInfo $info) {
-                    if ($context->offsetExists('session')) {
-                        /** @var Session $session */
-                        $session = $context->offsetGet('session');
-
-                        if ($session) {
-                            $coaches = [];
-                            $coachSubscriptions = $session->getUserCourseSubscriptionsByStatus(
-                                $course,
-                                Session::COACH
-                            );
-
-                            /** @var SessionRelCourseRelUser $coachSubscription */
-                            foreach ($coachSubscriptions as $coachSubscription) {
-                                $coaches[] = $coachSubscription->getUser();
-                            }
-
-                            return $coaches;
-                        }
-                    }
+                    $context->offsetSet('user', $user);
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.user');
 
-                    $courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
-                    $teachers = $courseRepo
-                        ->getSubscribedTeachers($course)
-                        ->getQuery()
-                        ->getResult();
-
-                    return $teachers;
+                    return $this->resolveField($info->fieldName, $user, $resolver, $args, $context);
                 },
-                'tools' => function (Course $course, Argument $args, \ArrayObject $context, ResolveInfo $info) {
-                    $session = null;
-
-                    if ($context->offsetExists('session')) {
-                        /** @var Session $session */
-                        $session = $context->offsetGet('session');
+            ],
+            'UserMessage' => [
+                self::RESOLVE_FIELD => function (
+                    Message $message,
+                    Argument $args,
+                    \ArrayObject $context,
+                    ResolveInfo $info
+                ) {
+                    if ('sender' === $info->fieldName) {
+                        return $message->getUserSender();
                     }
 
-                    $tools = $course->getTools($session);
-
-                    if (!isset($args['type'])) {
-                        return $tools;
+                    if ('hasAttachments' === $info->fieldName) {
+                        return $message->getAttachments()->count() > 0;
                     }
 
-                    return $tools->filter(
-                        function (CTool $tool) use ($args) {
-                            if ($tool->getName() === $args['type']) {
-                                return true;
-                            }
-                        }
-                    );
+                    return $this->resolveField($info->fieldName, $message);
                 },
             ],
-            'CourseTool' => [
-                self::RESOLVE_TYPE => function (CTool $tool) {
-                    switch ($tool->getName()) {
-                        case TOOL_COURSE_DESCRIPTION:
-                            return 'ToolDescription';
-                        case TOOL_ANNOUNCEMENT:
-                        default:
-                            return 'ToolAnnouncements';
-                    }
+            'Course' => [
+                self::RESOLVE_FIELD => function (
+                    Course $course,
+                    Argument $args,
+                    \ArrayObject $context,
+                    ResolveInfo $info
+                ) {
+                    $context->offsetSet('course', $course);
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                    return $this->resolveField($info->fieldName, $course, $resolver, $args, $context);
                 },
             ],
             'ToolDescription' => [
-                'descriptions' => function (CTool $tool, Argument $args, \ArrayObject $context) {
-                    $resolver = $this->container->get('Chamilo\ApiBundle\GraphQL\Resolver\ToolDescriptionResolver');
+                self::RESOLVE_FIELD => function (
+                    CTool $tool,
+                    Argument $args,
+                    \ArrayObject $context,
+                    ResolveInfo $info
+                ) {
+                    if ('descriptions' === $info->fieldName) {
+                        $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                        return $resolver->getDescriptions($tool, $context);
+                    }
 
-                    return $resolver->getDescriptions($tool, $context);
+                    return $this->resolveField($info->fieldName, $tool);
                 },
             ],
             //'CourseDescription' => [],
             'ToolAnnouncements' => [
-                'announcements' => function (CTool $tool, Argument $args, \ArrayObject $context) {
-                    $resolver = $this->container->get('Chamilo\ApiBundle\GraphQL\Resolver\ToolAnnouncementsResolver');
+                self::RESOLVE_FIELD => function (
+                    CTool $tool,
+                    Argument $args,
+                    \ArrayObject $context,
+                    ResolveInfo $info
+                ) {
+                    if ('announcements' === $info->fieldName) {
+                        $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
 
-                    return $resolver ? $resolver->getAnnouncements($tool, $context) : [];
+                        return $resolver->getAnnouncements($tool, $context);
+                    }
+
+                    return $this->resolveField($info->fieldName, $tool);
                 },
             ],
+            'CourseAnnouncement' => [
+                'content' => function(\stdClass $announcement, Argument $args, \ArrayObject $context) {
+                    /** @var User $reader */
+                    $reader = $context->offsetGet('user');
+                    /** @var Course $course */
+                    $course = $context->offsetGet('course');
+                    /** @var Session $session */
+                    $session = $context->offsetGet('session');
+
+                    return \AnnouncementManager::parseContent(
+                        $reader->getId(),
+                        $announcement->content,
+                        $course->getCode(),
+                        $session ? $session->getId() : 0
+                    );
+                }
+            ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
                     Session $session,
@@ -164,43 +144,9 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
                     ResolveInfo $info
                 ) {
                     $context->offsetSet('session', $session);
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.session');
 
-                    switch ($info->fieldName) {
-                        case 'description':
-                            if (false === $session->getShowDescription()) {
-                                return '';
-                            }
-
-                            return $session->getDescription();
-                        case 'numberOfUsers':
-                            return $session->getNbrUsers();
-                        case 'numberOfCourses':
-                            return $session->getNbrCourses();
-                        case 'courses':
-                            $authChecker = $this->container->get('security.authorization_checker');
-                            $courses = [];
-
-                            /** @var SessionRelCourse $sessionCourse */
-                            foreach ($session->getCourses() as $sessionCourse) {
-                                $course = $sessionCourse->getCourse();
-
-                                $session->setCurrentCourse($course);
-
-                                if (false !== $authChecker->isGranted(SessionVoter::VIEW, $session)) {
-                                    $courses[] = $course;
-                                }
-                            }
-
-                            return $courses;
-                        default:
-                            $method = 'get'.ucfirst($info->fieldName);
-
-                            if (method_exists($session, $method)) {
-                                return $session->$method();
-                            }
-
-                            return null;
-                    }
+                    return $this->resolveField($info->fieldName, $session, $resolver, $args, $context);
                 },
             ],
             'SessionCategory' => [
@@ -210,25 +156,45 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
                     \ArrayObject $context,
                     ResolveInfo $info
                 ) {
-                    switch ($info->fieldName) {
-                        case 'startDate':
-                            return $category->getDateStart();
-                        case 'endDate':
-                            return $category->getDateEnd();
-                        default:
-                            $method = 'get'.ucfirst($info->fieldName);
-
-                            if (method_exists($category, $method)) {
-                                return $category->$method();
-                            }
-
-                            return null;
+                    if ('startDate' === $info->fieldName) {
+                        return $category->getDateStart();
                     }
-                }
+
+                    if ('endDate' === $info->fieldName) {
+                        return $category->getDateEnd();
+                    }
+
+                    return $this->resolveField($info->fieldName, $category);
+                },
             ],
         ];
     }
 
+    /**
+     * @param string            $fieldName
+     * @param object            $object
+     * @param object|null       $resolver
+     * @param Argument|null     $args
+     * @param \ArrayObject|null $context
+     *
+     * @return mixed
+     */
+    private function resolveField(
+        $fieldName,
+        $object,
+        $resolver = null,
+        Argument $args = null,
+        \ArrayObject $context = null
+    ) {
+        $method = 'get'.ucfirst($fieldName);
+
+        if ($resolver && $args && $context && method_exists($resolver, $method)) {
+            return $resolver->$method($object, $args, $context);
+        }
+
+        return $object->$method();
+    }
+
     /**
      * @return User
      */
@@ -240,13 +206,11 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
     }
 
     /**
-     * @param Argument     $args
-     *
-     * @param \ArrayObject $context
+     * @param Argument $args
      *
      * @return Course
      */
-    protected function resolveCourse(Argument $args, \ArrayObject $context)
+    protected function resolveCourse(Argument $args)
     {
         $this->checkAuthorization();
 
@@ -265,18 +229,15 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
             throw new UserError($this->translator->trans('Not allowed'));
         }
 
-        $context->offsetSet('course', $course);
-
         return $course;
     }
 
     /**
-     * @param Argument     $args
-     * @param \ArrayObject $context
+     * @param Argument $args
      *
      * @return Session
      */
-    protected function resolveSession(Argument $args, \ArrayObject $context)
+    protected function resolveSession(Argument $args)
     {
         $this->checkAuthorization();
 
@@ -288,8 +249,6 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
             throw new UserError($this->translator->trans('Session not found.'));
         }
 
-        $context->offsetSet('course', $session);
-
         return $session;
     }
 
diff --git a/src/ApiBundle/GraphQL/Map/UnionMap.php b/src/ApiBundle/GraphQL/Map/UnionMap.php
new file mode 100644
index 0000000000..0cefb546f5
--- /dev/null
+++ b/src/ApiBundle/GraphQL/Map/UnionMap.php
@@ -0,0 +1,39 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ApiBundle\GraphQL\Map;
+
+use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
+use Chamilo\CourseBundle\Entity\CTool;
+use Overblog\GraphQLBundle\Resolver\ResolverMap;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+
+/**
+ * Class UnionMap.
+ *
+ * @package Chamilo\ApiBundle\GraphQL\Map
+ */
+class UnionMap extends ResolverMap implements ContainerAwareInterface
+{
+    use ApiGraphQLTrait;
+
+    /**
+     * @return array
+     */
+    protected function map()
+    {
+        return [
+            'CourseTool' => [
+                self::RESOLVE_TYPE => function (CTool $tool) {
+                    switch ($tool->getName()) {
+                        case TOOL_COURSE_DESCRIPTION:
+                            return 'ToolDescription';
+                        case TOOL_ANNOUNCEMENT:
+                        default:
+                            return 'ToolAnnouncements';
+                    }
+                },
+            ],
+        ];
+    }
+}
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseAnnouncementResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseAnnouncementResolver.php
deleted file mode 100644
index 4c35b232cc..0000000000
--- a/src/ApiBundle/GraphQL/Resolver/CourseAnnouncementResolver.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Resolver;
-
-use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
-use Chamilo\CourseBundle\Entity\CAnnouncement;
-use Chamilo\CourseBundle\Entity\CItemProperty;
-use Chamilo\UserBundle\Entity\User;
-use GraphQL\Type\Definition\ResolveInfo;
-use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-
-/**
- * Class CourseAnnouncementResolver.
- *
- * @package Chamilo\ApiBundle\GraphQL\Resolver
- */
-class CourseAnnouncementResolver implements ResolverInterface, ContainerAwareInterface
-{
-    use ApiGraphQLTrait;
-
-    /**
-     * @param array        $item
-     * @param Argument     $args
-     * @param ResolveInfo  $info
-     * @param \ArrayObject $context
-     *
-     * @return mixed
-     */
-    public function __invoke(array $item, Argument $args, ResolveInfo $info, \ArrayObject $context)
-    {
-        /** @var CAnnouncement $announcement */
-        $announcement = $item['announcement'];
-        /** @var CItemProperty $itemProperty */
-        $itemProperty = $item['item_property'];
-        $method = 'resolve'.ucfirst($info->fieldName);
-
-        if (method_exists($this, $method)) {
-            return $this->$method($announcement, $itemProperty, $args, $context);
-        }
-
-        $method = 'get'.ucfirst($info->fieldName);
-
-        if (method_exists($announcement, $method)) {
-            return $announcement->$method();
-        }
-
-        if (method_exists($itemProperty, $method)) {
-            return $itemProperty->$method();
-        }
-
-        return null;
-    }
-
-    /**
-     * @param CAnnouncement $announcement
-     *
-     * @return int
-     */
-    public function resolveId(CAnnouncement $announcement)
-    {
-        return $announcement->getIid();
-    }
-
-    /**
-     * @param CAnnouncement $announcement
-     * @param CItemProperty $itemProperty
-     *
-     * @return User
-     */
-    public function resolveBy(CAnnouncement $announcement, CItemProperty $itemProperty)
-    {
-        return $itemProperty->getInsertUser();
-    }
-
-    /**
-     * @param CAnnouncement $announcement
-     * @param CItemProperty $itemProperty
-     *
-     * @return \DateTime
-     */
-    public function resolveLastUpdateDate(CAnnouncement $announcement, CItemProperty $itemProperty)
-    {
-        return $itemProperty->getLasteditDate();
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index 567e59917b..6e8f0834bd 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -7,9 +7,12 @@ use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
 use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
-use GraphQL\Type\Definition\ResolveInfo;
+use Chamilo\CourseBundle\Entity\CAnnouncement;
+use Chamilo\CourseBundle\Entity\CItemProperty;
+use Chamilo\CourseBundle\Entity\CTool;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Criteria;
 use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 
 /**
@@ -17,10 +20,21 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  *
  * @package Chamilo\ApiBundle\GraphQL\Resolver
  */
-class CourseResolver implements ResolverInterface, ContainerAwareInterface
+class CourseResolver implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
+    /**
+     * @param Course   $course
+     * @param Argument $args
+     *
+     * @return null|string
+     */
+    public function getPicture(Course $course, Argument $args)
+    {
+        return \CourseManager::getPicturePath($course, $args['fullSize']);
+    }
+
     /**
      * @param Course       $course
      * @param Argument     $args
@@ -28,43 +42,132 @@ class CourseResolver implements ResolverInterface, ContainerAwareInterface
      *
      * @return array
      */
-    public function getTools(Course $course, Argument $args, \ArrayObject $context)
+    public function getTeachers(Course $course, Argument $args, \ArrayObject $context): array
+    {
+        if ($context->offsetExists('session')) {
+            /** @var Session $session */
+            $session = $context->offsetGet('session');
+
+            if ($session) {
+                $coaches = [];
+                $coachSubscriptions = $session->getUserCourseSubscriptionsByStatus($course, Session::COACH);
+
+                /** @var SessionRelCourseRelUser $coachSubscription */
+                foreach ($coachSubscriptions as $coachSubscription) {
+                    $coaches[] = $coachSubscription->getUser();
+                }
+
+                return $coaches;
+            }
+        }
+
+        $courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
+        $teachers = $courseRepo
+            ->getSubscribedTeachers($course)
+            ->getQuery()
+            ->getResult();
+
+        return $teachers;
+    }
+
+    /**
+     * @param Course       $course
+     * @param Argument     $args
+     * @param \ArrayObject $context
+     *
+     * @return ArrayCollection
+     */
+    public function getTools(Course $course, Argument $args, \ArrayObject $context): ArrayCollection
     {
-        $sessionId = 0;
+        $session = null;
 
         if ($context->offsetExists('session')) {
             /** @var Session $session */
             $session = $context->offsetGet('session');
-            $sessionId = $session->getId();
         }
 
-        $tools = \CourseHome::get_tools_category(TOOL_STUDENT_VIEW, $course->getId(), $sessionId);
-        $tools = array_filter($tools, function ($tool) {
-            switch ($tool['name']) {
-                case TOOL_COURSE_DESCRIPTION:
-                case TOOL_ANNOUNCEMENT:
-                    return true;
-                default:
-                    return false;
-            }
-        });
+        if (empty($args['type'])) {
+            return $course->getTools($session);
+        }
 
-        if (!empty($args['type'])) {
-            $tools = array_filter($tools, function ($tool) use ($args) {
-                return $tool['name'] === $args['type'];
-            });
+        $criteria = Criteria::create()
+            ->where(
+                Criteria::expr()->eq('name', $args['type'])
+            );
+
+        return $course->getTools($session)->matching($criteria);
+    }
+
+    /**
+     * @param CTool        $tool
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getDescriptions(Ctool $tool, \ArrayObject $context)
+    {
+        /** @var Session $session */
+        $session = $context->offsetGet('session');
+        $cd = new \CourseDescription();
+        $cd->set_course_id($tool->getCourse()->getId());
+
+        if ($session) {
+            $cd->set_session_id($session->getId());
         }
 
-        $ids = array_column($tools, 'iid');
+        $descriptions = $cd->get_description_data();
+
+        if (empty($descriptions)) {
+            return [];
+        }
 
         $qb = $this->em->createQueryBuilder();
         $qb
-            ->select('t')
-            ->from('ChamiloCourseBundle:CTool', 't')
+            ->select('d')
+            ->from('ChamiloCourseBundle:CCourseDescription', 'd')
             ->where(
-                $qb->expr()->in('t.id', $ids)
+                $qb->expr()->in('d.id', array_keys($descriptions['descriptions']))
             );
 
         return $qb->getQuery()->getResult();
     }
+
+    /**
+     * @param CTool        $tool
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getAnnouncements(CTool $tool, \ArrayObject $context): array
+    {
+        $announcementManager = $this->container->get('chamilo_course.entity.manager.announcement_manager');
+        $announcementsInfo = $announcementManager->getAnnouncements(
+            $this->getCurrentUser(),
+            $tool->getCourse(),
+            null,
+            $context->offsetGet('session'),
+            api_get_course_setting('allow_user_edit_announcement') === 'true',
+            api_get_configuration_value('hide_base_course_announcements_in_group') === true
+        );
+
+        $announcements = [];
+
+        for ($z = 0; $z < count($announcementsInfo); $z += 2) {
+            /** @var CAnnouncement $a */
+            $a = $announcementsInfo[$z];
+            /** @var CItemProperty $ip */
+            $ip = $announcementsInfo[$z + 1];
+
+            $announcement = new \stdClass();
+            $announcement->id = $a->getIid();
+            $announcement->title = $a->getTitle();
+            $announcement->content = $a->getContent();
+            $announcement->author = $ip->getInsertUser();
+            $announcement->lastUpdateDate = $ip->getLasteditDate();
+
+            $announcements[] = $announcement;
+        }
+
+        return $announcements;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resolver/SessionResolver.php b/src/ApiBundle/GraphQL/Resolver/SessionResolver.php
new file mode 100644
index 0000000000..ddf371d49a
--- /dev/null
+++ b/src/ApiBundle/GraphQL/Resolver/SessionResolver.php
@@ -0,0 +1,78 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ApiBundle\GraphQL\Resolver;
+
+use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
+use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CoreBundle\Entity\SessionRelCourse;
+use Chamilo\CoreBundle\Security\Authorization\Voter\SessionVoter;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+
+/**
+ * Class SessionResolver.
+ *
+ * @package Chamilo\ApiBundle\GraphQL\Resolver
+ */
+class SessionResolver implements ContainerAwareInterface
+{
+    use ApiGraphQLTrait;
+
+    /**
+     * @param Session $session
+     *
+     * @return string
+     */
+    public function getDescription(Session $session)
+    {
+        if (false === $session->getShowDescription()) {
+            return '';
+        }
+
+        return $session->getDescription();
+    }
+
+    /**
+     * @param Session $session
+     *
+     * @return int
+     */
+    public function getNumberOfUsers(Session $session)
+    {
+        return $session->getNbrUsers();
+    }
+
+    /**
+     * @param Session $session
+     *
+     * @return int
+     */
+    public function getNumberOfCourses(Session $session)
+    {
+        return $session->getNbrCourses();
+    }
+
+    /**
+     * @param Session $session
+     *
+     * @return array
+     */
+    public function getCourses(Session $session): array
+    {
+        $authChecker = $this->container->get('security.authorization_checker');
+        $courses = [];
+
+        /** @var SessionRelCourse $sessionCourse */
+        foreach ($session->getCourses() as $sessionCourse) {
+            $course = $sessionCourse->getCourse();
+
+            $session->setCurrentCourse($course);
+
+            if (false !== $authChecker->isGranted(SessionVoter::VIEW, $session)) {
+                $courses[] = $course;
+            }
+        }
+
+        return $courses;
+    }
+}
diff --git a/src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php b/src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php
deleted file mode 100644
index 0a6fa6fd30..0000000000
--- a/src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Resolver;
-
-use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
-use Chamilo\CoreBundle\Entity\Course;
-use Chamilo\CoreBundle\Entity\Session;
-use Chamilo\CourseBundle\Entity\CTool;
-use GraphQL\Error\UserError;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Class ToolAnnouncementsResolver.
- *
- * @package Chamilo\ApiBundle\GraphQL\Resolver
- */
-class ToolAnnouncementsResolver implements ContainerAwareInterface
-{
-    use ApiGraphQLTrait;
-
-    /**
-     * @param CTool        $tool
-     * @param \ArrayObject $context
-     *
-     * @return array
-     */
-    public function getAnnouncements(CTool $tool, \ArrayObject $context): array
-    {
-        /** @var Course $course */
-        $course = $context->offsetGet('course');
-        /** @var Session $session */
-        $session = null;
-
-        if ($context->offsetExists('session')) {
-            $session = $context->offsetGet('session');
-        }
-
-        $em = $this->container->get('chamilo_course.entity.manager.announcement_manager');
-
-        try {
-            $announcementsInfo = $em->getAnnouncements(
-                $this->getCurrentUser(),
-                $course,
-                null,
-                $session,
-                api_get_course_setting('allow_user_edit_announcement') === 'true',
-                api_get_configuration_value('hide_base_course_announcements_in_group') === true
-            );
-        } catch (\Exception $exception) {
-            throw new UserError($exception->getMessage());
-        }
-
-        if (empty($announcementsInfo)) {
-            return [];
-        }
-
-        $announcements = [];
-
-        for ($z = 0; $z < count($announcementsInfo); $z += 2) {
-            $announcements[] = [
-                'announcement' => $announcementsInfo[$z],
-                'item_property' => $announcementsInfo[$z + 1],
-            ];
-        }
-
-        return $announcements;
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php b/src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php
deleted file mode 100644
index 2be65f7921..0000000000
--- a/src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-namespace Chamilo\ApiBundle\GraphQL\Resolver;
-
-use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
-use Chamilo\CoreBundle\Entity\Course;
-use Chamilo\CourseBundle\Entity\CTool;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
-
-/**
- * Class ToolDescriptionResolver.
- *
- * @package Chamilo\ApiBundle\GraphQL\Resolver
- */
-class ToolDescriptionResolver implements ContainerAwareInterface
-{
-    use ApiGraphQLTrait;
-
-    /**
-     * @param CTool        $tool
-     * @param \ArrayObject $context
-     *
-     * @return array
-     */
-    public function getDescriptions(CTool $tool, \ArrayObject $context): array
-    {
-        /** @var Course $course */
-        $course = $context->offsetGet('course');
-        $cd = new \CourseDescription();
-        $cd->set_course_id($course->getId());
-
-        if ($context->offsetExists('session')) {
-            /** @var Session $session */
-            $session = $context->offsetGet('session');
-
-            if ($session) {
-                $cd->set_session_id($session->getId());
-            }
-        }
-
-        $descriptions = $cd->get_description_data();
-
-        if (empty($descriptions)) {
-            return [];
-        }
-
-        $qb = $this->em->createQueryBuilder();
-        $qb
-            ->select('d')
-            ->from('ChamiloCourseBundle:CCourseDescription', 'd')
-            ->where(
-                $qb->expr()->in('d.id', array_keys($descriptions['descriptions']))
-            );
-
-        return $qb->getQuery()->getResult();
-    }
-}
diff --git a/src/ApiBundle/GraphQL/Map/UserMap.php b/src/ApiBundle/GraphQL/Resolver/UserResolver.php
similarity index 74%
rename from src/ApiBundle/GraphQL/Map/UserMap.php
rename to src/ApiBundle/GraphQL/Resolver/UserResolver.php
index aefd457ed0..047fed1035 100644
--- a/src/ApiBundle/GraphQL/Map/UserMap.php
+++ b/src/ApiBundle/GraphQL/Resolver/UserResolver.php
@@ -1,73 +1,30 @@
 <?php
 /* For licensing terms, see /license.txt */
 
-namespace Chamilo\ApiBundle\GraphQL\Map;
+namespace Chamilo\ApiBundle\GraphQL\Resolver;
 
 use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
 use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\UserBundle\Entity\User;
-use GraphQL\Type\Definition\ResolveInfo;
+use Doctrine\Common\Collections\ArrayCollection;
 use Overblog\GraphQLBundle\Definition\Argument;
-use Overblog\GraphQLBundle\Resolver\ResolverMap;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 
 /**
- * Class UserResolverMap.
+ * Class UserResolver.
  *
- * @package Chamilo\ApiBundle\GraphQL\Map
+ * @package Chamilo\ApiBundle\GraphQL\Resolver
  */
-class UserMap extends ResolverMap implements ContainerAwareInterface
+class UserResolver implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
-    /**
-     * @return array
-     */
-    protected function map()
-    {
-        return [
-            'User' => [
-                self::RESOLVE_FIELD => function (
-                    User $user,
-                    Argument $args,
-                    \ArrayObject $context,
-                    ResolveInfo $info
-                ) {
-                    $context->offsetSet('user', $user);
-
-                    switch ($info->fieldName) {
-                        case 'email':
-                            return $this->resolveEmail($user);
-                        case 'picture':
-                            return $this->resolvePicture($user, $args['size']);
-                        case 'messages':
-                            return $this->resolveMessages($user, $args['lastId']);
-                        case 'messageContacts':
-                            return $this->resolveMessageContacts($user, $args['filter']);
-                        case 'courses':
-                            return $this->resolveCourses($user);
-                        case 'sessions':
-                            return $this->resolveSessions($user);
-                        default:
-                            $method = 'get'.ucfirst($info->fieldName);
-
-                            if (method_exists($user, $method)) {
-                                return $user->$method();
-                            }
-
-                            return null;
-                    }
-                },
-            ],
-        ];
-    }
-
     /**
      * @param User $user
      *
      * @return string
      */
-    private function resolveEmail(User $user)
+    public function getEmail(User $user)
     {
         $this->protectCurrentUserData($user);
 
@@ -81,59 +38,63 @@ class UserMap extends ResolverMap implements ContainerAwareInterface
     }
 
     /**
-     * @param User $user
-     * @param int  $size
+     * @param User     $user
+     * @param Argument $args
      *
      * @return string
      */
-    private function resolvePicture(User $user, $size)
+    public function getPicture(User $user, Argument $args)
     {
         $assets = $this->container->get('templating.helper.assets');
-        $path = $user->getAvatarOrAnonymous((int) $size);
+        $path = $user->getAvatarOrAnonymous($args['size']);
 
         return $assets->getUrl($path);
     }
 
     /**
-     * @param User $user
-     * @param int  $lastId
+     * @param User     $user
+     * @param Argument $args
      *
-     * @return \Doctrine\Common\Collections\ArrayCollection
+     * @return ArrayCollection
      */
-    private function resolveMessages(User $user, $lastId)
+    public function getMessages(User $user, Argument $args)
     {
         $this->protectCurrentUserData($user);
 
-        return $user->getUnreadReceivedMessages($lastId);
+        return $user->getUnreadReceivedMessages($args['lastId']);
     }
 
     /**
-     * @param User   $user
-     * @param string $filter
+     * @param User     $user
+     * @param Argument $args
      *
-     * @return array|mixed
+     * @return array
      */
-    private function resolveMessageContacts(User $user, $filter)
+    public function getMessageContacts(User $user, Argument $args)
     {
         $this->protectCurrentUserData($user);
 
-        if (strlen($filter) < 3) {
+        if (strlen($args['filter']) < 3) {
             return [];
         }
 
         $usersRepo = $this->em->getRepository('ChamiloUserBundle:User');
-        $users = $usersRepo->findUsersToSendMessage($user->getId(), $filter);
+        $users = $usersRepo->findUsersToSendMessage($user->getId(), $args['filter']);
 
         return $users;
     }
 
     /**
-     * @param User $user
+     * @param User         $user
+     * @param Argument     $args
+     * @param \ArrayObject $context
      *
      * @return array
      */
-    private function resolveCourses(User $user)
+    public function getCourses(User $user, Argument $args, \ArrayObject $context)
     {
+        $context->offsetSet('session', null);
+
         $this->protectCurrentUserData($user);
 
         $coursesInfo = \CourseManager::get_courses_list_by_user_id($user->getId());
@@ -159,7 +120,7 @@ class UserMap extends ResolverMap implements ContainerAwareInterface
      *
      * @return array
      */
-    private function getUserSessions(User $user)
+    private function findUserSessions(User $user)
     {
         $allowOrder = api_get_configuration_value('session_list_order');
         $showAllSessions = api_get_configuration_value('show_all_sessions_on_my_course_page') === true;
@@ -297,11 +258,11 @@ class UserMap extends ResolverMap implements ContainerAwareInterface
      *
      * @return array
      */
-    public function resolveSessions(User $user)
+    public function getSessions(User $user)
     {
         $this->protectCurrentUserData($user);
 
-        $sessionsId = $this->getUserSessions($user);
+        $sessionsId = $this->findUserSessions($user);
 
         if (empty($sessionsId)) {
             return [];
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index 32886c84b5..b162a4018b 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -46,7 +46,7 @@ type User {
     email: String
     officialCode: String
     status: UserStatus
-    picture(size: ImageSize = SIZE_SMALL): String
+    picture(size: ImageSize = ICON_SIZE_SMALL): String
     "Received messages for the user."
     messages(
         "Last received by the app message ID."
@@ -184,17 +184,17 @@ enum UserStatus {
 "One of the sizes for the picture."
 enum ImageSize {
     "Image in small size: 16px."
-    SIZE_TINY
+    ICON_SIZE_TINY
     "Image in small size: 22px."
-    SIZE_SMALL
+    ICON_SIZE_SMALL
     "Image in small size: 32px."
-    SIZE_MEDIUM
+    ICON_SIZE_MEDIUM
     "Image in small size: 48px."
-    SIZE_LARGE
+    ICON_SIZE_LARGE
     "Image in small size: 64px."
-    SIZE_BIG
+    ICON_SIZE_BIG
     "Image in small size: 128px."
-    SIZE_HUGE
+    ICON_SIZE_HUGE
 }
 
 "One of the types of course tool"
diff --git a/src/CoreBundle/Entity/Message.php b/src/CoreBundle/Entity/Message.php
index c5fbea068e..8774ab61a2 100644
--- a/src/CoreBundle/Entity/Message.php
+++ b/src/CoreBundle/Entity/Message.php
@@ -378,4 +378,20 @@ class Message
     {
         return $this->attachments;
     }
+
+    /**
+     * Get an excerpt from the content.
+     *
+     * @param int $length Optional. Length of the excerpt.
+     *
+     * @return string
+     */
+    public function getExcerpt($length = 50)
+    {
+        $striped = strip_tags($this->content);
+        $replaced = str_replace(["\r\n", "\n"], ' ', $striped);
+        $trimmed = trim($replaced);
+
+        return api_trunc_str($trimmed, $length);
+    }
 }
diff --git a/src/CourseBundle/Entity/Manager/AnnouncementManager.php b/src/CourseBundle/Entity/Manager/AnnouncementManager.php
index ed7669c6c1..a41a48ae46 100644
--- a/src/CourseBundle/Entity/Manager/AnnouncementManager.php
+++ b/src/CourseBundle/Entity/Manager/AnnouncementManager.php
@@ -29,8 +29,6 @@ class AnnouncementManager extends BaseEntityManager
      * @param string          $titleToSearch
      * @param User|null       $userToSearch
      *
-     * @throws \Doctrine\ORM\NonUniqueResultException
-     *
      * @return mixed
      */
     public function getAnnouncements(

From 751e77d579df362c723025069cbcf37e9980cb5e Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Wed, 26 Sep 2018 16:07:27 -0500
Subject: [PATCH 05/32] Fix current session id when parsin announcement #2644

---
 main/inc/lib/AnnouncementManager.php | 37 ++++++++++++----------------
 1 file changed, 16 insertions(+), 21 deletions(-)

diff --git a/main/inc/lib/AnnouncementManager.php b/main/inc/lib/AnnouncementManager.php
index 7cf21b80ca..3f6dbf41c1 100755
--- a/main/inc/lib/AnnouncementManager.php
+++ b/main/inc/lib/AnnouncementManager.php
@@ -24,9 +24,11 @@ class AnnouncementManager
     }
 
     /**
+     * @param int $sessionId
+     *
      * @return array
      */
-    public static function getTags()
+    public static function getTags($sessionId = 0)
     {
         $tags = [
             '((user_name))',
@@ -47,7 +49,7 @@ class AnnouncementManager
                 $tags[] = "((extra_".$extra['variable']."))";
             }
         }
-        $sessionId = api_get_session_id();
+        $sessionId = $sessionId ?: api_get_session_id();
         if (!empty($sessionId)) {
             $tags[] = '((coaches))';
             $tags[] = '((general_coach))';
@@ -75,21 +77,6 @@ class AnnouncementManager
         $courseInfo = api_get_course_info($courseCode);
         $teacherList = CourseManager::getTeacherListFromCourseCodeToString($courseInfo['code']);
 
-        $generalCoachName = '';
-        $generalCoachEmail = '';
-        $coaches = '';
-        if (!empty($sessionId)) {
-            $sessionInfo = api_get_session_info($sessionId);
-            $coaches = CourseManager::get_coachs_from_course_to_string(
-                $sessionId,
-                $courseInfo['real_id']
-            );
-
-            $generalCoach = api_get_user_info($sessionInfo['id_coach']);
-            $generalCoachName = $generalCoach['complete_name'];
-            $generalCoachEmail = $generalCoach['email'];
-        }
-
         $data = [];
         $data['user_name'] = '';
         $data['user_firstname'] = '';
@@ -134,13 +121,21 @@ class AnnouncementManager
             }
         }
 
-        if (!empty(api_get_session_id())) {
+        if (!empty($sessionId)) {
+            $sessionInfo = api_get_session_info($sessionId);
+            $coaches = CourseManager::get_coachs_from_course_to_string(
+                $sessionId,
+                $courseInfo['real_id']
+            );
+
+            $generalCoach = api_get_user_info($sessionInfo['id_coach']);
+
             $data['coaches'] = $coaches;
-            $data['general_coach'] = $generalCoachName;
-            $data['general_coach_email'] = $generalCoachEmail;
+            $data['general_coach'] = $generalCoach['complete_name'];
+            $data['general_coach_email'] = $generalCoach['email'];
         }
 
-        $tags = self::getTags();
+        $tags = self::getTags($sessionId);
         foreach ($tags as $tag) {
             $simpleTag = str_replace(['((', '))'], '', $tag);
             $value = isset($data[$simpleTag]) ? $data[$simpleTag] : '';

From 4fd4fdbbc0dc99d06ca39925a6952dfb866ee973 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Wed, 26 Sep 2018 17:02:16 -0500
Subject: [PATCH 06/32] GraphQL Rename RootMap to QueryMap #2644

---
 config/packages/graphql.yaml                            | 9 ++++-----
 src/ApiBundle/GraphQL/Map/{RootMap.php => QueryMap.php} | 9 +++++----
 2 files changed, 9 insertions(+), 9 deletions(-)
 rename src/ApiBundle/GraphQL/Map/{RootMap.php => QueryMap.php} (97%)

diff --git a/config/packages/graphql.yaml b/config/packages/graphql.yaml
index 1003df0de9..48d018c5b4 100644
--- a/config/packages/graphql.yaml
+++ b/config/packages/graphql.yaml
@@ -4,7 +4,7 @@ overblog_graphql:
             query: Query
             mutation: Mutation
             resolver_maps:
-                - '%chamilo_api.graphql.resolver_map.root.class%'
+                - '%chamilo_api.graphql.resolver_map.query.class%'
                 - '%chamilo_api.graphql.resolver_map.enum.class%'
                 - '%chamilo_api.graphql.resolver_map.union.class%'
                 - '%chamilo_api.graphql.resolver_map.scalar.class%'
@@ -16,14 +16,13 @@ overblog_graphql:
                     dir: "%kernel.root_dir%/ApiBundle/GraphQL/Resources/config"
 
 parameters:
-    chamilo_api.graphql.resolver_map.root.class: Chamilo\ApiBundle\GraphQL\Map\RootMap
+    chamilo_api.graphql.resolver_map.query.class: Chamilo\ApiBundle\GraphQL\Map\QueryMap
     chamilo_api.graphql.resolver_map.enum.class: Chamilo\ApiBundle\GraphQL\Map\EnumMap
     chamilo_api.graphql.resolver_map.union.class: Chamilo\ApiBundle\GraphQL\Map\UnionMap
     chamilo_api.graphql.resolver_map.scalar.class: Chamilo\ApiBundle\GraphQL\Map\ScalarMap
     chamilo_api.graphql.resolver_map.mutation.class: Chamilo\ApiBundle\GraphQL\Map\MutationMap
 
 services:
-
     chamilo_api.graphql.resolver.user:
         class: Chamilo\ApiBundle\GraphQL\Resolver\UserResolver
         arguments: [ '@service_container' ]
@@ -36,7 +35,7 @@ services:
         class: Chamilo\ApiBundle\GraphQL\Resolver\SessionResolver
         arguments: [ '@service_container' ]
 
-    Chamilo\ApiBundle\GraphQL\:
-        resource: "../../src/ApiBundle/GraphQL/*"
+    Chamilo\ApiBundle\GraphQL\Map\:
+        resource: "../../src/ApiBundle/GraphQL/Map/*"
         arguments:
             - "@service_container"
diff --git a/src/ApiBundle/GraphQL/Map/RootMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
similarity index 97%
rename from src/ApiBundle/GraphQL/Map/RootMap.php
rename to src/ApiBundle/GraphQL/Map/QueryMap.php
index d89c9cdc27..ab1a9092b6 100644
--- a/src/ApiBundle/GraphQL/Map/RootMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -19,10 +19,11 @@ use Overblog\GraphQLBundle\Resolver\ResolverMap;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 
 /**
- * Class RootResolverMap
+ * Class QueryMap.
+ *
  * @package Chamilo\ApiBundle\GraphQL\Map
  */
-class RootMap extends ResolverMap implements ContainerAwareInterface
+class QueryMap extends ResolverMap implements ContainerAwareInterface
 {
     use ApiGraphQLTrait;
 
@@ -120,7 +121,7 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
                 },
             ],
             'CourseAnnouncement' => [
-                'content' => function(\stdClass $announcement, Argument $args, \ArrayObject $context) {
+                'content' => function (\stdClass $announcement, Argument $args, \ArrayObject $context) {
                     /** @var User $reader */
                     $reader = $context->offsetGet('user');
                     /** @var Course $course */
@@ -134,7 +135,7 @@ class RootMap extends ResolverMap implements ContainerAwareInterface
                         $course->getCode(),
                         $session ? $session->getId() : 0
                     );
-                }
+                },
             ],
             'Session' => [
                 self::RESOLVE_FIELD => function (

From 62567d09f8c08e41dd0a59ba2ad6dd964bdd431e Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Wed, 26 Sep 2018 17:44:18 -0500
Subject: [PATCH 07/32] GraphQL add query for course announcement #2644

---
 main/inc/lib/AnnouncementManager.php          |  4 ++
 src/ApiBundle/GraphQL/Map/QueryMap.php        |  8 ++-
 .../GraphQL/Resolver/CourseResolver.php       | 54 ++++++++++++++-----
 .../Resources/config/schema.types.graphql     |  1 +
 4 files changed, 52 insertions(+), 15 deletions(-)

diff --git a/main/inc/lib/AnnouncementManager.php b/main/inc/lib/AnnouncementManager.php
index 3f6dbf41c1..783bdfc3e1 100755
--- a/main/inc/lib/AnnouncementManager.php
+++ b/main/inc/lib/AnnouncementManager.php
@@ -376,6 +376,10 @@ class AnnouncementManager
             ]
         );
 
+        if (empty($result)) {
+            return [];
+        }
+
         return [
             'announcement' => $result[0],
             'item_property' => $result[1],
diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index ab1a9092b6..f66600cbaa 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -111,12 +111,16 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                     \ArrayObject $context,
                     ResolveInfo $info
                 ) {
-                    if ('announcements' === $info->fieldName) {
-                        $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
 
+                    if ('announcements' === $info->fieldName) {
                         return $resolver->getAnnouncements($tool, $context);
                     }
 
+                    if ('announcement' === $info->fieldName) {
+                        return $resolver->getAnnouncement($args['id'], $context);
+                    }
+
                     return $this->resolveField($info->fieldName, $tool);
                 },
             ],
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index 6e8f0834bd..b985163d6b 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -13,6 +13,7 @@ use Chamilo\CourseBundle\Entity\CTool;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Criteria;
 use Overblog\GraphQLBundle\Definition\Argument;
+use Overblog\GraphQLBundle\Error\UserError;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 
 /**
@@ -153,21 +154,48 @@ class CourseResolver implements ContainerAwareInterface
         $announcements = [];
 
         for ($z = 0; $z < count($announcementsInfo); $z += 2) {
-            /** @var CAnnouncement $a */
-            $a = $announcementsInfo[$z];
-            /** @var CItemProperty $ip */
-            $ip = $announcementsInfo[$z + 1];
-
-            $announcement = new \stdClass();
-            $announcement->id = $a->getIid();
-            $announcement->title = $a->getTitle();
-            $announcement->content = $a->getContent();
-            $announcement->author = $ip->getInsertUser();
-            $announcement->lastUpdateDate = $ip->getLasteditDate();
-
-            $announcements[] = $announcement;
+            $announcements[] = self::getAnnouncementObject($announcementsInfo[$z], $announcementsInfo[$z + 1]);
         }
 
         return $announcements;
     }
+
+    /**
+     * @param int          $id
+     * @param \ArrayObject $context
+     *
+     * @return \stdClass
+     */
+    public function getAnnouncement($id, \ArrayObject $context)
+    {
+        $announcementInfo = \AnnouncementManager::getAnnouncementInfoById(
+            $id,
+            $context->offsetGet('course')->getId(),
+            $this->getCurrentUser()->getId()
+        );
+
+        if (empty($announcementInfo)) {
+            throw new UserError($this->translator->trans('Announcement not found.'));
+        }
+
+        return self::getAnnouncementObject($announcementInfo['announcement'], $announcementInfo['item_property']);
+    }
+
+    /**
+     * @param CAnnouncement $a
+     * @param CItemProperty $ip
+     *
+     * @return \stdClass
+     */
+    private static function getAnnouncementObject(CAnnouncement $a, CItemProperty $ip)
+    {
+        $announcement = new \stdClass();
+        $announcement->id = $a->getIid();
+        $announcement->title = $a->getTitle();
+        $announcement->content = $a->getContent();
+        $announcement->author = $ip->getInsertUser();
+        $announcement->lastUpdateDate = $ip->getLasteditDate();
+
+        return $announcement;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index b162a4018b..df2cfac06d 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -123,6 +123,7 @@ type ToolAnnouncements {
     image: String
     customIcon: String
     announcements: [CourseAnnouncement!]
+    announcement(id: Int!): CourseAnnouncement
 }
 
 "A course announcement."

From 6d9bbcc826c89a30d979c56c5cc2e5fc70f17751 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Thu, 27 Sep 2018 10:52:54 -0500
Subject: [PATCH 08/32] GraphQL add query for notebook tool #2644

---
 src/ApiBundle/GraphQL/Map/EnumMap.php         |  1 +
 src/ApiBundle/GraphQL/Map/QueryMap.php        | 22 +++++++++++++++++++
 src/ApiBundle/GraphQL/Map/UnionMap.php        |  3 ++-
 .../GraphQL/Resolver/CourseResolver.php       | 19 ++++++++++++++++
 .../Resources/config/schema.types.graphql     | 22 ++++++++++++++++++-
 5 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/src/ApiBundle/GraphQL/Map/EnumMap.php b/src/ApiBundle/GraphQL/Map/EnumMap.php
index 18edc97800..840e0c30f7 100644
--- a/src/ApiBundle/GraphQL/Map/EnumMap.php
+++ b/src/ApiBundle/GraphQL/Map/EnumMap.php
@@ -40,6 +40,7 @@ class EnumMap extends ResolverMap implements ContainerAwareInterface
             'CourseToolType' => [
                 'TOOL_COURSE_DESCRIPTION' => TOOL_COURSE_DESCRIPTION,
                 'TOOL_ANNOUNCEMENT' => TOOL_ANNOUNCEMENT,
+                'TOOL_NOTEBOOK' => TOOL_NOTEBOOK,
             ],
         ];
     }
diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index f66600cbaa..9027781709 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -10,6 +10,7 @@ use Chamilo\CoreBundle\Entity\Message;
 use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionCategory;
 use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
+use Chamilo\CourseBundle\Entity\CNotebook;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\UserBundle\Entity\User;
 use GraphQL\Type\Definition\ResolveInfo;
@@ -141,6 +142,27 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                     );
                 },
             ],
+            'ToolNotebook' => [
+                self::RESOLVE_FIELD => function (
+                    CTool $tool,
+                    Argument $args,
+                    \ArrayObject $context,
+                    ResolveInfo $info
+                ) {
+                    if ('notes' === $info->fieldName) {
+                        $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                        return $resolver->getNotes($context);
+                    }
+
+                    return $this->resolveField($info->fieldName, $tool);
+                },
+            ],
+            'CourseNote' => [
+                'id' => function (CNotebook $note) {
+                    return $note->getIid();
+                },
+            ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
                     Session $session,
diff --git a/src/ApiBundle/GraphQL/Map/UnionMap.php b/src/ApiBundle/GraphQL/Map/UnionMap.php
index 0cefb546f5..91634ccdc1 100644
--- a/src/ApiBundle/GraphQL/Map/UnionMap.php
+++ b/src/ApiBundle/GraphQL/Map/UnionMap.php
@@ -29,8 +29,9 @@ class UnionMap extends ResolverMap implements ContainerAwareInterface
                         case TOOL_COURSE_DESCRIPTION:
                             return 'ToolDescription';
                         case TOOL_ANNOUNCEMENT:
-                        default:
                             return 'ToolAnnouncements';
+                        case TOOL_NOTEBOOK:
+                            return 'ToolNotebook';
                     }
                 },
             ],
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index b985163d6b..2bc1347fa7 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -10,6 +10,7 @@ use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CourseBundle\Entity\CAnnouncement;
 use Chamilo\CourseBundle\Entity\CItemProperty;
 use Chamilo\CourseBundle\Entity\CTool;
+use Chamilo\CourseBundle\Repository\CNotebookRepository;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Criteria;
 use Overblog\GraphQLBundle\Definition\Argument;
@@ -198,4 +199,22 @@ class CourseResolver implements ContainerAwareInterface
 
         return $announcement;
     }
+
+    /**
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getNotes(\ArrayObject $context): array
+    {
+        /** @var CNotebookRepository $notebooksRepo */
+        $notebooksRepo = $this->em->getRepository('ChamiloCourseBundle:CNotebook');
+        $notebooks = $notebooksRepo->findByUser(
+            $this->getCurrentUser(),
+            $context->offsetGet('course'),
+            $context->offsetGet('session')
+        );
+
+        return $notebooks;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index df2cfac06d..77e72182a9 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -116,6 +116,25 @@ type CourseDescription {
     descriptionType: Int
 }
 
+"Personal notes relevant to student or coursework"
+type ToolNotebook {
+    name: String
+    category: String
+    image: String
+    customIcon: String
+    notes: [CourseNote!]
+}
+
+"Note by user in notebook tool"
+type CourseNote {
+    id: Int
+    title: String
+    description: String
+    creationDate: DateTime
+    updateDate: DateTime
+    status: Int
+}
+
 "Announcements related to the course."
 type ToolAnnouncements {
     name: String
@@ -166,7 +185,7 @@ type SessionCategory {
 
 # Unions
 
-union CourseTool = ToolDescription | ToolAnnouncements
+union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook
 
 # Enums
 
@@ -202,6 +221,7 @@ enum ImageSize {
 enum CourseToolType {
     TOOL_COURSE_DESCRIPTION
     TOOL_ANNOUNCEMENT
+    TOOL_NOTEBOOK
 }
 
 # Scalars

From b4c9e97f9194ec84a938b4bd282b9cd3d95eae39 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Thu, 27 Sep 2018 15:53:54 -0500
Subject: [PATCH 09/32] Add repository for forum categories #2644

---
 src/CourseBundle/Entity/CForumCategory.php    |  37 +++++-
 .../Repository/CForumCategoryRepository.php   | 119 ++++++++++++++++++
 2 files changed, 155 insertions(+), 1 deletion(-)
 create mode 100644 src/CourseBundle/Repository/CForumCategoryRepository.php

diff --git a/src/CourseBundle/Entity/CForumCategory.php b/src/CourseBundle/Entity/CForumCategory.php
index deef3937b4..9f27a1346b 100644
--- a/src/CourseBundle/Entity/CForumCategory.php
+++ b/src/CourseBundle/Entity/CForumCategory.php
@@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
  *      @ORM\Index(name="session_id", columns={"session_id"})
  *  }
  * )
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Chamilo\CourseBundle\Repository\CForumCategoryRepository")
  */
 class CForumCategory
 {
@@ -77,6 +77,21 @@ class CForumCategory
      */
     protected $catId;
 
+    /**
+     * @var CItemProperty
+     */
+    private $itemProperty;
+
+    /**
+     * Get iid.
+     *
+     * @return int
+     */
+    public function getIid()
+    {
+        return $this->iid;
+    }
+
     /**
      * Set catTitle.
      *
@@ -244,4 +259,24 @@ class CForumCategory
     {
         return $this->cId;
     }
+
+    /**
+     * @param CItemProperty $itemProperty
+     *
+     * @return CForumCategory
+     */
+    public function setItemProperty(CItemProperty $itemProperty)
+    {
+        $this->itemProperty = $itemProperty;
+
+        return $this;
+    }
+
+    /**
+     * @return CItemProperty
+     */
+    public function getItemProperty()
+    {
+        return $this->itemProperty;
+    }
 }
diff --git a/src/CourseBundle/Repository/CForumCategoryRepository.php b/src/CourseBundle/Repository/CForumCategoryRepository.php
new file mode 100644
index 0000000000..1f692cbfba
--- /dev/null
+++ b/src/CourseBundle/Repository/CForumCategoryRepository.php
@@ -0,0 +1,119 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\CourseBundle\Repository;
+
+use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CourseBundle\Entity\CForumCategory;
+use Chamilo\CourseBundle\Entity\CItemProperty;
+use Doctrine\ORM\EntityRepository;
+
+/**
+ * Class CForumCategoryRepository.
+ *
+ * @package Chamilo\CourseBundle\Repository
+ */
+class CForumCategoryRepository extends EntityRepository
+{
+    /**
+     * @param bool         $isAllowedToEdit
+     * @param Course       $course
+     * @param Session|null $session
+     *
+     * @return array
+     *
+     * @todo Remove api_get_session_condition
+     */
+    public function findAllInCourse($isAllowedToEdit, Course $course, Session $session = null): array
+    {
+        $conditionSession = api_get_session_condition(
+            $session ? $session->getId() : 0,
+            true,
+            true,
+            'fcat.sessionId'
+        );
+        $conditionVisibility = $isAllowedToEdit ? 'ip.visibility != 2' : 'ip.visibility = 1';
+
+        $dql = "SELECT ip, fcat
+            FROM ChamiloCourseBundle:CItemProperty AS ip
+            INNER JOIN ChamiloCourseBundle:CForumCategory fcat
+                WITH (fcat.catId = ip.ref AND ip.course = fcat.cId)
+            WHERE
+                ip.tool = :tool AND
+                ip.course = :course
+                $conditionSession AND
+                $conditionVisibility
+                ORDER BY fcat.catOrder ASC";
+
+        $result = $this
+            ->_em
+            ->createQuery($dql)
+            ->setParameters(['course' => $course, 'tool' => TOOL_FORUM_CATEGORY])
+            ->getResult();
+
+        $categories = [];
+
+        for ($i = 0; $i < count($result); $i += 2) {
+            /** @var CItemProperty $ip */
+            $ip = $result[$i];
+            /** @var CForumCategory $fc */
+            $fc = $result[$i + 1];
+
+            $fc->setItemProperty($ip);
+
+            $categories[] = $fc;
+        }
+
+        return $categories;
+    }
+
+    /**
+     * @param int     $id
+     * @param Course  $course
+     * @param Session $session
+     *
+     * @return CForumCategory|null
+     *
+     * @todo Remove api_get_session_condition
+     */
+    public function findOneInCourse($id, Course $course, Session $session)
+    {
+        $conditionSession = api_get_session_condition(
+            $session ? $session->getId() : 0,
+            true,
+            true,
+            'fcat.sessionId'
+        );
+
+        $dql = "SELECT ip, fcat
+            FROM ChamiloCourseBundle:CItemProperty AS ip
+            INNER JOIN ChamiloCourseBundle:CForumCategory fcat
+                WITH (fcat.catId = ip.ref AND ip.course = fcat.cId)
+            WHERE
+                ip.tool = :tool AND
+                ip.course = :course
+                fcat.iid = :id
+                $conditionSession AND
+                ORDER BY fcat.catOrder ASC";
+
+        $result = $this
+            ->_em
+            ->createQuery($dql)
+            ->setParameters(['tool' => TOOL_FORUM_CATEGORY, 'course' => $course, 'id' => (int) $id])
+            ->getResult();
+
+        if (empty($result)) {
+            return null;
+        }
+
+        /** @var CItemProperty $ip */
+        $ip = $result[0];
+        /** @var CForumCategory $fc */
+        $fc = $result[1];
+
+        $fc->setItemProperty($ip);
+
+        return $fc;
+    }
+}

From 0aeedf0ac484603ada68eb15bf48b38f69e6e9c1 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Thu, 27 Sep 2018 17:35:42 -0500
Subject: [PATCH 10/32] Add association mapping forum category - forum - forum
 thread - forum post #2644

---
 main/forum/forumfunction.inc.php              | 12 +++---
 main/lp/learnpath.class.php                   |  2 +-
 main/webservices/cm_webservice_forum.php      |  2 +-
 .../Schema/V200/Version20180927172830.php     | 39 +++++++++++++++++++
 src/CourseBundle/Entity/CForumCategory.php    | 18 +++++++++
 src/CourseBundle/Entity/CForumForum.php       | 39 ++++++++++++++++---
 src/CourseBundle/Entity/CForumPost.php        | 23 +++++------
 src/CourseBundle/Entity/CForumThread.php      | 37 +++++++++++++-----
 8 files changed, 139 insertions(+), 33 deletions(-)
 create mode 100644 src/CoreBundle/Migrations/Schema/V200/Version20180927172830.php

diff --git a/main/forum/forumfunction.inc.php b/main/forum/forumfunction.inc.php
index 884e30a4e8..0ef86122cb 100755
--- a/main/forum/forumfunction.inc.php
+++ b/main/forum/forumfunction.inc.php
@@ -1002,7 +1002,7 @@ function delete_post($post_id)
                 'parent_of_deleted_post' => $post->getPostParentId(),
                 'course' => $course_id,
                 'post' => $post->getPostId(),
-                'thread_of_deleted_post' => $post->getThreadId(),
+                'thread_of_deleted_post' => $post->getThread() ? $post->getThread()->getIid() : 0,
                 'forum_of_deleted_post' => $post->getForumId(),
             ]);
 
@@ -2020,7 +2020,7 @@ function getThreadInfo($threadId, $cId)
     if ($forumThread) {
         $thread['threadId'] = $forumThread->getThreadId();
         $thread['threadTitle'] = $forumThread->getThreadTitle();
-        $thread['forumId'] = $forumThread->getForumId();
+        $thread['forumId'] = $forumThread->getForum() ? $forumThread->getForum()->getIid() : 0;
         $thread['sessionId'] = $forumThread->getSessionId();
         $thread['threadSticky'] = $forumThread->getThreadSticky();
         $thread['locked'] = $forumThread->getLocked();
@@ -2121,7 +2121,7 @@ function getPosts(
             'post_id' => $post->getPostId(),
             'post_title' => $post->getPostTitle(),
             'post_text' => $post->getPostText(),
-            'thread_id' => $post->getThreadId(),
+            'thread_id' => $post->getThread() ? $post->getThread()->getIid() : 0,
             'forum_id' => $post->getForumId(),
             'poster_id' => $post->getPosterId(),
             'poster_name' => $post->getPosterName(),
@@ -2710,12 +2710,14 @@ function store_thread(
     }
     $clean_post_title = $values['post_title'];
 
+    $forum = $em->find('ChamiloCourseBundle:CForumForum', $values['forum_id']);
+
     // We first store an entry in the forum_thread table because the thread_id is used in the forum_post table.
     $lastThread = new CForumThread();
     $lastThread
         ->setCId($course_id)
         ->setThreadTitle($clean_post_title)
-        ->setForumId($values['forum_id'])
+        ->setForum($forum)
         ->setThreadPosterId($userId)
         ->setThreadPosterName(isset($values['poster_name']) ? $values['poster_name'] : null)
         ->setThreadDate($post_date)
@@ -2810,7 +2812,7 @@ function store_thread(
         ->setCId($course_id)
         ->setPostTitle($clean_post_title)
         ->setPostText($values['post_text'])
-        ->setThreadId($lastThread->getIid())
+        ->setThread($lastThread)
         ->setForumId($values['forum_id'])
         ->setPosterId($userId)
         ->setPosterName(isset($values['poster_name']) ? $values['poster_name'] : null)
diff --git a/main/lp/learnpath.class.php b/main/lp/learnpath.class.php
index f6cf460581..0fd6bce524 100755
--- a/main/lp/learnpath.class.php
+++ b/main/lp/learnpath.class.php
@@ -13224,7 +13224,7 @@ EOD;
                 /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
                 $thread = $repo->find($threadId);
                 if ($thread) {
-                    $itemList['forum'][] = $thread->getForumId();
+                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
                     $threadList[] = $thread->getIid();
                 }
             }
diff --git a/main/webservices/cm_webservice_forum.php b/main/webservices/cm_webservice_forum.php
index 7126543809..cf01ca0e36 100755
--- a/main/webservices/cm_webservice_forum.php
+++ b/main/webservices/cm_webservice_forum.php
@@ -276,7 +276,7 @@ class WSCMForum extends WSCM
             $post
                 ->setPostTitle($title)
                 ->setPostText(isset($content) ? (api_html_entity_decode($content)) : null)
-                ->setThreadId($thread_id)
+                ->setThread($thread_id)
                 ->setForumId($forum_id)
                 ->setPosterId($user_id)
                 ->setPostDate($postDate)
diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20180927172830.php b/src/CoreBundle/Migrations/Schema/V200/Version20180927172830.php
new file mode 100644
index 0000000000..caa43daa68
--- /dev/null
+++ b/src/CoreBundle/Migrations/Schema/V200/Version20180927172830.php
@@ -0,0 +1,39 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\CoreBundle\Migrations\Schema\V200;
+
+use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
+use Doctrine\DBAL\Schema\Schema;
+
+/**
+ * Class Version20180927172830.
+ *
+ * Add foreing keys between forum category - forum - forum thread - forum post
+ *
+ * @package Chamilo\CoreBundle\Migrations\Schema\V200
+ */
+class Version20180927172830 extends AbstractMigrationChamilo
+{
+    /**
+     * @param Schema $schema
+     */
+    public function up(Schema $schema)
+    {
+        $this->addSql('UPDATE c_forum_post SET thread_id = NULL WHERE thread_id NOT IN (SELECT iid FROM c_forum_thread)');
+        $this->addSql('UPDATE c_forum_thread SET forum_id = NULL WHERE forum_id NOT IN (SELECT iid FROM c_forum_forum)');
+        $this->addSql('UPDATE c_forum_forum SET forum_category = NULL WHERE forum_category NOT IN (SELECT iid FROM c_forum_category)');
+
+        $this->addSql('ALTER TABLE c_forum_post ADD CONSTRAINT FK_B5BEF559E2904019 FOREIGN KEY (thread_id) REFERENCES c_forum_thread (iid)');
+        $this->addSql('ALTER TABLE c_forum_forum ADD CONSTRAINT FK_47A9C9921BF9426 FOREIGN KEY (forum_category) REFERENCES c_forum_category (iid)');
+        $this->addSql('CREATE INDEX IDX_47A9C9921BF9426 ON c_forum_forum (forum_category)');
+        $this->addSql('ALTER TABLE c_forum_thread ADD CONSTRAINT FK_5DA7884C29CCBAD0 FOREIGN KEY (forum_id) REFERENCES c_forum_forum (iid)');
+    }
+
+    /**
+     * @param Schema $schema
+     */
+    public function down(Schema $schema)
+    {
+    }
+}
\ No newline at end of file
diff --git a/src/CourseBundle/Entity/CForumCategory.php b/src/CourseBundle/Entity/CForumCategory.php
index 9f27a1346b..9db253ff01 100644
--- a/src/CourseBundle/Entity/CForumCategory.php
+++ b/src/CourseBundle/Entity/CForumCategory.php
@@ -3,6 +3,7 @@
 
 namespace Chamilo\CourseBundle\Entity;
 
+use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -82,6 +83,13 @@ class CForumCategory
      */
     private $itemProperty;
 
+    /**
+     * @var ArrayCollection
+     *
+     * @ORM\OneToMany(targetEntity="Chamilo\CourseBundle\Entity\CForumForum", mappedBy="forumCategory")
+     */
+    private $forums;
+
     /**
      * Get iid.
      *
@@ -260,6 +268,16 @@ class CForumCategory
         return $this->cId;
     }
 
+    /**
+     * Get forums.
+     *
+     * @return ArrayCollection
+     */
+    public function getForums()
+    {
+        return $this->forums;
+    }
+
     /**
      * @param CItemProperty $itemProperty
      *
diff --git a/src/CourseBundle/Entity/CForumForum.php b/src/CourseBundle/Entity/CForumForum.php
index ea83687908..b58f357899 100644
--- a/src/CourseBundle/Entity/CForumForum.php
+++ b/src/CourseBundle/Entity/CForumForum.php
@@ -3,6 +3,7 @@
 
 namespace Chamilo\CourseBundle\Entity;
 
+use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -77,9 +78,10 @@ class CForumForum
     protected $forumLastPost;
 
     /**
-     * @var int
+     * @var CForumCategory|null
      *
-     * @ORM\Column(name="forum_category", type="integer", nullable=true)
+     * @ORM\ManyToOne(targetEntity="Chamilo\CourseBundle\Entity\CForumCategory", inversedBy="forums")
+     * @ORM\JoinColumn(name="forum_category", referencedColumnName="iid")
      */
     protected $forumCategory;
 
@@ -195,6 +197,13 @@ class CForumForum
      */
     protected $moderated;
 
+    /**
+     * @var ArrayCollection
+     *
+     * @ORM\OneToMany(targetEntity="Chamilo\CourseBundle\Entity\CForumThread", mappedBy="forum")
+     */
+    protected $threads;
+
     /**
      * Set forumTitle.
      *
@@ -318,11 +327,11 @@ class CForumForum
     /**
      * Set forumCategory.
      *
-     * @param int $forumCategory
+     * @param CForumCategory|null $forumCategory
      *
      * @return CForumForum
      */
-    public function setForumCategory($forumCategory)
+    public function setForumCategory(CForumCategory $forumCategory = null)
     {
         $this->forumCategory = $forumCategory;
 
@@ -332,7 +341,7 @@ class CForumForum
     /**
      * Get forumCategory.
      *
-     * @return int
+     * @return CForumCategory|null
      */
     public function getForumCategory()
     {
@@ -762,4 +771,24 @@ class CForumForum
 
         return $this;
     }
+
+    /**
+     * Get iid.
+     *
+     * @return int
+     */
+    public function getIid()
+    {
+        return $this->iid;
+    }
+
+    /**
+     * Get threads.
+     *
+     * @return ArrayCollection
+     */
+    public function getThreads()
+    {
+        return $this->threads;
+    }
 }
diff --git a/src/CourseBundle/Entity/CForumPost.php b/src/CourseBundle/Entity/CForumPost.php
index 7fa8cf2451..1d982dbeee 100644
--- a/src/CourseBundle/Entity/CForumPost.php
+++ b/src/CourseBundle/Entity/CForumPost.php
@@ -65,11 +65,12 @@ class CForumPost
     protected $postText;
 
     /**
-     * @var int
+     * @var CForumThread|null
      *
-     * @ORM\Column(name="thread_id", type="integer", nullable=true)
+     * @ORM\ManyToOne(targetEntity="Chamilo\CourseBundle\Entity\CForumThread", inversedBy="posts")
+     * @ORM\JoinColumn(name="thread_id", referencedColumnName="iid")
      */
-    protected $threadId;
+    protected $thread;
 
     /**
      * @var int
@@ -176,27 +177,27 @@ class CForumPost
     }
 
     /**
-     * Set threadId.
+     * Set thread.
      *
-     * @param int $threadId
+     * @param CForumThread|null $thread
      *
      * @return CForumPost
      */
-    public function setThreadId($threadId)
+    public function setThread(CForumThread $thread = null)
     {
-        $this->threadId = $threadId;
+        $this->thread = $thread;
 
         return $this;
     }
 
     /**
-     * Get threadId.
+     * Get thread.
      *
-     * @return int
+     * @return CForumThread|null
      */
-    public function getThreadId()
+    public function getThread()
     {
-        return $this->threadId;
+        return $this->thread;
     }
 
     /**
diff --git a/src/CourseBundle/Entity/CForumThread.php b/src/CourseBundle/Entity/CForumThread.php
index 4b5448317f..6f88d271c8 100644
--- a/src/CourseBundle/Entity/CForumThread.php
+++ b/src/CourseBundle/Entity/CForumThread.php
@@ -3,6 +3,7 @@
 
 namespace Chamilo\CourseBundle\Entity;
 
+use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -50,11 +51,12 @@ class CForumThread
     protected $threadTitle;
 
     /**
-     * @var int
+     * @var CForumForum|null
      *
-     * @ORM\Column(name="forum_id", type="integer", nullable=true)
+     * @ORM\ManyToOne(targetEntity="Chamilo\CourseBundle\Entity\CForumForum", inversedBy="threads")
+     * @ORM\JoinColumn(name="forum_id", referencedColumnName="iid")
      */
-    protected $forumId;
+    protected $forum;
 
     /**
      * @var int
@@ -161,6 +163,13 @@ class CForumThread
      */
     protected $lpItemId;
 
+    /**
+     * @var ArrayCollection
+     *
+     * @ORM\OneToMany(targetEntity="Chamilo\CourseBundle\Entity\CForumPost", mappedBy="thread")
+     */
+    protected $posts;
+
     /**
      * Constructor.
      */
@@ -218,15 +227,15 @@ class CForumThread
     }
 
     /**
-     * Set forumId.
+     * Set forum.
      *
-     * @param int $forumId
+     * @param CForumForum|null $forum
      *
      * @return CForumThread
      */
-    public function setForumId($forumId)
+    public function setForum(CForumForum $forum = null)
     {
-        $this->forumId = $forumId;
+        $this->forum = $forum;
 
         return $this;
     }
@@ -234,11 +243,11 @@ class CForumThread
     /**
      * Get forumId.
      *
-     * @return int
+     * @return CForumForum|null
      */
-    public function getForumId()
+    public function getForum()
     {
-        return $this->forumId;
+        return $this->forum;
     }
 
     /**
@@ -634,4 +643,12 @@ class CForumThread
     {
         return $this->iid;
     }
+
+    /**
+     * @return ArrayCollection
+     */
+    public function getPosts()
+    {
+        return $this->posts;
+    }
 }

From 2b494a0beb2eb83f3f2550c30264b3d346442188 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Thu, 27 Sep 2018 18:03:06 -0500
Subject: [PATCH 11/32] Add repository for forum #2644

---
 src/CourseBundle/Entity/CForumForum.php       |  29 ++++-
 .../Repository/CForumForumRepository.php      | 115 ++++++++++++++++++
 2 files changed, 143 insertions(+), 1 deletion(-)
 create mode 100644 src/CourseBundle/Repository/CForumForumRepository.php

diff --git a/src/CourseBundle/Entity/CForumForum.php b/src/CourseBundle/Entity/CForumForum.php
index b58f357899..a0404adf3d 100644
--- a/src/CourseBundle/Entity/CForumForum.php
+++ b/src/CourseBundle/Entity/CForumForum.php
@@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
  *      @ORM\Index(name="course", columns={"c_id"})
  *  }
  * )
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Chamilo\CourseBundle\Repository\CForumForumRepository")
  */
 class CForumForum
 {
@@ -197,6 +197,11 @@ class CForumForum
      */
     protected $moderated;
 
+    /**
+     * @var CItemProperty
+     */
+    protected $itemProperty;
+
     /**
      * @var ArrayCollection
      *
@@ -791,4 +796,26 @@ class CForumForum
     {
         return $this->threads;
     }
+
+    /**
+     * Set itemProperty.
+     *
+     * @param CItemProperty $itemProperty
+     *
+     * @return CForumForum
+     */
+    public function setItemProperty(CItemProperty $itemProperty)
+    {
+        $this->itemProperty = $itemProperty;
+
+        return $this;
+    }
+
+    /**
+     * @return CItemProperty
+     */
+    public function getItemProperty()
+    {
+        return $this->itemProperty;
+    }
 }
diff --git a/src/CourseBundle/Repository/CForumForumRepository.php b/src/CourseBundle/Repository/CForumForumRepository.php
new file mode 100644
index 0000000000..5ea73d324f
--- /dev/null
+++ b/src/CourseBundle/Repository/CForumForumRepository.php
@@ -0,0 +1,115 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\CourseBundle\Repository;
+
+use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CourseBundle\Entity\CForumForum;
+use Chamilo\CourseBundle\Entity\CItemProperty;
+use Doctrine\ORM\EntityRepository;
+
+/**
+ * Class CForumForumRepository.
+ *
+ * @package Chamilo\CourseBundle\Repository
+ */
+class CForumForumRepository extends EntityRepository
+{
+    /**
+     * @param bool         $isAllowedToEdit
+     * @param Course       $course
+     * @param Session|null $session
+     * @param bool         $includeGroupsForums
+     *
+     * @return array
+     *
+     * @todo Remove api_get_session_condition
+     */
+    public function findAllInCourse(
+        $isAllowedToEdit,
+        Course $course,
+        Session $session = null,
+        $includeGroupsForums = true
+    ): array {
+        $conditionSession = api_get_session_condition(
+            $session ? $session->getId() : 0,
+            true,
+            true,
+            'f.sessionId'
+        );
+        $conditionVisibility = $isAllowedToEdit ? 'ip.visibility != 2' : 'ip.visibility = 1';
+        $conditionGroups = $includeGroupsForums
+            ? 'AND (f.forumOfGroup = 0 OR f.forumOfGroup IS NULL)'
+            : '';
+
+        $dql = "SELECT ip, f
+            FROM ChamiloCourseBundle:CForumForum AS f
+            INNER JOIN ChamiloCourseBundle:CItemProperty AS ip
+                WITH (f.iid = ip.ref AND f.cId = ip.course)
+            WHERE
+                ip.tool = :tool AND
+                f.cId = :course
+                $conditionSession AND
+                $conditionVisibility
+                $conditionGroups
+            ORDER BY f.forumOrder ASC";
+
+        $result = $this
+            ->_em
+            ->createQuery($dql)
+            ->setParameters(['course' => $course, 'tool' => TOOL_FORUM])
+            ->getResult();
+
+        $forums = [];
+
+        for ($i = 0; $i < count($result); $i += 2) {
+            /** @var CItemProperty $ip */
+            $ip = $result[$i];
+            /** @var CForumForum $f */
+            $f = $result[$i + 1];
+            $f->setItemProperty($ip);
+
+            $forums[] = $f;
+        }
+
+        return $forums;
+    }
+
+    /**
+     * @param int    $id
+     * @param Course $course
+     *
+     * @return CForumForum
+     */
+    public function findOneInCourse($id, Course $course)
+    {
+        $dql = "SELECT ip, f
+            FROM ChamiloCourseBundle:CForumForum AS f
+            INNER JOIN ChamiloCourseBundle:CItemProperty AS ip
+                WITH (f.iid = ip.ref AND f.cId = ip.course)
+            WHERE
+                f.iid = :id
+                ip.tool = :tool AND
+                f.cId = :course AND
+                ip.visibility != 2";
+
+        $result = $this
+            ->_em
+            ->createQuery($dql)
+            ->setParameters(['id' => (int) $id, 'course' => $course, 'tool' => TOOL_FORUM])
+            ->getResult();
+
+        if (empty($result)) {
+            return null;
+        }
+
+        /** @var CItemProperty $ip */
+        $ip = $result[0];
+        /** @var CForumForum $f */
+        $f = $result[1];
+        $f->setItemProperty($ip);
+
+        return $f;
+    }
+}

From dea99072de3ba316e0088ff1faad54809211fc50 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Thu, 27 Sep 2018 18:04:03 -0500
Subject: [PATCH 12/32] GraphQL add query for course forum tool #2644

---
 src/ApiBundle/GraphQL/Map/EnumMap.php         |  1 +
 src/ApiBundle/GraphQL/Map/QueryMap.php        | 28 +++++++++
 src/ApiBundle/GraphQL/Map/UnionMap.php        |  2 +
 .../GraphQL/Resolver/CourseResolver.php       | 19 ++++++
 .../Resources/config/schema.types.graphql     | 58 ++++++++++++++++++-
 5 files changed, 105 insertions(+), 3 deletions(-)

diff --git a/src/ApiBundle/GraphQL/Map/EnumMap.php b/src/ApiBundle/GraphQL/Map/EnumMap.php
index 840e0c30f7..b130999b76 100644
--- a/src/ApiBundle/GraphQL/Map/EnumMap.php
+++ b/src/ApiBundle/GraphQL/Map/EnumMap.php
@@ -41,6 +41,7 @@ class EnumMap extends ResolverMap implements ContainerAwareInterface
                 'TOOL_COURSE_DESCRIPTION' => TOOL_COURSE_DESCRIPTION,
                 'TOOL_ANNOUNCEMENT' => TOOL_ANNOUNCEMENT,
                 'TOOL_NOTEBOOK' => TOOL_NOTEBOOK,
+                'TOOL_FORUM' => TOOL_FORUM,
             ],
         ];
     }
diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index 9027781709..90adea1b6a 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -10,6 +10,7 @@ use Chamilo\CoreBundle\Entity\Message;
 use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionCategory;
 use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
+use Chamilo\CourseBundle\Entity\CForumCategory;
 use Chamilo\CourseBundle\Entity\CNotebook;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\UserBundle\Entity\User;
@@ -163,6 +164,33 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                     return $note->getIid();
                 },
             ],
+            'ToolForums' => [
+                self::RESOLVE_FIELD => function (
+                    CTool $tool,
+                    Argument $args,
+                    \ArrayObject $context,
+                    ResolveInfo $info
+                ) {
+                    if ('categories' === $info->fieldName) {
+                        $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                        return $resolver->getForumCategories($context);
+                    }
+
+                    return $this->resolveField($info->fieldName, $tool);
+                },
+            ],
+            'CourseForumCategory' => [
+                'id' => function (CForumCategory $category) {
+                    return $category->getIid();
+                },
+                'title' => function (CForumCategory $category) {
+                    return $category->getCatTitle();
+                },
+                'comment' => function (CForumCategory $category) {
+                    return $category->getCatComment();
+                },
+            ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
                     Session $session,
diff --git a/src/ApiBundle/GraphQL/Map/UnionMap.php b/src/ApiBundle/GraphQL/Map/UnionMap.php
index 91634ccdc1..4926f6aa81 100644
--- a/src/ApiBundle/GraphQL/Map/UnionMap.php
+++ b/src/ApiBundle/GraphQL/Map/UnionMap.php
@@ -32,6 +32,8 @@ class UnionMap extends ResolverMap implements ContainerAwareInterface
                             return 'ToolAnnouncements';
                         case TOOL_NOTEBOOK:
                             return 'ToolNotebook';
+                        case TOOL_FORUM:
+                            return 'ToolForums';
                     }
                 },
             ],
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index 2bc1347fa7..89721209dd 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -8,6 +8,7 @@ use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CourseBundle\Entity\CAnnouncement;
+use Chamilo\CourseBundle\Entity\CForumCategory;
 use Chamilo\CourseBundle\Entity\CItemProperty;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\CourseBundle\Repository\CNotebookRepository;
@@ -217,4 +218,22 @@ class CourseResolver implements ContainerAwareInterface
 
         return $notebooks;
     }
+
+    /**
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getForumCategories(\ArrayObject $context): array
+    {
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+        /** @var Session $session */
+        $session = $context->offsetGet('session');
+
+        $catRepo = $this->em->getRepository('ChamiloCourseBundle:CForumCategory');
+        $cats = $catRepo->findAllInCourse(false, $course, $session);
+
+        return $cats;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index 77e72182a9..7d7a9c77f0 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -116,7 +116,7 @@ type CourseDescription {
     descriptionType: Int
 }
 
-"Personal notes relevant to student or coursework"
+"Personal notes relevant to student or coursework."
 type ToolNotebook {
     name: String
     category: String
@@ -125,7 +125,7 @@ type ToolNotebook {
     notes: [CourseNote!]
 }
 
-"Note by user in notebook tool"
+"Note by user in notebook tool."
 type CourseNote {
     id: Int
     title: String
@@ -154,6 +154,57 @@ type CourseAnnouncement {
     lastUpdateDate: DateTime
 }
 
+"Course forum tool."
+type ToolForums {
+    name: String
+    category: String
+    image: String
+    customIcon: String
+    categories: [CourseForumCategory!]
+}
+
+type CourseForumCategory {
+    id: Int
+    title: String
+    comment: String
+    locked: Int
+    forums: [CourseForum!]
+}
+
+type CourseForum {
+    id: Int
+    title: String
+    comment: String
+    image: String
+    numberOfThreads: Int
+    numberOfPosts: Int
+    threads: [CourseForumThread!]
+}
+
+type CourseForumThread {
+    id: Int
+    title: String
+    userPoster: User
+    date: DateTime
+    sticky: Boolean
+    locked: Int
+    numberOfViews: Int
+    numberOfReplies: Int
+    closeDate: DateTime
+    posts: [CourseForumPost!]
+}
+
+type CourseForumPost {
+    id: Int
+    title: String
+    text: String
+    userPoster: User
+    date: DateTime
+    parent: CourseForumPost
+    visible: Boolean
+    status: Int
+}
+
 "A session registered on the platform."
 type Session {
     "The unique ID of the session."
@@ -185,7 +236,7 @@ type SessionCategory {
 
 # Unions
 
-union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook
+union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums
 
 # Enums
 
@@ -222,6 +273,7 @@ enum CourseToolType {
     TOOL_COURSE_DESCRIPTION
     TOOL_ANNOUNCEMENT
     TOOL_NOTEBOOK
+    TOOL_FORUM
 }
 
 # Scalars

From 2f4f1350841ebf27da58a65c08edfb67e1117916 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Fri, 28 Sep 2018 09:14:22 -0500
Subject: [PATCH 13/32] Filter forums by category in course #2644

---
 .../Repository/CForumForumRepository.php      | 34 +++++++++++--------
 1 file changed, 19 insertions(+), 15 deletions(-)

diff --git a/src/CourseBundle/Repository/CForumForumRepository.php b/src/CourseBundle/Repository/CForumForumRepository.php
index 5ea73d324f..e7f416a9bb 100644
--- a/src/CourseBundle/Repository/CForumForumRepository.php
+++ b/src/CourseBundle/Repository/CForumForumRepository.php
@@ -5,6 +5,7 @@ namespace Chamilo\CourseBundle\Repository;
 
 use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CourseBundle\Entity\CForumCategory;
 use Chamilo\CourseBundle\Entity\CForumForum;
 use Chamilo\CourseBundle\Entity\CItemProperty;
 use Doctrine\ORM\EntityRepository;
@@ -17,17 +18,19 @@ use Doctrine\ORM\EntityRepository;
 class CForumForumRepository extends EntityRepository
 {
     /**
-     * @param bool         $isAllowedToEdit
-     * @param Course       $course
-     * @param Session|null $session
-     * @param bool         $includeGroupsForums
+     * @param bool           $isAllowedToEdit
+     * @param CForumCategory $category
+     * @param Course         $course
+     * @param Session|null   $session
+     * @param bool           $includeGroupsForums
      *
      * @return array
      *
      * @todo Remove api_get_session_condition
      */
-    public function findAllInCourse(
+    public function findAllInCourseByCategory(
         $isAllowedToEdit,
+        CForumCategory $category,
         Course $course,
         Session $session = null,
         $includeGroupsForums = true
@@ -43,11 +46,12 @@ class CForumForumRepository extends EntityRepository
             ? 'AND (f.forumOfGroup = 0 OR f.forumOfGroup IS NULL)'
             : '';
 
-        $dql = "SELECT ip, f
+        $dql = "SELECT f, ip
             FROM ChamiloCourseBundle:CForumForum AS f
             INNER JOIN ChamiloCourseBundle:CItemProperty AS ip
                 WITH (f.iid = ip.ref AND f.cId = ip.course)
             WHERE
+                f.forumCategory = :category AND
                 ip.tool = :tool AND
                 f.cId = :course
                 $conditionSession AND
@@ -58,16 +62,16 @@ class CForumForumRepository extends EntityRepository
         $result = $this
             ->_em
             ->createQuery($dql)
-            ->setParameters(['course' => $course, 'tool' => TOOL_FORUM])
+            ->setParameters(['category' => $category, 'course' => $course, 'tool' => TOOL_FORUM])
             ->getResult();
 
         $forums = [];
 
         for ($i = 0; $i < count($result); $i += 2) {
-            /** @var CItemProperty $ip */
-            $ip = $result[$i];
             /** @var CForumForum $f */
-            $f = $result[$i + 1];
+            $f = $result[$i];
+            /** @var CItemProperty $ip */
+            $ip = $result[$i + 1];
             $f->setItemProperty($ip);
 
             $forums[] = $f;
@@ -84,12 +88,12 @@ class CForumForumRepository extends EntityRepository
      */
     public function findOneInCourse($id, Course $course)
     {
-        $dql = "SELECT ip, f
+        $dql = "SELECT f, ip
             FROM ChamiloCourseBundle:CForumForum AS f
             INNER JOIN ChamiloCourseBundle:CItemProperty AS ip
                 WITH (f.iid = ip.ref AND f.cId = ip.course)
             WHERE
-                f.iid = :id
+                f.iid = :id AND
                 ip.tool = :tool AND
                 f.cId = :course AND
                 ip.visibility != 2";
@@ -104,10 +108,10 @@ class CForumForumRepository extends EntityRepository
             return null;
         }
 
-        /** @var CItemProperty $ip */
-        $ip = $result[0];
         /** @var CForumForum $f */
-        $f = $result[1];
+        $f = $result[0];
+        /** @var CItemProperty $ip */
+        $ip = $result[1];
         $f->setItemProperty($ip);
 
         return $f;

From 779f195de7d1748a3ab3d0a7321063d2940a0128 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Fri, 28 Sep 2018 10:39:49 -0500
Subject: [PATCH 14/32] GraphQL add query for course forums #2644

---
 src/ApiBundle/GraphQL/Map/QueryMap.php        | 34 ++++++++++++++-
 .../GraphQL/Resolver/CourseResolver.php       | 41 +++++++++++++++++++
 .../Resources/config/schema.types.graphql     |  1 +
 3 files changed, 74 insertions(+), 2 deletions(-)

diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index 90adea1b6a..f108ca5d0e 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -11,6 +11,7 @@ use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionCategory;
 use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
 use Chamilo\CourseBundle\Entity\CForumCategory;
+use Chamilo\CourseBundle\Entity\CForumForum;
 use Chamilo\CourseBundle\Entity\CNotebook;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\UserBundle\Entity\User;
@@ -171,12 +172,16 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                     \ArrayObject $context,
                     ResolveInfo $info
                 ) {
-                    if ('categories' === $info->fieldName) {
-                        $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
 
+                    if ('categories' === $info->fieldName) {
                         return $resolver->getForumCategories($context);
                     }
 
+                    if ('forum' === $info->fieldName) {
+                        return $resolver->getForum($args['id'], $context);
+                    }
+
                     return $this->resolveField($info->fieldName, $tool);
                 },
             ],
@@ -190,6 +195,31 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                 'comment' => function (CForumCategory $category) {
                     return $category->getCatComment();
                 },
+                'forums' => function (CForumCategory $category, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                    return $resolver->getForums($category, $context);
+                },
+            ],
+            'CourseForum' => [
+                'id' => function (CForumForum $forum) {
+                    return $forum->getIid();
+                },
+                'title' => function (CForumForum $forum) {
+                    return $forum->getForumTitle();
+                },
+                'comment' => function (CForumForum $forum) {
+                    return $forum->getForumComment();
+                },
+                'numberOfThreads' => function (CForumForum $forum) {
+                    return (int) $forum->getForumThreads();
+                },
+                'numberOfPosts' => function (CForumForum $forum) {
+                    return (int) $forum->getForumPosts();
+                },
+                'threads' => function (CForumForum $forum) {
+                    return [];
+                },
             ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index 89721209dd..5cc2f10c28 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -9,6 +9,7 @@ use Chamilo\CoreBundle\Entity\Session;
 use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CourseBundle\Entity\CAnnouncement;
 use Chamilo\CourseBundle\Entity\CForumCategory;
+use Chamilo\CourseBundle\Entity\CForumForum;
 use Chamilo\CourseBundle\Entity\CItemProperty;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\CourseBundle\Repository\CNotebookRepository;
@@ -236,4 +237,44 @@ class CourseResolver implements ContainerAwareInterface
 
         return $cats;
     }
+
+    /**
+     * @param CForumCategory $category
+     * @param \ArrayObject   $context
+     *
+     * @return array
+     */
+    public function getForums(CForumCategory $category, \ArrayObject $context): array
+    {
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+        /** @var Session $session */
+        $session = $context->offsetGet('session');
+
+        $forumRepo = $this->em->getRepository('ChamiloCourseBundle:CForumForum');
+        $forums = $forumRepo->findAllInCourseByCategory(false, $category, $course, $session);
+
+        return $forums;
+    }
+
+    /**
+     * @param int          $id
+     * @param \ArrayObject $context
+     *
+     * @return CForumForum
+     */
+    public function getForum($id, \ArrayObject $context)
+    {
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+
+        $forumRepo = $this->em->getRepository('ChamiloCourseBundle:CForumForum');
+        $forum = $forumRepo->findOneInCourse($id, $course);
+
+        if (empty($forum)) {
+            throw new UserError($this->translator->trans('Forum not found in this course.'));
+        }
+
+        return $forum;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index 7d7a9c77f0..843c08f5cc 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -161,6 +161,7 @@ type ToolForums {
     image: String
     customIcon: String
     categories: [CourseForumCategory!]
+    forum(id: Int!): CourseForum
 }
 
 type CourseForumCategory {

From 09af77248e80ccb986d55ae1a12f30f46d943194 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Fri, 28 Sep 2018 11:00:07 -0500
Subject: [PATCH 15/32] Fix query for thread posts #2644

See 0aeedf0ac484603ada68eb15bf48b38f69e6e9c1
---
 main/forum/forumfunction.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/main/forum/forumfunction.inc.php b/main/forum/forumfunction.inc.php
index 0ef86122cb..f2d564faf9 100755
--- a/main/forum/forumfunction.inc.php
+++ b/main/forum/forumfunction.inc.php
@@ -2066,7 +2066,7 @@ function getPosts(
 
     $criteria = Criteria::create();
     $criteria
-        ->where(Criteria::expr()->eq('threadId', $threadId))
+        ->where(Criteria::expr()->eq('thread', $threadId))
         ->andWhere(Criteria::expr()->eq('cId', $forumInfo['c_id']))
         ->andWhere($visibleCriteria)
     ;

From 8b0cdf941373630a09723bfa7db44f1429ee883c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Arag=C3=B3n?= <aragcar@gmail.com>
Date: Fri, 28 Sep 2018 12:13:59 -0500
Subject: [PATCH 16/32] update box card courses - refs #2681

---
 assets/css/base.css                           |  25 ++-
 assets/css/scss/_base.scss                    |  35 +++-
 assets/css/scss/_sidebar.scss                 |   8 +-
 assets/css/scss/_variables.scss               |   7 +-
 assets/css/themes/chamilo/default.css         |  13 --
 .../grid_courses_without_category.html.twig   | 155 ++++++++++-------
 public/img/session_default.png                | Bin 3031 -> 905 bytes
 .../default/layout/hot_course_item.html.twig  |  37 ++--
 .../default/layout/hot_courses.html.twig      |   6 +-
 .../Resources/views/Layout/topbar.html.twig   | 161 +-----------------
 .../Resources/views/Macros/box.html.twig      |   2 +-
 11 files changed, 162 insertions(+), 287 deletions(-)

diff --git a/assets/css/base.css b/assets/css/base.css
index 6b9eb4c835..d6f5883841 100644
--- a/assets/css/base.css
+++ b/assets/css/base.css
@@ -5950,7 +5950,17 @@ div#chat-remote-video video {
     padding-top: 10px;
     border-bottom: 1px solid #ECF0F1;
 }
+.toolbar-edit {
+    display: inline-block;
+    width: 100%;
+    margin-top: 10px;
+}
 
+.bar-progress {
+    display: inline-block;
+    width: 100%;
+}
+/*
 .grid-courses .items .course-student-info {
     background-color: #d9edf7;
     border: 1px solid #bce8f1;
@@ -5965,19 +5975,7 @@ div#chat-remote-video video {
     color: #666;
 }
 
-.toolbar-edit {
-    display: inline-block;
-    width: 100%;
-    margin-top: 10px;
-}
-
-.bar-progress {
-    display: inline-block;
-    width: 100%;
-}
-
 .grid-courses .items {
-    /* position: relative; */
     vertical-align: top;
     white-space: normal;
     box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
@@ -6191,7 +6189,6 @@ div#chat-remote-video video {
     color: #fff;
 }
 
-/*---- */
 .grid-courses .items .block-author {
     display: flex;
     margin: 5px 0 5px;
@@ -6227,7 +6224,7 @@ div#chat-remote-video video {
     margin-top: 15px;
     margin-bottom: 15px;
 }
-
+*/
 .title-session,
 .title-courses {
     text-align: center;
diff --git a/assets/css/scss/_base.scss b/assets/css/scss/_base.scss
index b82c6a36d1..f30ec3c768 100644
--- a/assets/css/scss/_base.scss
+++ b/assets/css/scss/_base.scss
@@ -53,12 +53,12 @@ ul {
 }
 
 a {
-  color: $default-info;
+  color: $default-link;
 
   &:hover,
   &:focus {
     text-decoration: none;
-    color: darken($default-info, 10%);
+    color: darken($default-link, 20%);
   }
 
   &:focus {
@@ -253,4 +253,33 @@ footer {
   display: block;
   padding: 10px;
   position: relative;
-}
\ No newline at end of file
+}
+.carousel-item img{
+  max-width: 100%;
+  height: auto;
+}
+
+.card{
+  border: none;
+  .category{
+    position: absolute;
+    border-radius: 10px;
+    background-color: $default-yellow;
+    color: $default-black;
+    font-weight: bold;
+    padding: 0.2rem 0.8rem;
+    top: -0.8rem;
+    left: 0.5rem;
+  }
+  .card-body{
+    padding: 0.5rem 0.25rem;
+    .card-title{
+      .title{
+        font-size: 16px;
+        font-weight: bold;
+        line-height: 22px;
+      }
+    }
+  }
+}
+
diff --git a/assets/css/scss/_sidebar.scss b/assets/css/scss/_sidebar.scss
index b434c9a2fa..4f6195c13b 100644
--- a/assets/css/scss/_sidebar.scss
+++ b/assets/css/scss/_sidebar.scss
@@ -195,7 +195,7 @@
 
   .sidebar-link {
     &.active::before {
-      background: $default-warning;
+      background: $default-yellow;
       content: '';
       display: block;
       height: 100%;
@@ -227,7 +227,7 @@
           color: $default-white;
 
           &.dropdown-toggle {
-            background: $default-active;
+            background: $default-link;
           }
 
           .icon-holder {
@@ -278,14 +278,14 @@
       }
 
       &.active {
-        background: $default-active;
+        background: $default-link;
       }
 
       &:hover,
       &:focus {
         color: $default-info;
         text-decoration: none;
-        background: $default-active;
+        background: $default-link;
 
         .icon-holder {
           color: $default-info;
diff --git a/assets/css/scss/_variables.scss b/assets/css/scss/_variables.scss
index bcb2aa52fb..90c81fd6eb 100644
--- a/assets/css/scss/_variables.scss
+++ b/assets/css/scss/_variables.scss
@@ -34,9 +34,10 @@ $default-info         : #4abaff;
 $default-primary      : #7774e7;
 $default-success      : #37c936;
 $default-text-color   : #72777a;
-$default-warning      : #fc0;
-$default-white        : #fff;
-$default-active       : #006b9e;
+$default-yellow       : #FFCC00;
+$default-white        : #FFFFFF;
+$default-black        : #000000;
+$default-link         : rgb(0, 153, 255);
 $default-background   : #FFFFFF;
 $default-sidebar      : #0181bf;
 
diff --git a/assets/css/themes/chamilo/default.css b/assets/css/themes/chamilo/default.css
index d3753e731a..35c319342e 100644
--- a/assets/css/themes/chamilo/default.css
+++ b/assets/css/themes/chamilo/default.css
@@ -8,16 +8,3 @@
 
 body {
 }
-
-a {
-  color: #337AB7;
-  text-decoration: none;
-}
-a:hover,
-a:focus {
-  color: #2E75A3;
-  text-decoration: none;
-}
-a:focus {
-  outline: none;
-}
diff --git a/main/template/default/user_portal/grid_courses_without_category.html.twig b/main/template/default/user_portal/grid_courses_without_category.html.twig
index 18d9cdc297..62200b37ca 100644
--- a/main/template/default/user_portal/grid_courses_without_category.html.twig
+++ b/main/template/default/user_portal/grid_courses_without_category.html.twig
@@ -1,3 +1,91 @@
+{% import "ChamiloThemeBundle:Macros:box.html.twig" as macro %}
+
+{% if not courses is empty %}
+    <div class="course-columns">
+        <div class="row">
+
+
+    {% for item in courses %}
+        <div class="col-sm">
+        {% if item.title %}
+            {% set image %}
+                {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
+                    <img src="{{ item.image }}" class="card-img-top">
+                {% else %}
+                    <a title="{{ item.title }}" href="{{ item.link }}">
+                        <img src="{{ item.image }}" alt="{{ item.title }}" class="card-img-top">
+                    </a>
+                {% endif %}
+            {% endset %}
+
+            {% set content %}
+                <div class="card-title">
+                    <h5 class="title">
+                        {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
+                            {{ item.title_cut }} {{ item.code_course }}
+                        {% else %}
+                            <a title="{{ item.title }}" href="{{ item.link }}">{{ item.title_cut }} {{ item.code_course }}</a>
+                        {% endif %}
+                    </h5>
+                </div>
+                <div class="block-author">
+                    {% if item.teachers | length > 6 %}
+                        <a id="plist-{{ loop.index }}" data-trigger="focus" tabindex="0" role="button" class="btn btn-default panel_popover" data-toggle="popover" title="{{ 'CourseTeachers' | get_lang }}" data-html="true">
+                            <i class="fa fa-graduation-cap" aria-hidden="true"></i>
+                        </a>
+                        <div id="popover-content-plist-{{ loop.index }}" class="hide">
+                            {% for teacher in item.teachers %}
+                                <div class="popover-teacher">
+                                    <a href="{{ teacher.url }}" class="ajax"
+                                       data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                        <img src="{{ teacher.avatar }}"/>
+                                    </a>
+                                    <div class="teachers-details">
+                                        <h5>
+                                            <a href="{{ teacher.url }}" class="ajax"
+                                               data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                                {{ teacher.firstname }} {{ teacher.lastname }}
+                                            </a>
+                                        </h5>
+                                    </div>
+                                </div>
+                            {% endfor %}
+                        </div>
+                    {% else %}
+                        {% for teacher in item.teachers %}
+                            {% if item.teachers | length <= 2 %}
+                                <a href="{{ teacher.url }}" class="ajax"
+                                   data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                    <img src="{{ teacher.avatar }}"/>
+                                </a>
+                                <div class="teachers-details">
+                                    <h5>
+                                        <a href="{{ teacher.url }}" class="ajax"
+                                           data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                            {{ teacher.firstname }} {{ teacher.lastname }}
+                                        </a>
+                                    </h5>
+                                    <p>{{ 'Teacher' | get_lang }}</p>
+                                </div>
+                            {% elseif item.teachers | length <= 6 %}
+                                <a href="{{ teacher.url }}" class="ajax"
+                                   data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                    <img src="{{ teacher.avatar }}"/>
+                                </a>
+                            {% endif %}
+                        {% endfor %}
+                    {% endif %}
+                </div>
+            {% endset %}
+            {{ macro.panel('', content, '', '', '', image) }}
+        {% endif %}
+        </div>
+    {% endfor %}
+        </div>
+    </div>
+{% endif %}
+
+
 {% if not courses is empty %}
     <div class="grid-courses">
         <div class="row">
@@ -8,13 +96,7 @@
                             {% if item.is_special_course %}
                                 <div class="pin">{{ item.icon }}</div>
                             {% endif %}
-                            {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
-                                <img src="{{ item.image }}" class="img-responsive">
-                            {% else %}
-                                <a title="{{ item.title }}" href="{{ item.link }}">
-                                    <img src="{{ item.image }}" alt="{{ item.title }}" class="img-responsive">
-                                </a>
-                            {% endif %}
+
                             {% if item.category != '' %}
                                 <span class="category">{{ item.category }}</span>
                                 <div class="cribbon"></div>
@@ -38,63 +120,8 @@
                             {% endif %}
                         </div>
                         <div class="description">
-                            <div class="block-title">
-                                <h4 class="title" title="{{ item.title }}">
-                                    {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
-                                        {{ item.title_cut }} {{ item.code_course }}
-                                    {% else %}
-                                        <a title="{{ item.title }}" href="{{ item.link }}">{{ item.title_cut }} {{ item.code_course }}</a>
-                                    {% endif %}
-                                </h4>
-                            </div>
-                            <div class="block-author">
-                                {% if item.teachers | length > 6 %}
-                                    <a id="plist-{{ loop.index }}" data-trigger="focus" tabindex="0" role="button" class="btn btn-default panel_popover" data-toggle="popover" title="{{ 'CourseTeachers' | get_lang }}" data-html="true">
-                                        <i class="fa fa-graduation-cap" aria-hidden="true"></i>
-                                    </a>
-                                    <div id="popover-content-plist-{{ loop.index }}" class="hide">
-                                        {% for teacher in item.teachers %}
-                                        <div class="popover-teacher">
-                                            <a href="{{ teacher.url }}" class="ajax"
-                                               data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                <img src="{{ teacher.avatar }}"/>
-                                            </a>
-                                            <div class="teachers-details">
-                                                <h5>
-                                                    <a href="{{ teacher.url }}" class="ajax"
-                                                       data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                        {{ teacher.firstname }} {{ teacher.lastname }}
-                                                    </a>
-                                                </h5>
-                                            </div>
-                                        </div>
-                                        {% endfor %}
-                                    </div>
-                                {% else %}
-                                    {% for teacher in item.teachers %}
-                                        {% if item.teachers | length <= 2 %}
-                                            <a href="{{ teacher.url }}" class="ajax"
-                                               data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                <img src="{{ teacher.avatar }}"/>
-                                            </a>
-                                            <div class="teachers-details">
-                                                <h5>
-                                                    <a href="{{ teacher.url }}" class="ajax"
-                                                       data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                        {{ teacher.firstname }} {{ teacher.lastname }}
-                                                    </a>
-                                                </h5>
-                                                <p>{{ 'Teacher' | get_lang }}</p>
-                                            </div>                                       
-                                        {% elseif item.teachers | length <= 6 %}
-                                            <a href="{{ teacher.url }}" class="ajax"
-                                               data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                <img src="{{ teacher.avatar }}"/>
-                                            </a>
-                                        {% endif %}
-                                    {% endfor %}
-                                {% endif %}
-                            </div>
+
+
                             {% if item.notifications %}
                                 <div class="notifications">{{ item.notifications }}</div>
                             {% endif %}
diff --git a/public/img/session_default.png b/public/img/session_default.png
index 294e8636a633623dfdbd57af68bd1a89da6d11ec..86a8a5f9a9af3ec5155a2cb6737fb83028b47acd 100644
GIT binary patch
literal 905
zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCKj2^jl8ZW)zXMWiN#5=*4F5rJ!QSPQ85o%D
zJY5_^D(1YsbCC0hfdI?Ft$`=}&Rx8z`9fW4mC}xI<0!uP&vwiK1@r$gGVH!qv%H?^
zfE+jX2>~4i=M4=!<~j<=y-AFM^I9C*)D<TrCNfGkv8Hf{O?cSGni3J=(3Sue*Z~w5
z6Put25y%5lt*j|O<bcXNIZ`Twm<VY?82w`p57=z1nn11q8Vs}^WRtWQ%stf51hNpr
z3m7)7`MHO)kqr?(_1O$YI*_0`$YgW}7-9%HVAvf##AL+6ed0r<B#a3R*TeUva41t@
zK{Bi+j_t$-pn*VhK}OgkOhZwIYUljvj7UcO;pc`KVZ+&o)oCF2JWqwW2dC3O%6O3t
z>oM(Ob%90_*4W1gI*?*JeP&p4z@-V4Vo(znc1?&RgDW*YtcIjRB%4q)K~pOvJ>%f~
Yk<swdnpfwP2+Ugyp00i_>zopr0GCA>9{>OV

literal 3031
zcmchZS5y;P7KRgAK&6bJp@v`qX@Y=sAtFdX5b2lBrHHf(Bm_c{fS?9J6HpNW0WpAp
z^w3SB6od2{h)8Im_bwnZ+%;=v&7GHdnt3??Is4yxowfJcFJJtf+lJgHgiinf0B$3=
zz6AgP#2>%MIa!ZYSmjf~@s)DN#LB>)H0VGca-s}-e(JybdEj0n4e@!<w`DlEX$aL!
z5B)NNrhX4=9S#4=h-e#&X`{z>jKp=0KI&#X>tf)$$CG*{Ui33x4$h>|W;19LnZwMy
z;pzAEnSznoqVMy?jK$LN<+6zt;^ch8_&jNHfjqTDVJ?4~Uimz;+Bmz`HnZM5ztJ(j
z*|o6Ux47EBv_7!BF}SiRbx()oxI>`ZEkk_(mR$^f9B>7~?U2Xg4E=e4dY97C$0jJ)
z$kYHd&dwolP9Vwg2L=E*K{e99ZiW7~oN*GDqb=~rXdvnRrE(!rsA;bXkVD<*=qT&p
z8zL#;6@9e3Q)!b-fAcry-K?GO_Ehs8-HRYo0F*Bpz{v&$umCxuVE~rn@E`i`=D$+^
zH2+fnr}@A8q%cu%)2xLuEc)UI2Hk7CV(-z-Bi9Vls^Axgv_{JRN|PE<e<s^mH2gj8
zkPn(<DT_v1o$=4GS<D^^lJ9y&JX2Tw?#2Fe???Mze9#GS^c`X_hg-{HjN^jCLtSq8
zlB4bqvD^}6+5?IxXTUH91C6YSm&_W8Uxj3;*KFNZ;5dhA<&?OqGdjqfmCiw4Onr!S
z&>@}0ezQ9@s0PxD@<(i#8wMv+#jHWv;!33zw}$DZKU!cil&2MBWj-2lCgt0B1zkeC
zMN^Y_dgw~kFRk<XV;>7K7<cG^T}B{LDnc;M|2-xtdn$-sFWJy7**T5m*f&ZwLPr+V
zO9YWo@#(Na<^*b%l~BJA&|pJoS?6mqP~M_IF>gN4-U6MTTcElNMU`FC1r|xZ9^I#R
zJoA(&Tm-pfB3+Z{6#M#Jbxrhz1{&@wJ^e#AYo98j(umisyM++fi|P_?oleWgdIP-d
zEi+3(3wGPr_Al*S{YiFiWU)7m5pY%@4uh3Jed;KYT3Uq7Hf_0y<fZY1eS?N0Gu5a4
z5*dQnv55X$ba=cLjHzhk)E35S9z3Vh-m>!E!Dsu^wA>sQ{`Yn>D%Co8_Dz$k?=Apd
zEjs%i!GH<)kKigQst=mTI*hK~Me+wSz=LGRv%l*#^l-GM0al$J)nCsjT<!UT<t+-7
z(}29XX+m03;u0xI#^zxAV+o4kJk89L0^7(a8nR)UY6^{E$keY)cpK`8UsFKv!+V@l
zg4t$y{Z=D=?F>ddZC#3_*7ISEHVE5@POh@6G}sb~(x~)dtk3!6`{dO`3p7>eZC%pK
zFb^uPYI+y<tSh*3BUPKd@xbn|e72U#o(jQV<hB?jVjR)DaMN*>He?gsHe!*-*W^*o
zt=7o1(>wj7HvzBZ)=$9~x95#NSHJB)`AW+CtVp6>=y=tG2w2EF=sWUM$PUcUs5f{~
z=HC5xx95im>(fJ#ttF%OD46P@4VmNfHR$>{L_Tw)9(A(SP1Iva{DL`>B?qA;*<1)_
z2KFk8Q3O0upbri5p^-U)tVSHL2iIRwKyi|13qv*rPsLb?n`<(_+~@bv78m#2Abl+z
zwx`<5ahhgI+t*Q*zRbt4!mk6J^Ht7=D40xt*pNw2v*&K5^X8O$S^?E8x=b4Xm2(0Y
zqwm6^Bqrfka-LA+jy6{<JJV06OIHpuS=+r}e&OYr!zsFKO4}0YLpC!mAc6}X$@*ON
zMUj?eOfOOMV}6jjs5Y?i=5x8N9SWdaCo*VR9Chtex&;3B#fYIFn8Fyax6;%NOyck!
z9!G^W2MT{c*<wyyfaK2nQhvT)%JS9-sEAr=lb7N@cPMmYekW}Wf}hd88T)Y!7o!q(
zUuEucg3k_YI2NdsCaxWt+PLYJ<kZ-i4Ugi5s}0_(I7-ww;>YI+EG#JyWO@fa-}af&
zX=cVTwJo+To%Ma&cLQo|+vuq9hfM^Am$MFMy5tvqhq<d`teJLAST+h@d}@}t%2Mag
ze<X-)qi#yH+m^IO1?4##Lj*4y8WbI<(EL=U&*xR%Dbl~j+GmYk?GXGW$@h|*+Nx=}
z$=>%Y<@Zt-aqZ)c3dv}%Z{4)f2vPrF(H%o%j4`IZWb6m9n0sP2c7H3_V>NFAR>0vH
zG{qq$q;d;|i10S)hNDG_FQaxkX@k_$rPLP93un@67Vb%uD~u!vgcPMC>1pyJmY|uz
z&osl1BIgPTT-B(3*<PupkfzU+5$##!`EQy!F2sc5Kfo@RwRH^EbAK4fN+*K_!`cY7
zkx{WIJ0HG6{e^M$#;Y^2k+bjC-dJ77fU${1J4N7wff7HQ@sP>WJ7p{KH4lEB;8QAi
z5t6z()I(Ei=UEh9+oK6t7^l}@7^Jyo=$YjwT*;)t%!|{(tBE0@S66tYOH0i3vc^tv
z2GzW_9d+5{d;&4;DQx9sX^315+{GWJQd0u_m2DsivHpt)#-=JXM~mNC>x}cGh|^iC
z_BZ8nnrc`q-lRB<df0-pJ@*InP_=&UgpfKuuFU)kn~rV22`lGg@@c|tj%pN6Yq6h8
zon}I5N8z0I(2KM6`3IhkE>cSQi;wZ^xjzL;T4k)))zzl@9PRdUC+lP;YX>BVwSIAs
z=KVDpYdg8EhkXevp605zprq2#rB08BUb4u5pSIC!QwE_?8<Ul*Gvq9VvFGP)58T3x
ze^$EXpG0nN7s~Hm>M8!t6Q@`kry~UI<9DM-44M6?hU(UTeC#v(Dx5D<^-5?MT3S03
zU184_UuxOC)+ErlqldO&{oCc;lT~0DlUmHPUFw#6SLh@HKEvtJ??R7Mq&p}%gAx4L
z26{6$pVG{YjC1_Jd(b*W@g5z#Ps%WQ-tk%ijtLL2D^%0B%sx_a)rkX6zLe~hw0!f@
zB-OE}yXHljkn358=O~lTzBc2~SB+{rR{81?_btK{c?0j9%OgR?JvM}*212j6C#fRY
zDi#+WdB~Eq(m6}TFRyzWV%P5XL-x`S>wom3*;9OxlXkon3aP-RPS=EO-2F`d&Ts=d
zbG3S+TZ*;0<Tl;>P*(A#v@uUxrnC=qr`X@y6<tNq2=F;_i@__@9(_e1=O<G+eB2@P
z4+mR@fZjGIS2k%;(<Me+6gGmGL|*)p4$Zw{AfoI*1nQ;yxV6?>>t$L!XWdKgCAn0r
z2=p$wFnHedsLl5Q^=CFgL-|9@y>yS8ScozqmVFsht2kvb_Kn4y>|#JJGfNhP?n?_e
z-L+f3iUbwqg?Ue?kIdbYl=W$J_HRj8vubs6_9O7{Ny6qnwGW%PlQ#XjL+@h!x-W!+
z{i(*~H9GnS(PrJb7iBFKa-A%`bOaw<D{ZdlI7d>^-8joRg|LO|c5h>gVz6)8z=Q{Q
z<`=-%pG4Pn(U|vsqo!knRR+6r&J?XcHQcGRGBqBSw`|IuACz3R3xYE|>eX#C;<yUV
zv?Pis>ra>uU~&oSdo$=KxNjN>#*gij*^~KSQK6L}8u%^tp~Zr&lC53#s2<Y5K<ci4
z*521;-M*ldvULM|yoD~^D^NhW^TzuCZyb)Yu;<`xEHi(<#^h+V*pZ0(ZTCnnC8fiC
ztYl+D?TC0&+S01z*Tw%Jvj0wSe*)nD5$pa6jCO~hr%#dAJF<V&A5$*C$l$hqiJt3&
FzXNfzg0TPq

diff --git a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
index b7318eefe3..a32ffed531 100644
--- a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
+++ b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
@@ -1,44 +1,31 @@
 {% import "ChamiloThemeBundle:Macros:box.html.twig" as macro %}
 
 {% autoescape false %}
-
 {% for item in hot_courses %}
-    {% if item.title %}
-
+    <div class="col-md-3">
+        {% if item.title %}
             {% set tools %}
                 {% if item.categoryName != '' %}
                     <span class="category">{{ item.categoryName }}</span>
                     <div class="cribbon"></div>
                 {% endif %}
                 <div class="user-actions">{{ item.description_button }}</div>
-
             {% endset %}
 
             {% set image %}
-                {% if item.is_registered %}
-                    <a title="{{ item.title}}" href="{{ item.course_public_url }}">
-                        <img src="{{ item.course_image_large }}" class="card-img-top" alt="{{ item.title }}">
-                    </a>
-                {% else %}
+                <div class="category">
+                    {{ item.categoryName }}
+                </div>
+                <a title="{{ item.title}}" href="{{ item.course_public_url }}">
                     <img src="{{ item.course_image_large }}" class="card-img-top" alt="{{ item.title }}">
-                {% endif %}
+                </a>
             {% endset %}
 
             {% set content %}
-
-
-
                 <div class="card-title">
-
-                    {% if item.is_registered %}
-                        <h5 class="title">
-                            <a title="{{ item.title }}" href="{{ item.course_public_url }}">{{ item.title_cut}}</a>
-                        </h5>
-                    {% else %}
-                        <h5 class="title" title="{{ item.title }}">
-                            {{ item.title_cut}}
-                        </h5>
-                    {% endif %}
+                    <h5 class="title">
+                        <a title="{{ item.title }}" href="{{ item.course_public_url }}">{{ item.title}}</a>
+                    </h5>
                 </div>
 
                 <div class="ranking">
@@ -81,10 +68,8 @@
                     </div>
                 </div>
             {% endset %}
-
             {{ macro.panel('', content, '', '', '', image) }}
-
-
     {% endif %}
+    </div>
 {% endfor %}
 {% endautoescape %}
diff --git a/src/CoreBundle/Resources/views/default/layout/hot_courses.html.twig b/src/CoreBundle/Resources/views/default/layout/hot_courses.html.twig
index ba0e7972b0..36cabb0e5b 100644
--- a/src/CoreBundle/Resources/views/default/layout/hot_courses.html.twig
+++ b/src/CoreBundle/Resources/views/default/layout/hot_courses.html.twig
@@ -34,10 +34,8 @@
                 {{ "HottestCourses"|get_lang }}
             </h5>
         </div>
-        <div class="card-columns">-
-
-                {% include '@ChamiloCore/default/layout/hot_course_item.html.twig' %}
-
+        <div class="row">
+            {% include '@ChamiloCore/default/layout/hot_course_item.html.twig' %}
         </div>
     {% endif %}
 {% endautoescape %}
\ No newline at end of file
diff --git a/src/ThemeBundle/Resources/views/Layout/topbar.html.twig b/src/ThemeBundle/Resources/views/Layout/topbar.html.twig
index 5c44fd32a4..77600c48c9 100644
--- a/src/ThemeBundle/Resources/views/Layout/topbar.html.twig
+++ b/src/ThemeBundle/Resources/views/Layout/topbar.html.twig
@@ -107,7 +107,7 @@
             {% if app.user is not null and is_granted('IS_AUTHENTICATED_FULLY') %}
             <ul class="nav-right">
                 <li class="btn-padding">
-                    <a class="btn btn-light btn-create-two btn-sm" href="{{ url('legacy_main', { 'name' : 'main/create_course/add_course.php' }) }}">
+                    <a class="btn btn-light btn-create-two btn-sm" href="{{ url('legacy_main', { 'name' : 'create_course/add_course.php' }) }}">
                         <i class="fa fa-plus fa-lg" aria-hidden="true"></i>
                         {{ "AddCourse"|trans }}
                     </a>
@@ -132,171 +132,22 @@
                     </a>
                 </li>
 
-                <li class="notifications dropdown">
+                <li class="notifications">
                     <span class="counter bgc-red">3</span>
-                    <a href="" class="dropdown-toggle no-after" data-toggle="dropdown">
+                    <a href="#" class="no-after" >
                         <i class="far fa-bell"></i>
                     </a>
-
-                    <ul class="dropdown-menu">
-                        <li class="pX-20 pY-15 bdB">
-                            <i class="ti-bell pR-10"></i>
-                            <span class="fsz-sm fw-600 c-grey-900">Notifications</span>
-                        </li>
-                        <li>
-                            <ul class="ovY-a pos-r scrollable lis-n p-0 m-0 fsz-sm">
-                                <li>
-                                    <a href="" class='peers fxw-nw td-n p-20 bdB c-grey-800 cH-blue bgcH-grey-100'>
-                                        <div class="peer mR-15">
-                                            <img class="w-3r bdrs-50p" src="https://randomuser.me/api/portraits/men/1.jpg" alt="">
-                                        </div>
-                                        <div class="peer peer-greed">
-                            <span>
-                              <span class="fw-500">John Doe</span>
-                              <span class="c-grey-600">liked your <span class="text-dark">post</span>
-                              </span>
-                            </span>
-                                            <p class="m-0">
-                                                <small class="fsz-xs">5 mins ago</small>
-                                            </p>
-                                        </div>
-                                    </a>
-                                </li>
-                                <li>
-                                    <a href="" class='peers fxw-nw td-n p-20 bdB c-grey-800 cH-blue bgcH-grey-100'>
-                                        <div class="peer mR-15">
-                                            <img class="w-3r bdrs-50p" src="https://randomuser.me/api/portraits/men/2.jpg" alt="">
-                                        </div>
-                                        <div class="peer peer-greed">
-                            <span>
-                              <span class="fw-500">Moo Doe</span>
-                              <span class="c-grey-600">liked your <span class="text-dark">cover image</span>
-                              </span>
-                            </span>
-                                            <p class="m-0">
-                                                <small class="fsz-xs">7 mins ago</small>
-                                            </p>
-                                        </div>
-                                    </a>
-                                </li>
-                                <li>
-                                    <a href="" class='peers fxw-nw td-n p-20 bdB c-grey-800 cH-blue bgcH-grey-100'>
-                                        <div class="peer mR-15">
-                                            <img class="w-3r bdrs-50p" src="https://randomuser.me/api/portraits/men/3.jpg" alt="">
-                                        </div>
-                                        <div class="peer peer-greed">
-                            <span>
-                              <span class="fw-500">Lee Doe</span>
-                              <span class="c-grey-600">commented on your <span class="text-dark">video</span>
-                              </span>
-                            </span>
-                                            <p class="m-0">
-                                                <small class="fsz-xs">10 mins ago</small>
-                                            </p>
-                                        </div>
-                                    </a>
-                                </li>
-                            </ul>
-                        </li>
-                        <li class="pX-20 pY-15 ta-c bdT">
-                    <span>
-                      <a href="" class="c-grey-600 cH-blue fsz-sm td-n">View All Notifications <i class="ti-angle-right fsz-xs mL-10"></i></a>
-                    </span>
-                        </li>
-                    </ul>
                 </li>
-                <li class="notifications dropdown">
+                <li class="notifications">
                     <span class="counter bgc-blue">3</span>
-                    <a href="" class="dropdown-toggle no-after" data-toggle="dropdown">
+                    <a href="" class="no-after">
                         <i class="far fa-envelope"></i>
                     </a>
-
-                    <ul class="dropdown-menu">
-                        <li class="pX-20 pY-15 bdB">
-                            <i class="ti-email pR-10"></i>
-                            <span class="fsz-sm fw-600">Emails</span>
-                        </li>
-                        <li>
-                            <ul class="ovY-a pos-r scrollable lis-n p-0 m-0 fsz-sm">
-                                <li>
-                                    <a href="" class='peers fxw-nw td-n p-20 bdB c-grey-800 cH-blue bgcH-grey-100'>
-                                        <div class="peer mR-15">
-                                            <img class="w-3r bdrs-50p" src="https://randomuser.me/api/portraits/men/1.jpg" alt="">
-                                        </div>
-                                        <div class="peer peer-greed">
-                                            <div>
-                                                <div class="peers jc-sb fxw-nw mB-5">
-                                                    <div class="peer">
-                                                        <p class="fw-500 mB-0">John Doe</p>
-                                                    </div>
-                                                    <div class="peer">
-                                                        <small class="fsz-xs">5 mins ago</small>
-                                                    </div>
-                                                </div>
-                                                <span class="c-grey-600 fsz-sm">
-                                Want to create your own customized data generator for your app...
-                              </span>
-                                            </div>
-                                        </div>
-                                    </a>
-                                </li>
-                                <li>
-                                    <a href="" class='peers fxw-nw td-n p-20 bdB c-grey-800 cH-blue bgcH-grey-100'>
-                                        <div class="peer mR-15">
-                                            <img class="w-3r bdrs-50p" src="https://randomuser.me/api/portraits/men/2.jpg" alt="">
-                                        </div>
-                                        <div class="peer peer-greed">
-                                            <div>
-                                                <div class="peers jc-sb fxw-nw mB-5">
-                                                    <div class="peer">
-                                                        <p class="fw-500 mB-0">Moo Doe</p>
-                                                    </div>
-                                                    <div class="peer">
-                                                        <small class="fsz-xs">15 mins ago</small>
-                                                    </div>
-                                                </div>
-                                                <span class="c-grey-600 fsz-sm">
-                                Want to create your own customized data generator for your app...
-                              </span>
-                                            </div>
-                                        </div>
-                                    </a>
-                                </li>
-                                <li>
-                                    <a href="" class='peers fxw-nw td-n p-20 bdB c-grey-800 cH-blue bgcH-grey-100'>
-                                        <div class="peer mR-15">
-                                            <img class="w-3r bdrs-50p" src="https://randomuser.me/api/portraits/men/3.jpg" alt="">
-                                        </div>
-                                        <div class="peer peer-greed">
-                                            <div>
-                                                <div class="peers jc-sb fxw-nw mB-5">
-                                                    <div class="peer">
-                                                        <p class="fw-500 mB-0">Lee Doe</p>
-                                                    </div>
-                                                    <div class="peer">
-                                                        <small class="fsz-xs">25 mins ago</small>
-                                                    </div>
-                                                </div>
-                                                <span class="c-grey-600 fsz-sm">
-                                Want to create your own customized data generator for your app...
-                              </span>
-                                            </div>
-                                        </div>
-                                    </a>
-                                </li>
-                            </ul>
-                        </li>
-                        <li class="pX-20 pY-15 ta-c bdT">
-                    <span>
-                      <a href="email.html" class="c-grey-600 cH-blue fsz-sm td-n">View All Email <i class="fs-xs ti-angle-right mL-10"></i></a>
-                    </span>
-                        </li>
-                    </ul>
                 </li>
                 <li class="dropdown">
                     <a href="" class="dropdown-toggle no-after peers fxw-nw ai-c lh-1" data-toggle="dropdown">
                         <div class="peer mR-10">
-                            <img class="w-2r bdrs-50p" src="{{ asset(app.user.avatarOrAnonymous(32)) }}" alt="{{ app.user.completeName }}">
+                            <img class="rounded-circle" src="{{ asset(app.user.avatarOrAnonymous(32)) }}" alt="{{ app.user.completeName }}">
                         </div>
                         <div class="peer">
                             <span class="fsz-sm">{{ app.user.completeName }}</span>
diff --git a/src/ThemeBundle/Resources/views/Macros/box.html.twig b/src/ThemeBundle/Resources/views/Macros/box.html.twig
index 9b9a7b2501..abffd7cde3 100644
--- a/src/ThemeBundle/Resources/views/Macros/box.html.twig
+++ b/src/ThemeBundle/Resources/views/Macros/box.html.twig
@@ -154,7 +154,7 @@
 
 {% macro panel(header, content, title, footer, subtitle, top_image) %}
     {% autoescape false %}
-        <div class="card" style="width: 20rem;">
+        <div class="card" >
             {% if header %}
                 <div class="card-header"> {{ header }} </div>
             {% endif %}

From 834ca71edeb3037b1cb65c180313973d40dbb946 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Arag=C3=B3n?= <aragcar@gmail.com>
Date: Fri, 28 Sep 2018 15:21:34 -0500
Subject: [PATCH 17/32] card animation shadow and before - refs #2681

---
 assets/css/scss/_base.scss                    | 36 +++++++++++++++++++
 .../default/layout/hot_course_item.html.twig  | 32 ++++++++---------
 .../Resources/views/Macros/box.html.twig      |  2 +-
 3 files changed, 51 insertions(+), 19 deletions(-)

diff --git a/assets/css/scss/_base.scss b/assets/css/scss/_base.scss
index f30ec3c768..75944e416b 100644
--- a/assets/css/scss/_base.scss
+++ b/assets/css/scss/_base.scss
@@ -261,6 +261,35 @@ footer {
 
 .card{
   border: none;
+  transform: perspective(1px) translateZ(0);
+  transition-property: box-shadow;
+  transition-duration: 0.3s;
+  box-shadow: 0 0 1px rgba(0, 0, 0, 0);
+  &:hover,
+  &:focus{
+    box-shadow: 0 18px 10px -10px rgba(0, 0, 0, 0.3);
+  }
+  &.card-line{
+    &:hover{
+      &:before{
+        transition: all .3s;
+        width: 100%;
+        left: 0;
+      }
+    }
+    &:before {
+      content: "";
+      margin: 0 auto;
+      width: 0;
+      height: 4px;
+      background: $default-link;
+      display: block;
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      transition: all .3s;
+    }
+  }
   .category{
     position: absolute;
     border-radius: 10px;
@@ -280,6 +309,13 @@ footer {
         line-height: 22px;
       }
     }
+    .card-author{
+      .details{
+        .name{
+          font-size: 12px;
+        }
+      }
+    }
   }
 }
 
diff --git a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
index a32ffed531..307dc97631 100644
--- a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
+++ b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
@@ -5,17 +5,15 @@
     <div class="col-md-3">
         {% if item.title %}
             {% set tools %}
-                {% if item.categoryName != '' %}
-                    <span class="category">{{ item.categoryName }}</span>
-                    <div class="cribbon"></div>
-                {% endif %}
                 <div class="user-actions">{{ item.description_button }}</div>
             {% endset %}
 
             {% set image %}
-                <div class="category">
-                    {{ item.categoryName }}
-                </div>
+                {% if item.categoryName != '' %}
+                    <div class="category">
+                        {{ item.categoryName }}
+                    </div>
+                {% endif %}
                 <a title="{{ item.title}}" href="{{ item.course_public_url }}">
                     <img src="{{ item.course_image_large }}" class="card-img-top" alt="{{ item.title }}">
                 </a>
@@ -32,28 +30,26 @@
                     {{ item.rating_html }}
                 </div>
 
-                <div class="block-author">
+                <div class="card-author d-flex flex-row bd-highlight mb-3">
                     {% for teacher in item.teachers %}
                         {% if item.teachers | length > 2 %}
                             <a href="{{ teacher.url }}" class="ajax" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
                                 <img src="{{ teacher.avatar }}" alt="{{ 'TeacherPicture' | get_lang }}" />
                             </a>
                         {% else %}
-                            <a href="{{ teacher.url }}" class="ajax" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                            <a href="{{ teacher.url }}" class="ajax p-2 bd-highlight" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
                                 <img src="{{ teacher.avatar }}" alt="{{ 'TeacherPicture' | get_lang }}" />
                             </a>
-                            <div class="teachers-details">
-                                <h5>
-                                    <a href="{{ teacher.url }}" class="ajax" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                            <div class="details p-2 bd-highlight">
+                                <a href="{{ teacher.url }}" class="ajax" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
                                         {{ teacher.firstname }} {{ teacher.lastname }}
-                                    </a>
-                                </h5>
-                                <p>{{ 'Teacher' | get_lang }}</p>
+                                </a>
+                                <div class="name">{{ 'Teacher' | get_lang }}</div>
                             </div>
                         {% endif %}
                     {% endfor %}
                 </div>
-
+                <!--
                 <div class="toolbar row">
                     <div class="col-sm-4">
                         {#{% if item.price %}#}
@@ -62,11 +58,11 @@
                     </div>
                     <div class="col-sm-8">
                         <div class="btn-group" role="group">
-                            {{ item.register_button }}
+                            {# item.register_button #}
                             {#{{ item.unsubscribe_button }}#}
                         </div>
                     </div>
-                </div>
+                </div> -->
             {% endset %}
             {{ macro.panel('', content, '', '', '', image) }}
     {% endif %}
diff --git a/src/ThemeBundle/Resources/views/Macros/box.html.twig b/src/ThemeBundle/Resources/views/Macros/box.html.twig
index abffd7cde3..a24c518edc 100644
--- a/src/ThemeBundle/Resources/views/Macros/box.html.twig
+++ b/src/ThemeBundle/Resources/views/Macros/box.html.twig
@@ -154,7 +154,7 @@
 
 {% macro panel(header, content, title, footer, subtitle, top_image) %}
     {% autoescape false %}
-        <div class="card" >
+        <div class="card card-line mt-3 mb-3">
             {% if header %}
                 <div class="card-header"> {{ header }} </div>
             {% endif %}

From aed00eabc54dc722511d38350c8ae2e534a3fe94 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Fri, 28 Sep 2018 15:27:33 -0500
Subject: [PATCH 18/32] Add repository for forum thread #2644

---
 src/CourseBundle/Entity/CForumThread.php      |  27 +++-
 src/CourseBundle/Entity/CGroupInfo.php        |  10 ++
 .../Repository/CForumThreadRepository.php     | 129 ++++++++++++++++++
 3 files changed, 165 insertions(+), 1 deletion(-)
 create mode 100644 src/CourseBundle/Repository/CForumThreadRepository.php

diff --git a/src/CourseBundle/Entity/CForumThread.php b/src/CourseBundle/Entity/CForumThread.php
index 6f88d271c8..d80aafec15 100644
--- a/src/CourseBundle/Entity/CForumThread.php
+++ b/src/CourseBundle/Entity/CForumThread.php
@@ -16,7 +16,7 @@ use Doctrine\ORM\Mapping as ORM;
  *      @ORM\Index(name="idx_forum_thread_forum_id", columns={"forum_id"})
  *  }
  * )
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Chamilo\CourseBundle\Repository\CForumThreadRepository")
  */
 class CForumThread
 {
@@ -170,6 +170,11 @@ class CForumThread
      */
     protected $posts;
 
+    /**
+     * @var CItemProperty
+     */
+    protected $itemProperty;
+
     /**
      * Constructor.
      */
@@ -651,4 +656,24 @@ class CForumThread
     {
         return $this->posts;
     }
+
+    /**
+     * @param CItemProperty $itemProperty
+     *
+     * @return CForumThread
+     */
+    public function setItemProperty(CItemProperty $itemProperty)
+    {
+        $this->itemProperty = $itemProperty;
+
+        return $this;
+    }
+
+    /**
+     * @return CItemProperty
+     */
+    public function getItemProperty()
+    {
+        return $this->itemProperty;
+    }
 }
diff --git a/src/CourseBundle/Entity/CGroupInfo.php b/src/CourseBundle/Entity/CGroupInfo.php
index 8e3b546d1f..ad28f80836 100644
--- a/src/CourseBundle/Entity/CGroupInfo.php
+++ b/src/CourseBundle/Entity/CGroupInfo.php
@@ -188,6 +188,16 @@ class CGroupInfo
      */
     protected $tutors;
 
+    /**
+     * Get iid.
+     *
+     * @return int
+     */
+    public function getIid()
+    {
+        return $this->iid;
+    }
+
     /**
      * Set name.
      *
diff --git a/src/CourseBundle/Repository/CForumThreadRepository.php b/src/CourseBundle/Repository/CForumThreadRepository.php
new file mode 100644
index 0000000000..2b48b58691
--- /dev/null
+++ b/src/CourseBundle/Repository/CForumThreadRepository.php
@@ -0,0 +1,129 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\CourseBundle\Repository;
+
+use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\CoreBundle\Entity\Session;
+use Chamilo\CourseBundle\Entity\CForumForum;
+use Chamilo\CourseBundle\Entity\CForumThread;
+use Chamilo\CourseBundle\Entity\CGroupInfo;
+use Chamilo\CourseBundle\Entity\CItemProperty;
+use Doctrine\ORM\EntityRepository;
+
+/**
+ * Class CForumThreadRepository.
+ *
+ * @package Chamilo\CourseBundle\Repository
+ */
+class CForumThreadRepository extends EntityRepository
+{
+    /**
+     * @param bool            $isAllowedToEdit
+     * @param CForumForum     $forum
+     * @param Course          $course
+     * @param CGroupInfo|null $group
+     * @param Session|null    $session
+     *
+     * @return array
+     *
+     * @todo Remove api_get_session_condition
+     */
+    public function findAllInCourseByForum(
+        $isAllowedToEdit,
+        CForumForum $forum,
+        Course $course,
+        CGroupInfo $group = null,
+        Session $session = null
+    ): array {
+        $conditionSession = api_get_session_condition(
+            $session ? $session->getId() : 0,
+            true,
+            false,
+            't.sessionId'
+        );
+        $conditionVisibility = $isAllowedToEdit ? 'ip.visibility != 2' : 'ip.visibility = 1';
+        $conditionGroup = $group
+            ? 'AND ip.group = '.$group->getIid()
+            : '';
+
+        $dql = "SELECT DISTINCT t, ip
+            FROM ChamiloCourseBundle:CForumThread t
+            INNER JOIN ChamiloCourseBundle:CItemProperty ip
+                WITH (t.iid = ip.ref AND t.cId = ip.course AND ip.tool = :tool)
+            WHERE
+                ip.course = :course AND
+                t.forum = :forum AND
+                $conditionVisibility
+                $conditionGroup
+                $conditionSession
+            ORDER BY t.threadSticky DESC, t.threadDate DESC";
+
+        $result = $this
+            ->_em
+            ->createQuery($dql)
+            ->setParameters(['forum' => $forum, 'course' => $course, 'tool' => TOOL_FORUM_THREAD])
+            ->getResult();
+
+        $forums = [];
+
+        for ($i = 0; $i < count($result); $i += 2) {
+            /** @var CForumThread $t */
+            $t = $result[$i];
+            /** @var CItemProperty $ip */
+            $ip = $result[$i + 1];
+            $t->setItemProperty($ip);
+
+            $forums[] = $t;
+        }
+
+        return $forums;
+    }
+
+    /**
+     * @param int          $id
+     * @param Course       $course
+     * @param Session|null $session
+     *
+     * @return CForumThread|null
+     *
+     * @todo Remove api_get_session_condition
+     */
+    public function findOneInCourse($id, Course $course, Session $session = null)
+    {
+        $conditionSession = api_get_session_condition(
+            $session ? $session->getId() : 0,
+            true,
+            false,
+            't.sessionId'
+        );
+
+        $dql = "SELECT t, ip
+            FROM ChamiloCourseBundle:CForumThread AS t
+            INNER JOIN ChamiloCourseBundle:CItemProperty AS ip
+                WITH (t.iid = ip.ref AND t.cId = ip.course)
+            WHERE
+                t.iid = :id AND
+                ip.tool = :tool AND
+                t.cId = :course
+                $conditionSession";
+
+        $result = $this
+            ->_em
+            ->createQuery($dql)
+            ->setParameters(['id' => (int) $id, 'course' => $course, 'tool' => TOOL_FORUM_THREAD])
+            ->getResult();
+
+        if (empty($result)) {
+            return null;
+        }
+
+        /** @var CForumThread $t */
+        $t = $result[0];
+        /** @var CItemProperty $ip */
+        $ip = $result[1];
+        $t->setItemProperty($ip);
+
+        return $t;
+    }
+}

From 6ed353861374bfb7ee7ecf9b549c3555889261f0 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Fri, 28 Sep 2018 15:29:32 -0500
Subject: [PATCH 19/32] GraphQL add query for forum threads #2644

---
 src/ApiBundle/GraphQL/Map/QueryMap.php        | 41 +++++++++++++++++-
 .../GraphQL/Resolver/CourseResolver.php       | 43 +++++++++++++++++++
 .../Resources/config/schema.types.graphql     |  1 +
 3 files changed, 83 insertions(+), 2 deletions(-)

diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index f108ca5d0e..25538d6aa5 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -12,6 +12,8 @@ use Chamilo\CoreBundle\Entity\SessionCategory;
 use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter;
 use Chamilo\CourseBundle\Entity\CForumCategory;
 use Chamilo\CourseBundle\Entity\CForumForum;
+use Chamilo\CourseBundle\Entity\CForumPost;
+use Chamilo\CourseBundle\Entity\CForumThread;
 use Chamilo\CourseBundle\Entity\CNotebook;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\UserBundle\Entity\User;
@@ -182,6 +184,10 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                         return $resolver->getForum($args['id'], $context);
                     }
 
+                    if ('thread' === $info->fieldName) {
+                        return $resolver->getThread($args['id'], $context);
+                    }
+
                     return $this->resolveField($info->fieldName, $tool);
                 },
             ],
@@ -217,8 +223,39 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                 'numberOfPosts' => function (CForumForum $forum) {
                     return (int) $forum->getForumPosts();
                 },
-                'threads' => function (CForumForum $forum) {
-                    return [];
+                'threads' => function (CForumForum $forum, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                    return $resolver->getThreads($forum, $context);
+                },
+            ],
+            'CourseForumThread' => [
+                'id' => function (CForumThread $thread) {
+                    return $thread->getIid();
+                },
+                'title' => function (CForumThread $thread) {
+                    return $thread->getThreadTitle();
+                },
+                'userPoster' => function (CForumThread $thread) {
+                    $userRepo = $this->em->getRepository('ChamiloUserBundle:User');
+                    $user = $userRepo->find($thread->getThreadPosterId());
+
+                    return $user;
+                },
+                'date' => function (CForumThread $thread) {
+                    return $thread->getThreadDate();
+                },
+                'sticky' => function (CForumThread $thread) {
+                    return $thread->getThreadSticky();
+                },
+                'numberOfViews' => function (CForumThread $thread) {
+                    return $thread->getThreadViews();
+                },
+                'numberOfReplies' => function (CForumThread $thread) {
+                    return $thread->getThreadReplies();
+                },
+                'closeDate' => function (CForumThread $thread) {
+                    return $thread->getThreadCloseDate();
                 },
             ],
             'Session' => [
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index 5cc2f10c28..b4f7861c82 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -10,6 +10,7 @@ use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
 use Chamilo\CourseBundle\Entity\CAnnouncement;
 use Chamilo\CourseBundle\Entity\CForumCategory;
 use Chamilo\CourseBundle\Entity\CForumForum;
+use Chamilo\CourseBundle\Entity\CForumThread;
 use Chamilo\CourseBundle\Entity\CItemProperty;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\CourseBundle\Repository\CNotebookRepository;
@@ -277,4 +278,46 @@ class CourseResolver implements ContainerAwareInterface
 
         return $forum;
     }
+
+    /**
+     * @param CForumForum  $forum
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getThreads(CForumForum $forum, \ArrayObject $context): array
+    {
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+        /** @var Session $session */
+        $session = $context->offsetGet('session');
+
+        $threadRepo = $this->em->getRepository('ChamiloCourseBundle:CForumThread');
+        $threads = $threadRepo->findAllInCourseByForum(false, $forum, $course, $session);
+
+        return $threads;
+    }
+
+    /**
+     * @param int          $id
+     * @param \ArrayObject $context
+     *
+     * @return CForumThread
+     */
+    public function getThread($id, \ArrayObject $context)
+    {
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+        /** @var Session $session */
+        $session = $context->offsetGet('session');
+
+        $threadRepo = $this->em->getRepository('ChamiloCourseBundle:CForumThread');
+        $thread = $threadRepo->findOneInCourse($id, $course, $session);
+
+        if (empty($thread)) {
+            throw new UserError($this->translator->trans('Forum thread not found in this course.'));
+        }
+
+        return $thread;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index 843c08f5cc..d4f7785fbe 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -162,6 +162,7 @@ type ToolForums {
     customIcon: String
     categories: [CourseForumCategory!]
     forum(id: Int!): CourseForum
+    thread(id: Int!): CourseForumThread
 }
 
 type CourseForumCategory {

From 94f30f040a7af0461255ed77e37a83e374d91007 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Fri, 28 Sep 2018 17:36:09 -0500
Subject: [PATCH 20/32] Add repository for forum post #2644

---
 src/CourseBundle/Entity/CForumPost.php        |  2 +-
 src/CourseBundle/Entity/CGroupInfo.php        | 30 +++++++
 .../Repository/CForumPostRepository.php       | 80 +++++++++++++++++++
 3 files changed, 111 insertions(+), 1 deletion(-)
 create mode 100644 src/CourseBundle/Repository/CForumPostRepository.php

diff --git a/src/CourseBundle/Entity/CForumPost.php b/src/CourseBundle/Entity/CForumPost.php
index 1d982dbeee..165f9299b3 100644
--- a/src/CourseBundle/Entity/CForumPost.php
+++ b/src/CourseBundle/Entity/CForumPost.php
@@ -19,7 +19,7 @@ use Doctrine\ORM\Mapping as ORM;
  *      @ORM\Index(name="c_id_visible_post_date", columns={"c_id", "visible", "post_date"})
  *  }
  * )
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Chamilo\CourseBundle\Repository\CForumPostRepository")
  */
 class CForumPost
 {
diff --git a/src/CourseBundle/Entity/CGroupInfo.php b/src/CourseBundle/Entity/CGroupInfo.php
index ad28f80836..e73d41a34d 100644
--- a/src/CourseBundle/Entity/CGroupInfo.php
+++ b/src/CourseBundle/Entity/CGroupInfo.php
@@ -5,7 +5,9 @@ namespace Chamilo\CourseBundle\Entity;
 
 use Chamilo\CoreBundle\Entity\Course;
 use Chamilo\CoreBundle\Traits\CourseTrait;
+use Chamilo\UserBundle\Entity\User;
 use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Criteria;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -685,4 +687,32 @@ class CGroupInfo
 
         return $this;
     }
+
+    /**
+     * @param User|null $user
+     *
+     * @return bool
+     */
+    public function userIsTutor(User $user = null): bool
+    {
+        if (empty($user)) {
+            return false;
+        }
+
+        if (0 === $this->tutors->count()) {
+            return false;
+        }
+
+        $criteria = Criteria::create()
+            ->where(
+                Criteria::expr()->eq('cId', $this->course)
+            )
+            ->andWhere(
+                Criteria::expr()->eq('user', $user)
+            );
+
+        $relation = $this->tutors->matching($criteria);
+
+        return $relation->count() > 0;
+    }
 }
diff --git a/src/CourseBundle/Repository/CForumPostRepository.php b/src/CourseBundle/Repository/CForumPostRepository.php
new file mode 100644
index 0000000000..379545d918
--- /dev/null
+++ b/src/CourseBundle/Repository/CForumPostRepository.php
@@ -0,0 +1,80 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\CourseBundle\Repository;
+
+use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\CourseBundle\Entity\CForumPost;
+use Chamilo\CourseBundle\Entity\CForumThread;
+use Chamilo\CourseBundle\Entity\CGroupInfo;
+use Chamilo\UserBundle\Entity\User;
+use Doctrine\ORM\EntityRepository;
+
+/**
+ * Class CForumPostRepository.
+ *
+ * @package Chamilo\CourseBundle\Repository
+ */
+class CForumPostRepository extends EntityRepository
+{
+    /**
+     * @param bool            $onlyVisibles
+     * @param bool            $isAllowedToEdit
+     * @param CForumThread    $thread
+     * @param Course          $course
+     * @param User|null       $currentUser
+     * @param CGroupInfo|null $group
+     * @param string          $orderDirection
+     *
+     * @return array
+     */
+    public function findAllInCourseByThread(
+        $onlyVisibles,
+        $isAllowedToEdit,
+        CForumThread $thread,
+        Course $course,
+        User $currentUser = null,
+        CGroupInfo $group = null,
+        $orderDirection = 'ASC'
+    ): array {
+        $conditionVisibility = $onlyVisibles ? 'p.visible = 1' : 'p.visible != 2';
+        $conditionModetared = '';
+        $filterModerated = true;
+
+        if (
+            (empty($group) && $isAllowedToEdit) ||
+            (
+                ($group ? $group->userIsTutor($currentUser) : false) ||
+                !$onlyVisibles
+            )
+        ) {
+            $filterModerated = false;
+        }
+
+        if ($filterModerated && $thread->getForum()->isModerated() && $onlyVisibles) {
+            $userId = $currentUser ? $currentUser->getId() : 0;
+
+            $conditionModetared = "AND p.status = 1 OR
+                (p.status = ".CForumPost::STATUS_WAITING_MODERATION." AND p.posterId = $userId) OR
+                (p.status = ".CForumPost::STATUS_REJECTED." AND p.poster = $userId) OR
+                (p.status IS NULL AND p.posterId = $userId)";
+        }
+
+        $dql = "SELECT p
+            FROM ChamiloCourseBundle:CForumPost p
+            WHERE
+                p.thread = :thread AND
+                p.cId = :course AND
+                $conditionVisibility
+                $conditionModetared
+            ORDER BY p.iid $orderDirection";
+
+        $result = $this
+            ->_em
+            ->createQuery($dql)
+            ->setParameters(['thread' => $thread, 'course' => $course])
+            ->getResult();
+
+        return $result;
+    }
+}

From 82834ee24132fc24d9d3f48845712aa4a404cd34 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Fri, 28 Sep 2018 17:36:36 -0500
Subject: [PATCH 21/32] GraphQL add query for forum post #2644

---
 src/ApiBundle/GraphQL/Map/QueryMap.php        | 31 +++++++++++++++++++
 .../GraphQL/Resolver/CourseResolver.php       | 23 ++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index 25538d6aa5..6aad4d158d 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -257,6 +257,37 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                 'closeDate' => function (CForumThread $thread) {
                     return $thread->getThreadCloseDate();
                 },
+                'posts' => function (CForumThread $thread, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                    return $resolver->getPosts($thread, $context);
+                }
+            ],
+            'CourseForumPost' => [
+                'id' => function (CForumPost $post) {
+                    return $post->getIid();
+                },
+                'title' => function (CForumPost $post) {
+                    return $post->getPostTitle();
+                },
+                'text' => function (CForumPost $post) {
+                    return $post->getPostText();
+                },
+                'userPoster' => function (CForumPost $post) {
+                    $userRepo = $this->em->getRepository('ChamiloUserBundle:User');
+                    $user = $userRepo->find($post->getPosterId());
+
+                    return $user;
+                },
+                'date' => function (CForumPost $post) {
+                    return $post->getPostDate();
+                },
+                'parent' => function (CForumPost $post) {
+                    $postRepo = $this->em->getRepository('ChamiloCourseBundle:CForumPost');
+                    $parent = $postRepo->find((int) $post->getPostParentId());
+
+                    return $parent;
+                },
             ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index b4f7861c82..ea436f694b 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -320,4 +320,27 @@ class CourseResolver implements ContainerAwareInterface
 
         return $thread;
     }
+
+    /**
+     * @param CForumThread $thread
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getPosts(CForumThread $thread, \ArrayObject $context)
+    {
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+
+        $postRepo = $this->em->getRepository('ChamiloCourseBundle:CForumPost');
+        $posts = $postRepo->findAllInCourseByThread(
+            api_is_allowed_to_edit(false, true),
+            api_is_allowed_to_edit(),
+            $thread,
+            $course,
+            $this->getCurrentUser()
+        );
+
+        return $posts;
+    }
 }

From 0d72afba0188a8d9df2096a1b789a26e62cbac16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Arag=C3=B3n?= <aragcar@gmail.com>
Date: Fri, 28 Sep 2018 17:45:10 -0500
Subject: [PATCH 22/32] List of teachers in popular courses - refs #2681

---
 assets/css/scss/_base.scss                    |  25 ++-
 main/course_info/about.php                    |   2 +-
 .../grid_courses_without_category.html.twig   | 156 +++++++++---------
 public/img/session_default.png                | Bin 905 -> 1786 bytes
 .../default/layout/hot_course_item.html.twig  |  43 ++---
 .../Resources/views/Macros/box.html.twig      |   2 +-
 6 files changed, 125 insertions(+), 103 deletions(-)

diff --git a/assets/css/scss/_base.scss b/assets/css/scss/_base.scss
index 75944e416b..8bef616473 100644
--- a/assets/css/scss/_base.scss
+++ b/assets/css/scss/_base.scss
@@ -303,6 +303,7 @@ footer {
   .card-body{
     padding: 0.5rem 0.25rem;
     .card-title{
+      margin-bottom: 0.5rem;
       .title{
         font-size: 16px;
         font-weight: bold;
@@ -310,9 +311,27 @@ footer {
       }
     }
     .card-author{
-      .details{
-        .name{
-          font-size: 12px;
+      display: block;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      .list-name{
+        font-size: 12px;
+        color: $grey-700;
+        cursor: pointer;
+      }
+      .name{
+        font-size: 12px;
+        white-space: nowrap;
+        color: $grey-700;
+        display: inline-block;
+        &:after{
+          content: ", ";
+        }
+        &:last-child{
+          &:after{
+            content: " ";
+          }
         }
       }
     }
diff --git a/main/course_info/about.php b/main/course_info/about.php
index f0b3775400..42519afa04 100644
--- a/main/course_info/about.php
+++ b/main/course_info/about.php
@@ -162,7 +162,7 @@ $htmlHeadXtra[] = api_get_asset('readmore-js/readmore.js');
 
 $template = new Template($course->getTitle(), true, true, false, true, false);
 $template->assign('course', $courseItem);
-$essence = Essence\Essence::instance();
+$essence = new Essence\Essence();
 $template->assign('essence', $essence);
 $template->assign('is_premium', $courseIsPremium);
 $template->assign('token', $token);
diff --git a/main/template/default/user_portal/grid_courses_without_category.html.twig b/main/template/default/user_portal/grid_courses_without_category.html.twig
index 62200b37ca..8d7ce85837 100644
--- a/main/template/default/user_portal/grid_courses_without_category.html.twig
+++ b/main/template/default/user_portal/grid_courses_without_category.html.twig
@@ -3,84 +3,91 @@
 {% if not courses is empty %}
     <div class="course-columns">
         <div class="row">
+        {% for item in courses %}
+            <div class="col-sm">
+            {% if item.title %}
+                {% set image %}
+                    {% if item.category != '' %}
+                        <div class="category">
+                            {{ item.category }}
+                        </div>
+                    {% endif %}
+                    {% if item.is_special_course %}
+                        <div class="pin">{{ item.icon }}</div>
+                    {% endif %}
+                    {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
+                        <img src="{{ item.image }}" class="card-img-top">
+                    {% else %}
+                        <a title="{{ item.title }}" href="{{ item.link }}">
+                            <img src="{{ item.image }}" alt="{{ item.title }}" class="card-img-top">
+                        </a>
+                    {% endif %}
+                {% endset %}
 
-
-    {% for item in courses %}
-        <div class="col-sm">
-        {% if item.title %}
-            {% set image %}
-                {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
-                    <img src="{{ item.image }}" class="card-img-top">
-                {% else %}
-                    <a title="{{ item.title }}" href="{{ item.link }}">
-                        <img src="{{ item.image }}" alt="{{ item.title }}" class="card-img-top">
-                    </a>
-                {% endif %}
-            {% endset %}
-
-            {% set content %}
-                <div class="card-title">
-                    <h5 class="title">
-                        {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
-                            {{ item.title_cut }} {{ item.code_course }}
+                {% set content %}
+                    <div class="card-title">
+                        <h5 class="title">
+                            {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
+                                {{ item.title_cut }} {{ item.code_course }}
+                            {% else %}
+                                <a title="{{ item.title }}" href="{{ item.link }}">{{ item.title_cut }} {{ item.code_course }}</a>
+                            {% endif %}
+                        </h5>
+                    </div>
+                    {% if item.notifications %}
+                        <div class="notifications">{{ item.notifications }}</div>
+                    {% endif %}
+                    <div class="card-author d-flex flex-row bd-highlight mb-3">
+                        {% if item.teachers | length > 6 %}
+                            <a id="plist-{{ loop.index }}" data-trigger="focus" tabindex="0" role="button" class="btn btn-default panel_popover" data-toggle="popover" title="{{ 'CourseTeachers' | get_lang }}" data-html="true">
+                                <i class="fa fa-graduation-cap" aria-hidden="true"></i>
+                            </a>
+                            <div id="popover-content-plist-{{ loop.index }}" class="hide">
+                                {% for teacher in item.teachers %}
+                                    <div class="popover-teacher">
+                                        <a href="{{ teacher.url }}" class="ajax"
+                                           data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                            <img src="{{ teacher.avatar }}"/>
+                                        </a>
+                                        <div class="teachers-details">
+                                            <h5>
+                                                <a href="{{ teacher.url }}" class="ajax"
+                                                   data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                                    {{ teacher.firstname }} {{ teacher.lastname }}
+                                                </a>
+                                            </h5>
+                                        </div>
+                                    </div>
+                                {% endfor %}
+                            </div>
                         {% else %}
-                            <a title="{{ item.title }}" href="{{ item.link }}">{{ item.title_cut }} {{ item.code_course }}</a>
-                        {% endif %}
-                    </h5>
-                </div>
-                <div class="block-author">
-                    {% if item.teachers | length > 6 %}
-                        <a id="plist-{{ loop.index }}" data-trigger="focus" tabindex="0" role="button" class="btn btn-default panel_popover" data-toggle="popover" title="{{ 'CourseTeachers' | get_lang }}" data-html="true">
-                            <i class="fa fa-graduation-cap" aria-hidden="true"></i>
-                        </a>
-                        <div id="popover-content-plist-{{ loop.index }}" class="hide">
                             {% for teacher in item.teachers %}
-                                <div class="popover-teacher">
-                                    <a href="{{ teacher.url }}" class="ajax"
+                                {% if item.teachers | length <= 2 %}
+                                    <a href="{{ teacher.url }}" class="ajax p-2 bd-highlight"
                                        data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
                                         <img src="{{ teacher.avatar }}"/>
                                     </a>
-                                    <div class="teachers-details">
-                                        <h5>
-                                            <a href="{{ teacher.url }}" class="ajax"
-                                               data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                {{ teacher.firstname }} {{ teacher.lastname }}
-                                            </a>
-                                        </h5>
-                                    </div>
-                                </div>
-                            {% endfor %}
-                        </div>
-                    {% else %}
-                        {% for teacher in item.teachers %}
-                            {% if item.teachers | length <= 2 %}
-                                <a href="{{ teacher.url }}" class="ajax"
-                                   data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                    <img src="{{ teacher.avatar }}"/>
-                                </a>
-                                <div class="teachers-details">
-                                    <h5>
+                                    <div class="details p-2 bd-highlight">
                                         <a href="{{ teacher.url }}" class="ajax"
                                            data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
                                             {{ teacher.firstname }} {{ teacher.lastname }}
                                         </a>
-                                    </h5>
-                                    <p>{{ 'Teacher' | get_lang }}</p>
-                                </div>
-                            {% elseif item.teachers | length <= 6 %}
-                                <a href="{{ teacher.url }}" class="ajax"
-                                   data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                    <img src="{{ teacher.avatar }}"/>
-                                </a>
-                            {% endif %}
-                        {% endfor %}
-                    {% endif %}
-                </div>
-            {% endset %}
-            {{ macro.panel('', content, '', '', '', image) }}
-        {% endif %}
-        </div>
-    {% endfor %}
+                                        <div class="name">{{ 'Teacher' | get_lang }}</div>
+                                    </div>
+                                {% elseif item.teachers | length <= 6 %}
+                                    <a href="{{ teacher.url }}" class="ajax p-2 bd-highlight"
+                                       data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                        <img src="{{ teacher.avatar }}"/>
+                                    </a>
+                                {% endif %}
+                            {% endfor %}
+                        {% endif %}
+                    </div>
+                {% endset %}
+                {{ macro.panel('', content, '', '', '', image) }}
+            {% endif %}
+            </div>
+        {% endfor %}
         </div>
     </div>
 {% endif %}
@@ -93,14 +100,9 @@
                 <div class="col-xs-12 col-sm-6 col-md-4">
                     <div class="items my-courses">
                         <div class="image">
-                            {% if item.is_special_course %}
-                                <div class="pin">{{ item.icon }}</div>
-                            {% endif %}
 
-                            {% if item.category != '' %}
-                                <span class="category">{{ item.category }}</span>
-                                <div class="cribbon"></div>
-                            {% endif %}
+
+
 
                             {% if item.edit_actions != '' %}
                                 <div class="admin-actions">
@@ -122,9 +124,7 @@
                         <div class="description">
 
 
-                            {% if item.notifications %}
-                                <div class="notifications">{{ item.notifications }}</div>
-                            {% endif %}
+
                             {% if item.student_info %}
                                 {% if (item.student_info.progress is not null) and (item.student_info.score is not null) %}
                                     <div class="course-student-info">
diff --git a/public/img/session_default.png b/public/img/session_default.png
index 86a8a5f9a9af3ec5155a2cb6737fb83028b47acd..40c685d26a978b3288fd26b6daae62b80d204deb 100644
GIT binary patch
literal 1786
zcmeAS@N?(olHy`uVBq!ia0y~yU@Bx_V2tBn1B$$w@974lSc;uILpXq-h9ji|$mcBZ
zh%9Dc5ElYr#`O7@fVvnYOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zA
zEBAD945^s&_L?K_0R@Hw2Mh%@oEZL{HQ=qDs8-JP>&=Yp`Roi0Q@)o04g5dTf}ue{
zn4Li&<q-ozlaCC8!-P&|1`Z>j`yAEy7!*z%VPs$#RWup|qp4stBaD^>qs1Yv8sP%F
Y*>slFjUMa^fYl0vr>mdKI;Vst0J?CE-~a#s

literal 905
zcmeAS@N?(olHy`uVBq!ia0y~yV4MJCKj2^jl8ZW)zXMWiN#5=*4F5rJ!QSPQ85o%D
zJY5_^D(1YsbCC0hfdI?Ft$`=}&Rx8z`9fW4mC}xI<0!uP&vwiK1@r$gGVH!qv%H?^
zfE+jX2>~4i=M4=!<~j<=y-AFM^I9C*)D<TrCNfGkv8Hf{O?cSGni3J=(3Sue*Z~w5
z6Put25y%5lt*j|O<bcXNIZ`Twm<VY?82w`p57=z1nn11q8Vs}^WRtWQ%stf51hNpr
z3m7)7`MHO)kqr?(_1O$YI*_0`$YgW}7-9%HVAvf##AL+6ed0r<B#a3R*TeUva41t@
zK{Bi+j_t$-pn*VhK}OgkOhZwIYUljvj7UcO;pc`KVZ+&o)oCF2JWqwW2dC3O%6O3t
z>oM(Ob%90_*4W1gI*?*JeP&p4z@-V4Vo(znc1?&RgDW*YtcIjRB%4q)K~pOvJ>%f~
Yk<swdnpfwP2+Ugyp00i_>zopr0GCA>9{>OV

diff --git a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
index 307dc97631..0c63010828 100644
--- a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
+++ b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
@@ -25,30 +25,33 @@
                         <a title="{{ item.title }}" href="{{ item.course_public_url }}">{{ item.title}}</a>
                     </h5>
                 </div>
+                <div class="card-author mb-2">
+                    <i class="fa fa-graduation-cap" aria-hidden="true"></i>
+                    {% if item.teachers | length >= 3 %}
+                        <a id="plist-{{ loop.index }}" data-trigger="focus" tabindex="0" role="button" class="list-name" data-toggle="popover" title="{{ 'CourseTeachers' | get_lang }}" data-html="true">
+                            {{ 'CourseTeachers' | get_lang }}
+                        </a>
+                        <div id="popover-content-plist-{{ loop.index }}" style="display: none;">
+                            {% for teacher in item.teachers %}
+                                <div class="popover-teacher">
+                                    <a href="{{ teacher.url }}" class="ajax name" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                        {{ teacher.firstname }} {{ teacher.lastname }}
+                                    </a>
+                                </div>
+                            {% endfor %}
+                        </div>
+                    {% else %}
+                        {% for teacher in item.teachers %}
+                            <a href="{{ teacher.url }}" class="ajax name" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                {{ teacher.firstname }} {{ teacher.lastname }}
+                            </a>
+                        {% endfor %}
+                    {% endif %}
 
+                </div>
                 <div class="ranking">
                     {{ item.rating_html }}
                 </div>
-
-                <div class="card-author d-flex flex-row bd-highlight mb-3">
-                    {% for teacher in item.teachers %}
-                        {% if item.teachers | length > 2 %}
-                            <a href="{{ teacher.url }}" class="ajax" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                <img src="{{ teacher.avatar }}" alt="{{ 'TeacherPicture' | get_lang }}" />
-                            </a>
-                        {% else %}
-                            <a href="{{ teacher.url }}" class="ajax p-2 bd-highlight" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                <img src="{{ teacher.avatar }}" alt="{{ 'TeacherPicture' | get_lang }}" />
-                            </a>
-                            <div class="details p-2 bd-highlight">
-                                <a href="{{ teacher.url }}" class="ajax" data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                        {{ teacher.firstname }} {{ teacher.lastname }}
-                                </a>
-                                <div class="name">{{ 'Teacher' | get_lang }}</div>
-                            </div>
-                        {% endif %}
-                    {% endfor %}
-                </div>
                 <!--
                 <div class="toolbar row">
                     <div class="col-sm-4">
diff --git a/src/ThemeBundle/Resources/views/Macros/box.html.twig b/src/ThemeBundle/Resources/views/Macros/box.html.twig
index a24c518edc..5139625fef 100644
--- a/src/ThemeBundle/Resources/views/Macros/box.html.twig
+++ b/src/ThemeBundle/Resources/views/Macros/box.html.twig
@@ -161,7 +161,7 @@
             {% if top_image %}
                 {{ top_image }}
             {% endif %}
-            <div class="card-body">
+            <div class="card-body pb-3">
                 {% if title %}
                     <h5 class="card-title">{{ title }}</h5>
                 {% endif %}

From 98aa3010d8d00e2f609d10cdf27d15bae5c6e46c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Arag=C3=B3n?= <aragcar@gmail.com>
Date: Fri, 28 Sep 2018 18:00:09 -0500
Subject: [PATCH 23/32] list of user courses and add about.twig course - refs
 #2681

---
 assets/css/scss/_base.scss                    |   1 +
 .../default/course_home/about.html.twig       | 188 ++++++++++++++++++
 .../grid_courses_without_category.html.twig   |  43 ++--
 3 files changed, 201 insertions(+), 31 deletions(-)
 create mode 100644 main/template/default/course_home/about.html.twig

diff --git a/assets/css/scss/_base.scss b/assets/css/scss/_base.scss
index 8bef616473..bcda77149f 100644
--- a/assets/css/scss/_base.scss
+++ b/assets/css/scss/_base.scss
@@ -299,6 +299,7 @@ footer {
     padding: 0.2rem 0.8rem;
     top: -0.8rem;
     left: 0.5rem;
+    font-size: 12px;
   }
   .card-body{
     padding: 0.5rem 0.25rem;
diff --git a/main/template/default/course_home/about.html.twig b/main/template/default/course_home/about.html.twig
new file mode 100644
index 0000000000..cbb65e728c
--- /dev/null
+++ b/main/template/default/course_home/about.html.twig
@@ -0,0 +1,188 @@
+<div id="about-course">
+    <div id="course-info-top">
+        <h2 class="session-title">{{ course.title }}</h2>
+        <div class="course-short">
+            <ul>
+                <li class="author">{{ "Professors"|get_lang }}</li>
+                {%  for teacher in course.teachers %}
+                    <li>{{ teacher.complete_name }} | </li>
+                {% endfor %}
+            </ul>
+
+        </div>
+    </div>
+
+    {% set course_video = '' %}
+    {% for extra_field in course.extra_fields %}
+    {% if extra_field.value.getField().getVariable() == 'video_url' %}
+    {% set course_video = extra_field.value.getValue() %}
+    {% endif %}
+    {% endfor %}
+
+    <div class="panel panel-default">
+        <div class="panel-body">
+            <div class="row">
+                <div class="col-sm-5">
+                    {% if course_video %}
+                    <div class="course-video">
+                        <div class="embed-responsive embed-responsive-16by9">
+                            {{ essence.replace(course_video) }}
+                        </div>
+                    </div>
+                    {% else %}
+                    <div class="course-image">
+                        <img src="{{ course.image }}" class="img-responsive" />
+                    </div>
+                    {% endif %}
+
+                    <div class="share-social-media">
+                        <ul class="sharing-buttons">
+                            <li>
+                                {{ "ShareWithYourFriends"|get_lang }}
+                            </li>
+                            <li>
+                                <a href="https://www.facebook.com/sharer/sharer.php?u={{ url }}"
+                                   target="_blank" class="btn btn-facebook btn-inverse btn-xs">
+                                    <em class="fa fa-facebook"></em> Facebook
+                                </a>
+                            </li>
+                            <li>
+                                <a href="https://twitter.com/home?{{ {'status': course.title ~ ' ' ~ url }|url_encode }}"
+                                   target="_blank" class="btn btn-twitter btn-inverse btn-xs">
+                                    <em class="fa fa-twitter"></em> Twitter
+                                </a>
+                            </li>
+                            <li>
+                                <a href="https://www.linkedin.com/shareArticle?{{ {'mini': 'true', 'url': url , 'title': course.title }|url_encode }}"
+                                   target="_blank" class="btn btn-linkedin btn-inverse btn-xs">
+                                    <em class="fa fa-linkedin"></em> Linkedin
+                                </a>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+                <div class="col-sm-7">
+                    <div class="course-description">
+                        {{ course.description }}
+                    </div>
+                </div>
+            </div>
+            {% if course.tags %}
+                <div class="panel-tags">
+
+                    <ul class="list-inline course-tags">
+                        <li>{{ 'Tags'|get_lang }} :</li>
+                        {% for tag in course.tags %}
+                            <li class="tag-value">
+                                <span>{{ tag.getTag }}</span>
+                            </li>
+                        {% endfor %}
+                    </ul>
+                </div>
+            {% endif %}
+        </div>
+    </div>
+    <section id="course-info-bottom" class="course">
+        <div class="row">
+            <div class="col-sm-8">
+                <div class="panel panel-default">
+                    <div class="panel-body">
+                        <h3 class="sub-title">{{ "CourseInformation"|get_lang }}</h3>
+                        <div class="course-information">
+                            {% for topic in course.syllabus %}
+                                {% if topic.content != '' %}
+                                    <div class="topics">
+                                        <h4 class="title-info">
+                                            <em class="fa fa-book"></em> {{ topic.title }}
+                                        </h4>
+                                        <div class="content-info">
+                                            {{ topic.content }}
+                                        </div>
+                                    </div>
+                                {% endif %}
+                            {% endfor %}
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="col-sm-4">
+                <div class="panel panel-default">
+                    <div class="panel-body">
+                        {% if is_premium == false %}
+                            <h5>{{ 'CourseSubscription'|get_lang }}</h5>
+                            <div class="session-subscribe">
+                                {% if _u.logged == 0 %}
+                                    {% if 'allow_registration'|api_get_setting != 'false' %}
+                                        <a href="{{ _p.web_main ~ 'auth/inscription.php' ~ redirect_to_session }}" class="btn btn-success btn-block btn-lg">
+                                            <i class="fa fa-pencil" aria-hidden="true"></i> {{ 'SignUp'|get_lang }}
+                                        </a>
+                                    {% endif %}
+                                {% elseif course.subscription %}
+                                    <a href="{{ _p.web }}courses/{{ course.code }}/index.php?id_session=0" class="btn btn-lg btn-success btn-block">{{ 'CourseHomepage'|get_lang }}</a>
+                                {% else %}
+                                    <a href="{{ _p.web }}courses/{{ course.code }}/index.php?action=subscribe&sec_token={{ token }}" class="btn btn-lg btn-success btn-block">{{ 'Subscribe'|get_lang }}</a>
+                                {% endif %}
+                            </div>
+                        {% else %}
+                            <div class="session-price">
+                                <div class="sale-price">
+                                    {{ 'SalePrice'|get_lang }}
+                                </div>
+                                <div class="price-text">
+                                    {{ is_premium.iso_code }} {{ is_premium.price }}
+                                </div>
+                                <div class="buy-box">
+                                    <a href="{{ _p.web }}plugin/buycourses/src/process.php?i={{ is_premium.product_id }}&t={{ is_premium.product_type }}" class="btn btn-lg btn-primary btn-block">{{ 'BuyNow'|get_lang }}</a>
+                                </div>
+                            </div>
+                        {% endif %}
+                    </div>
+                </div>
+                <div class="panel panel-default">
+                    <div class="panel-body">
+                        <div class="panel-teachers">
+                            <h3 class="sub-title">{{ "Coaches"|get_lang }}</h3>
+                        </div>
+                        {%  for teacher in course.teachers %}
+                        <div class="coach-information">
+                            <div class="coach-header">
+                                <div class="coach-avatar">
+                                    <img class="img-circle img-responsive" src="{{ teacher.image }}" alt="{{ teacher.complete_name }}">
+                                </div>
+                                <div class="coach-title">
+                                    <h4>{{ teacher.complete_name }}</h4>
+                                    <p> {{ teacher.diploma }}</p>
+                                </div>
+                            </div>
+                            <div class="open-area  {{ course.teachers | length >= 2 ? 'open-more' : ' ' }}">
+                                {{ teacher.openarea }}
+                            </div>
+                        </div>
+                        {% endfor %}
+
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+</div>
+
+<script type="text/javascript">
+    $(document).ready(function() {
+        $('.course-information').readmore({
+            speed: 100,
+            lessLink: '<a class="hide-content" href="#">{{ 'SetInvisible' | get_lang }}</a>',
+            moreLink: '<a class="read-more" href="#">{{ 'ReadMore' | get_lang }}</a>',
+            collapsedHeight: 730,
+            heightMargin: 100
+        });
+        $('.open-more').readmore({
+            speed: 100,
+            lessLink: '<a class="hide-content" href="#">{{ 'SetInvisible' | get_lang }}</a>',
+            moreLink: '<a class="read-more" href="#">{{ 'ReadMore' | get_lang }}</a>',
+            collapsedHeight: 90,
+            heightMargin: 20
+        });
+    });
+</script>
\ No newline at end of file
diff --git a/main/template/default/user_portal/grid_courses_without_category.html.twig b/main/template/default/user_portal/grid_courses_without_category.html.twig
index 8d7ce85837..a734d1c5f2 100644
--- a/main/template/default/user_portal/grid_courses_without_category.html.twig
+++ b/main/template/default/user_portal/grid_courses_without_category.html.twig
@@ -34,55 +34,36 @@
                             {% endif %}
                         </h5>
                     </div>
-                    {% if item.notifications %}
-                        <div class="notifications">{{ item.notifications }}</div>
-                    {% endif %}
-                    <div class="card-author d-flex flex-row bd-highlight mb-3">
-                        {% if item.teachers | length > 6 %}
-                            <a id="plist-{{ loop.index }}" data-trigger="focus" tabindex="0" role="button" class="btn btn-default panel_popover" data-toggle="popover" title="{{ 'CourseTeachers' | get_lang }}" data-html="true">
-                                <i class="fa fa-graduation-cap" aria-hidden="true"></i>
+                    <div class="card-author mb-2">
+                        <i class="fa fa-graduation-cap" aria-hidden="true"></i>
+                        {% if item.teachers | length >= 3 %}
+                            <a id="plist-{{ loop.index }}" data-trigger="focus" tabindex="0" role="button" class="list-name" data-toggle="popover" title="{{ 'CourseTeachers' | get_lang }}" data-html="true">
+                                {{ 'CourseTeachers' | get_lang }}
                             </a>
-                            <div id="popover-content-plist-{{ loop.index }}" class="hide">
+                            <div id="popover-content-plist-{{ loop.index }}" style="display: none;">
                                 {% for teacher in item.teachers %}
                                     <div class="popover-teacher">
                                         <a href="{{ teacher.url }}" class="ajax"
                                            data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                            <img src="{{ teacher.avatar }}"/>
+                                            {{ teacher.firstname }} {{ teacher.lastname }}
                                         </a>
-                                        <div class="teachers-details">
-                                            <h5>
-                                                <a href="{{ teacher.url }}" class="ajax"
-                                                   data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                    {{ teacher.firstname }} {{ teacher.lastname }}
-                                                </a>
-                                            </h5>
-                                        </div>
                                     </div>
                                 {% endfor %}
                             </div>
                         {% else %}
                             {% for teacher in item.teachers %}
                                 {% if item.teachers | length <= 2 %}
-                                    <a href="{{ teacher.url }}" class="ajax p-2 bd-highlight"
+                                    <a href="{{ teacher.url }}" class="ajax name"
                                        data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                        <img src="{{ teacher.avatar }}"/>
-                                    </a>
-                                    <div class="details p-2 bd-highlight">
-                                        <a href="{{ teacher.url }}" class="ajax"
-                                           data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                            {{ teacher.firstname }} {{ teacher.lastname }}
-                                        </a>
-                                        <div class="name">{{ 'Teacher' | get_lang }}</div>
-                                    </div>
-                                {% elseif item.teachers | length <= 6 %}
-                                    <a href="{{ teacher.url }}" class="ajax p-2 bd-highlight"
-                                       data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                        <img src="{{ teacher.avatar }}"/>
+                                        {{ teacher.firstname }} {{ teacher.lastname }}
                                     </a>
                                 {% endif %}
                             {% endfor %}
                         {% endif %}
                     </div>
+                    {% if item.notifications %}
+                        <div class="notifications">{{ item.notifications }}</div>
+                    {% endif %}
                 {% endset %}
                 {{ macro.panel('', content, '', '', '', image) }}
             {% endif %}

From 0fa0acd2b92eff9ee858fcb5f2aa4f854c29d064 Mon Sep 17 00:00:00 2001
From: Julio <gugli100@gmail.com>
Date: Mon, 1 Oct 2018 15:56:47 +0200
Subject: [PATCH 24/32] Media - Fix sonata media/classification installation

---
 config/packages/security.yaml                                 | 1 +
 config/packages/sonata_media.yaml                             | 2 --
 .../Resources/config/doctrine/Context.orm.xml                 | 4 ++--
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index 41946ec5ab..d9c9f0fc55 100644
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -19,6 +19,7 @@ security:
     role_hierarchy:
         ROLE_SONATA_ADMIN: ROLE_USER
         ROLE_ADMIN:
+            - ROLE_SUPER_ADMIN
             - ROLE_SONATA_ADMIN
             - ROLE_QUESTION_MANAGER
             - ROLE_SESSION_MANAGER
diff --git a/config/packages/sonata_media.yaml b/config/packages/sonata_media.yaml
index f78b4cb352..8f55daa9fe 100644
--- a/config/packages/sonata_media.yaml
+++ b/config/packages/sonata_media.yaml
@@ -20,14 +20,12 @@ sonata_media:
                 small: {width: 100, quality: 100}
                 medium: {width: 300, quality: 100}
                 big: {width: 970, quality: 100}
-
         sonata_collection:
             providers:
                 - sonata.media.provider.image
             formats:
                 preview: {width: 100, quality: 100}
                 wide: {width: 820, quality: 100}
-
         sonata_category:
             providers:
                 - sonata.media.provider.image
diff --git a/src/ClassificationBundle/Resources/config/doctrine/Context.orm.xml b/src/ClassificationBundle/Resources/config/doctrine/Context.orm.xml
index 84eb4572ea..7102a376dd 100644
--- a/src/ClassificationBundle/Resources/config/doctrine/Context.orm.xml
+++ b/src/ClassificationBundle/Resources/config/doctrine/Context.orm.xml
@@ -16,8 +16,8 @@
             table="classification__context"
             repository-class="Doctrine\ORM\EntityRepository">
 
-        <id name="id" type="integer" column="id">
-            <generator strategy="AUTO"/>
+        <id name="id" type="string" column="id">
+            <generator strategy="NONE"/>
         </id>
         <options>
             <option name="row_format">DYNAMIC</option>

From ad6838511a86b97aeacea7c84b6c3b4837758385 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Mon, 1 Oct 2018 14:54:30 -0500
Subject: [PATCH 25/32] Fix skills_gradebook in french language #2692

---
 main/admin/skills_gradebook.php | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/main/admin/skills_gradebook.php b/main/admin/skills_gradebook.php
index eb73dae182..ddd55198f8 100755
--- a/main/admin/skills_gradebook.php
+++ b/main/admin/skills_gradebook.php
@@ -95,13 +95,19 @@ $extra_params['autowidth'] = 'true';
 //height auto
 $extra_params['height'] = 'auto';
 
+$iconAdd = Display::return_icon('add.png', addslashes(get_lang('AddSkill')));
+$iconAddNa = Display::return_icon(
+    'add_na.png',
+    addslashes(get_lang('YourGradebookFirstNeedsACertificateInOrderToBeLinkedToASkill'))
+);
+
 //With this function we can add actions to the jgrid (edit, delete, etc)
 $action_links = 'function action_formatter(cellvalue, options, rowObject) {
     //certificates
     if (rowObject[4] == 1) {
-        return \'<a href="?action=add_skill&id=\'+options.rowId+\'">'.Display::return_icon('add.png', get_lang('AddSkill'), '', ICON_SIZE_SMALL).'</a>'.'\';
+        return \'<a href="?action=add_skill&id=\'+options.rowId+\'">'.$iconAdd.'</a>'.'\';
     } else {
-        return \''.Display::return_icon('add_na.png', get_lang('YourGradebookFirstNeedsACertificateInOrderToBeLinkedToASkill'), '', ICON_SIZE_SMALL).''.'\';
+        return \''.$iconAddNa.'\';
     }
 }';
 ?>

From 5227842cc68cb68e68029eab01aa39b04c83ff68 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Mon, 1 Oct 2018 16:10:59 -0500
Subject: [PATCH 26/32] GraphQL add query for course agenda #2644

---
 src/ApiBundle/GraphQL/Map/EnumMap.php         |  1 +
 src/ApiBundle/GraphQL/Map/QueryMap.php        | 30 ++++++++++++++
 src/ApiBundle/GraphQL/Map/UnionMap.php        |  2 +
 .../GraphQL/Resolver/CourseResolver.php       | 41 +++++++++++++++++++
 .../Resources/config/schema.types.graphql     | 24 ++++++++++-
 5 files changed, 97 insertions(+), 1 deletion(-)

diff --git a/src/ApiBundle/GraphQL/Map/EnumMap.php b/src/ApiBundle/GraphQL/Map/EnumMap.php
index b130999b76..cdce1871d8 100644
--- a/src/ApiBundle/GraphQL/Map/EnumMap.php
+++ b/src/ApiBundle/GraphQL/Map/EnumMap.php
@@ -42,6 +42,7 @@ class EnumMap extends ResolverMap implements ContainerAwareInterface
                 'TOOL_ANNOUNCEMENT' => TOOL_ANNOUNCEMENT,
                 'TOOL_NOTEBOOK' => TOOL_NOTEBOOK,
                 'TOOL_FORUM' => TOOL_FORUM,
+                'TOOL_CALENDAR_EVENT' => TOOL_CALENDAR_EVENT,
             ],
         ];
     }
diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index 6aad4d158d..94050c8e72 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -289,6 +289,36 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                     return $parent;
                 },
             ],
+            'ToolAgenda' => [
+                self::RESOLVE_FIELD => function (
+                    CTool $tool,
+                    Argument $args,
+                    \ArrayObject $context,
+                    ResolveInfo $info
+                ) {
+                    if ('events' === $info->fieldName) {
+                        $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                        return $resolver->getAgenda($context);
+                    }
+
+                    return $this->resolveField($info->fieldName, $tool);
+                },
+            ],
+            'CourseAgendaEvent' => [
+                'id' => function (array $event) {
+                    return $event['unique_id'];
+                },
+                'description' => function (array $event) {
+                    return $event['comment'];
+                },
+                'startDate' => function (array $event) {
+                    return new \DateTime($event['start'], new \DateTimeZone('UTC'));
+                },
+                'endDate' => function (array $event) {
+                    return new \DateTime($event['end'], new \DateTimeZone('UTC'));
+                },
+            ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
                     Session $session,
diff --git a/src/ApiBundle/GraphQL/Map/UnionMap.php b/src/ApiBundle/GraphQL/Map/UnionMap.php
index 4926f6aa81..b820065acb 100644
--- a/src/ApiBundle/GraphQL/Map/UnionMap.php
+++ b/src/ApiBundle/GraphQL/Map/UnionMap.php
@@ -34,6 +34,8 @@ class UnionMap extends ResolverMap implements ContainerAwareInterface
                             return 'ToolNotebook';
                         case TOOL_FORUM:
                             return 'ToolForums';
+                        case TOOL_CALENDAR_EVENT:
+                            return 'ToolAgenda';
                     }
                 },
             ],
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index ea436f694b..93ae03e7c7 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -343,4 +343,45 @@ class CourseResolver implements ContainerAwareInterface
 
         return $posts;
     }
+
+    /**
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getAgenda(\ArrayObject $context): array
+    {
+        /** @var Session|null $session */
+        $session = $context->offsetGet('session');
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+
+        $agenda = new \Agenda(
+            'course',
+            $this->getCurrentUser()->getId(),
+            $course->getId(),
+            $session ? $session->getId() : 0
+        );
+        $result = $agenda->parseAgendaFilter(null);
+        $firstDay = new \DateTime('now', new \DateTimeZone('UTC'));
+        $firstDay->modify('first day of this month');
+        $firstDay->setTime(0, 0);
+        $lastDay = new \DateTime('now', new \DateTimeZone('UTC'));
+        $lastDay->modify('last day of this month');
+        $lastDay->setTime(0, 0);
+
+        $groupId = current($result['groups']);
+        $userId = current($result['users']);
+
+        $events = $agenda->getEvents(
+            $firstDay->getTimestamp(),
+            $lastDay->getTimestamp(),
+            $course->getId(),
+            $groupId,
+            $userId,
+            'array'
+        );
+
+        return $events;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index d4f7785fbe..57a617d11e 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -207,6 +207,27 @@ type CourseForumPost {
     status: Int
 }
 
+"A comprehensive diary/calendar tool"
+type ToolAgenda {
+    name: String
+    category: String
+    image: String
+    customIcon: String
+    events: [CourseAgendaEvent!]
+}
+
+type CourseAgendaEvent {
+    id: Int
+    className: String
+    title: String
+    description: String
+    startDate: DateTime
+    endDate: DateTime
+    allDay: Boolean
+    borderColor: String
+    backgroundColor: String
+}
+
 "A session registered on the platform."
 type Session {
     "The unique ID of the session."
@@ -238,7 +259,7 @@ type SessionCategory {
 
 # Unions
 
-union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums
+union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums | ToolAgenda
 
 # Enums
 
@@ -276,6 +297,7 @@ enum CourseToolType {
     TOOL_ANNOUNCEMENT
     TOOL_NOTEBOOK
     TOOL_FORUM
+    TOOL_CALENDAR_EVENT
 }
 
 # Scalars

From 52e0af0ad77a7c3a8c1d4a2f99f6783b045629fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Arag=C3=B3n?= <aragcar@gmail.com>
Date: Tue, 2 Oct 2018 14:06:04 -0500
Subject: [PATCH 27/32] fix category course - refs BT#2681

---
 assets/css/scss/_base.scss                    |  8 +--
 .../user_portal/course_categories.html.twig   | 58 +++++++++++++------
 .../grid_courses_without_category.html.twig   |  2 +-
 .../Resources/views/Macros/box.html.twig      | 31 +++++++++-
 4 files changed, 74 insertions(+), 25 deletions(-)

diff --git a/assets/css/scss/_base.scss b/assets/css/scss/_base.scss
index bcda77149f..ca2927fe07 100644
--- a/assets/css/scss/_base.scss
+++ b/assets/css/scss/_base.scss
@@ -264,12 +264,12 @@ footer {
   transform: perspective(1px) translateZ(0);
   transition-property: box-shadow;
   transition-duration: 0.3s;
-  box-shadow: 0 0 1px rgba(0, 0, 0, 0);
+  box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.15);
   &:hover,
   &:focus{
-    box-shadow: 0 18px 10px -10px rgba(0, 0, 0, 0.3);
+    box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.20);
   }
-  &.card-line{
+  /*&.card-line{
     &:hover{
       &:before{
         transition: all .3s;
@@ -289,7 +289,7 @@ footer {
       left: 0;
       transition: all .3s;
     }
-  }
+  }*/
   .category{
     position: absolute;
     border-radius: 10px;
diff --git a/main/template/default/user_portal/course_categories.html.twig b/main/template/default/user_portal/course_categories.html.twig
index f547cb4eb7..724fd7de20 100644
--- a/main/template/default/user_portal/course_categories.html.twig
+++ b/main/template/default/user_portal/course_categories.html.twig
@@ -1,25 +1,45 @@
-<p class="lead">{{ 'MyCoursePageCategoryIntroduction'|get_lang }}</p>
-<ul class="media-list">
+{% import "ChamiloThemeBundle:Macros:box.html.twig" as macro %}
+
+<h2>{{ 'CourseCategory'|get_lang }}</h2>
+<p>{{ 'MyCoursePageCategoryIntroduction'|get_lang }}</p>
+{% autoescape false %}
+<div class="row">
     {% for category in course_categories %}
+    <div class="col-sm-12">
         {% if category %}
-            <li class="media">
-                <div class="media-left">
-                    <a href="{{ _p.web_self ~ '?' ~ {'category':category.code}|url_encode }}">
-                        <img src="{{ category.image ? _p.web_upload ~ category.image : 'session_default.png'|icon(128) }}"
-                             alt="{{ category.name }}" width="200" class="media-object">
+
+            {% set image %}
+                <a href="{{ _p.web_self ~ '?' ~ {'category':category.code}|url_encode }}">
+                    <img src="{{ category.image ? _p.web_upload ~ category.image : 'session_default.png'|icon(128) }}"
+                         alt="{{ category.name }}" class="img-fluid">
+                </a>
+            {% endset %}
+
+            {% set title %}
+                <a href="{{ _p.web_self ~ '?' ~ {'category':category.code}|url_encode }}">
+                    {{ category.name }}
+                </a>
+            {% endset %}
+
+            {% set subtitle %}
+                {{ category.code }}
+            {% endset %}
+
+            {% set content %}
+                {% if category.description %}
+                    {{ category.description }}
+                {% endif %}
+                <div class="float-right">
+                    <a href="{{ _p.web_self ~ '?' ~ {'category':category.code}|url_encode }}" class="btn btn-outline-primary btn-sm">
+                        {{ 'View'|get_lang }}
                     </a>
                 </div>
-                <div class="media-body">
-                    <h4 class="media-heading">{{ category.name }}</h4>
-                    <p>{{ category.code }}</p>
-                    {% if category.description %}
-                        <p>{{ category.description }}</p>
-                    {% endif %}
-                    <a href="{{ _p.web_self ~ '?' ~ {'category':category.code}|url_encode }}" class="btn btn-default">
-                        {{ 'View'|get_lang }} <span class="fa fa-arrow-right" aria-hidden="true"></span>
-                    </a>
-                </div>
-            </li>
+            {% endset %}
+
+            {{ macro.rowpanel('category', title, subtitle, content, image) }}
+
         {% endif %}
+    </div>
     {% endfor %}
-</ul>
+</div>
+{% endautoescape %}
\ No newline at end of file
diff --git a/main/template/default/user_portal/grid_courses_without_category.html.twig b/main/template/default/user_portal/grid_courses_without_category.html.twig
index a734d1c5f2..95dd7053c9 100644
--- a/main/template/default/user_portal/grid_courses_without_category.html.twig
+++ b/main/template/default/user_portal/grid_courses_without_category.html.twig
@@ -4,7 +4,7 @@
     <div class="course-columns">
         <div class="row">
         {% for item in courses %}
-            <div class="col-sm">
+            <div class="col-sm-3 col-md-3">
             {% if item.title %}
                 {% set image %}
                     {% if item.category != '' %}
diff --git a/src/ThemeBundle/Resources/views/Macros/box.html.twig b/src/ThemeBundle/Resources/views/Macros/box.html.twig
index 5139625fef..1c8af67390 100644
--- a/src/ThemeBundle/Resources/views/Macros/box.html.twig
+++ b/src/ThemeBundle/Resources/views/Macros/box.html.twig
@@ -179,4 +179,33 @@
             {% endif %}
         </div>
     {% endautoescape %}
-{% endmacro %}
\ No newline at end of file
+{% endmacro %}
+
+{% macro rowpanel(id, title, subtitle, content, image) %}
+    {% autoescape false %}
+        <div class="card card-line card-{{ id }} mt-3 mb-3">
+            <div class="card-body pb-3">
+                <div class="row">
+                    <div class="col-sm-3">
+                        {% if image %}
+                            {{ image }}
+                        {% endif %}
+                    </div>
+                    <div class="col-sm-9">
+                        {% if title %}
+                            <h5 class="card-title">{{ title }}</h5>
+                        {% endif %}
+                        {% if subtitle %}
+                            <p class="card-subtitle mb-2 text-muted">{{ subtitle }}</p>
+                        {% endif %}
+                        {% if content %}
+                            <div class="description">
+                                {{ content }}
+                            </div>
+                        {% endif %}
+                    </div>
+                </div>
+            </div>
+        </div>
+    {% endautoescape %}
+{% endmacro %}

From 61dc81d4f8a092ca9b24fe8a0f014e3cb3ae5336 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Tue, 2 Oct 2018 11:53:45 -0500
Subject: [PATCH 28/32] GraphQL add query for documents tool #2644

---
 src/ApiBundle/GraphQL/Map/EnumMap.php         |  1 +
 src/ApiBundle/GraphQL/Map/QueryMap.php        | 10 +++
 src/ApiBundle/GraphQL/Map/UnionMap.php        |  2 +
 .../GraphQL/Resolver/CourseResolver.php       | 83 +++++++++++++++++++
 .../Resources/config/schema.types.graphql     | 22 ++++-
 5 files changed, 117 insertions(+), 1 deletion(-)

diff --git a/src/ApiBundle/GraphQL/Map/EnumMap.php b/src/ApiBundle/GraphQL/Map/EnumMap.php
index cdce1871d8..18f244110a 100644
--- a/src/ApiBundle/GraphQL/Map/EnumMap.php
+++ b/src/ApiBundle/GraphQL/Map/EnumMap.php
@@ -43,6 +43,7 @@ class EnumMap extends ResolverMap implements ContainerAwareInterface
                 'TOOL_NOTEBOOK' => TOOL_NOTEBOOK,
                 'TOOL_FORUM' => TOOL_FORUM,
                 'TOOL_CALENDAR_EVENT' => TOOL_CALENDAR_EVENT,
+                'TOOL_DOCUMENT' => TOOL_DOCUMENT,
             ],
         ];
     }
diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index 94050c8e72..f6ebcca728 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -319,6 +319,16 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                     return new \DateTime($event['end'], new \DateTimeZone('UTC'));
                 },
             ],
+            'ToolDocuments' => [
+                'documents' => function (CTool $tool, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                    $dirId = !empty($args['dirId']) ? $args['dirId'] : null;
+
+                    return $resolver->getDocuments($dirId, $context);
+                },
+            ],
+            //'CourseDocument' => [],
             'Session' => [
                 self::RESOLVE_FIELD => function (
                     Session $session,
diff --git a/src/ApiBundle/GraphQL/Map/UnionMap.php b/src/ApiBundle/GraphQL/Map/UnionMap.php
index b820065acb..b2898ad210 100644
--- a/src/ApiBundle/GraphQL/Map/UnionMap.php
+++ b/src/ApiBundle/GraphQL/Map/UnionMap.php
@@ -36,6 +36,8 @@ class UnionMap extends ResolverMap implements ContainerAwareInterface
                             return 'ToolForums';
                         case TOOL_CALENDAR_EVENT:
                             return 'ToolAgenda';
+                        case TOOL_DOCUMENT:
+                            return 'ToolDocuments';
                     }
                 },
             ],
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index 93ae03e7c7..a4d058e927 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -384,4 +384,87 @@ class CourseResolver implements ContainerAwareInterface
 
         return $events;
     }
+
+    /**
+     * @param int          $dirId
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getDocuments($dirId, \ArrayObject $context): array
+    {
+        $path = '/';
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+        /** @var Session $session */
+        $session = $context->offsetGet('session');
+
+        if (!empty($dirId)) {
+            $directory = $this->em->getRepository('ChamiloCourseBundle:CDocument')->find($dirId);
+
+            if (empty($directory)) {
+                throw new UserError($this->translator->trans('Directory not found.'));
+            }
+
+            if (empty($directory->getCourse())) {
+                throw new UserError('The directory has not been assigned to a course.');
+            }
+
+            if ($directory->getCourse()->getId() !== $course->getId()) {
+                throw new UserError('The directory has not been assgined to this course.');
+            }
+
+            $path = $directory->getPath();
+        }
+
+        $documents = \DocumentManager::getAllDocumentData(
+            ['code' => $course->getCode(), 'real_id' => $course->getId()],
+            $path,
+            0,
+            null,
+            false,
+            false,
+            $session ? $session->getId() : 0
+        );
+
+        if (empty($documents)) {
+            return [];
+        }
+
+        $webPath = api_get_path(WEB_CODE_PATH).'document/document.php?';
+
+        $results = array_map(
+            function ($documentInfo) use ($webPath, $course, $session) {
+                $icon = $documentInfo['filetype'] == 'file'
+                    ? choose_image($documentInfo['path'])
+                    : chooseFolderIcon($documentInfo['path']);
+
+                return [
+                    'id' => $documentInfo['id'],
+                    'fileType' => $documentInfo['filetype'],
+                    'title' => $documentInfo['title'],
+                    'comment' => $documentInfo['comment'],
+                    'path' => $documentInfo['path'],
+                    'icon' => $icon,
+                    'size' => format_file_size($documentInfo['size']),
+                    'url' => $webPath.http_build_query(
+                            [
+                                'username' => $this->getCurrentUser()->getUsername(),
+                                'api_key' => '', //$this->apiKey,
+                                'cidReq' => $course->getCode(),
+                                'id_session' => $session ? $session->getId() : 0,
+                                'gidReq' => 0,
+                                'gradebook' => 0,
+                                'origin' => '',
+                                'action' => 'download',
+                                'id' => $documentInfo['id'],
+                            ]
+                        ),
+                ];
+            },
+            $documents
+        );
+
+        return $results;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index 57a617d11e..fa6697b78d 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -228,6 +228,25 @@ type CourseAgendaEvent {
     backgroundColor: String
 }
 
+type ToolDocuments {
+    name: String
+    category: String
+    image: String
+    customIcon: String
+    documents(dirId: Int): [CourseDocument!]
+}
+
+type CourseDocument {
+  id: Int
+  fileType: String
+  title: String
+  comment: String
+  path: String
+  icon: String
+  size: String
+  url: String
+}
+
 "A session registered on the platform."
 type Session {
     "The unique ID of the session."
@@ -259,7 +278,7 @@ type SessionCategory {
 
 # Unions
 
-union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums | ToolAgenda
+union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums | ToolAgenda | ToolDocuments
 
 # Enums
 
@@ -298,6 +317,7 @@ enum CourseToolType {
     TOOL_NOTEBOOK
     TOOL_FORUM
     TOOL_CALENDAR_EVENT
+    TOOL_DOCUMENT
 }
 
 # Scalars

From 8c47c2f496b124ec091c7c63a42c5a62a814162f Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Tue, 2 Oct 2018 14:31:14 -0500
Subject: [PATCH 29/32] Minor - add type of variable

---
 src/CourseBundle/Entity/CDocument.php | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/CourseBundle/Entity/CDocument.php b/src/CourseBundle/Entity/CDocument.php
index ac686d80f0..e76bb502f1 100644
--- a/src/CourseBundle/Entity/CDocument.php
+++ b/src/CourseBundle/Entity/CDocument.php
@@ -85,6 +85,8 @@ class CDocument extends AbstractResource implements ResourceInterface
     protected $readonly;
 
     /**
+     * @var Course|null
+     *
      * @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\Course", cascade={"persist"})
      * @ORM\JoinColumn(name="c_id", referencedColumnName="id")
      */

From 043db15f3420e162036845873ac8fb7bccb74757 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Tue, 2 Oct 2018 15:13:13 -0500
Subject: [PATCH 30/32] GraphQL add query for learnpaths categories #2644

---
 src/ApiBundle/GraphQL/Map/EnumMap.php         |  1 +
 src/ApiBundle/GraphQL/Map/QueryMap.php        |  7 ++++++
 src/ApiBundle/GraphQL/Map/UnionMap.php        |  2 ++
 .../GraphQL/Resolver/CourseResolver.php       | 25 +++++++++++++++++++
 .../Resources/config/schema.types.graphql     | 17 ++++++++++++-
 5 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/src/ApiBundle/GraphQL/Map/EnumMap.php b/src/ApiBundle/GraphQL/Map/EnumMap.php
index 18f244110a..d49600c6ed 100644
--- a/src/ApiBundle/GraphQL/Map/EnumMap.php
+++ b/src/ApiBundle/GraphQL/Map/EnumMap.php
@@ -44,6 +44,7 @@ class EnumMap extends ResolverMap implements ContainerAwareInterface
                 'TOOL_FORUM' => TOOL_FORUM,
                 'TOOL_CALENDAR_EVENT' => TOOL_CALENDAR_EVENT,
                 'TOOL_DOCUMENT' => TOOL_DOCUMENT,
+                'TOOL_LEARNPATH' => TOOL_LEARNPATH,
             ],
         ];
     }
diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index f6ebcca728..7245f73388 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -329,6 +329,13 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                 },
             ],
             //'CourseDocument' => [],
+            'ToolLearningPath' => [
+                'categories' => function (CTool $tool, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                    return $resolver->getLearnpathCategories($context);
+                },
+            ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
                     Session $session,
diff --git a/src/ApiBundle/GraphQL/Map/UnionMap.php b/src/ApiBundle/GraphQL/Map/UnionMap.php
index b2898ad210..5ff6172183 100644
--- a/src/ApiBundle/GraphQL/Map/UnionMap.php
+++ b/src/ApiBundle/GraphQL/Map/UnionMap.php
@@ -38,6 +38,8 @@ class UnionMap extends ResolverMap implements ContainerAwareInterface
                             return 'ToolAgenda';
                         case TOOL_DOCUMENT:
                             return 'ToolDocuments';
+                        case TOOL_LEARNPATH:
+                            return 'ToolLearningPath';
                     }
                 },
             ],
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index a4d058e927..775a3112ad 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -12,6 +12,7 @@ use Chamilo\CourseBundle\Entity\CForumCategory;
 use Chamilo\CourseBundle\Entity\CForumForum;
 use Chamilo\CourseBundle\Entity\CForumThread;
 use Chamilo\CourseBundle\Entity\CItemProperty;
+use Chamilo\CourseBundle\Entity\CLpCategory;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\CourseBundle\Repository\CNotebookRepository;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -467,4 +468,28 @@ class CourseResolver implements ContainerAwareInterface
 
         return $results;
     }
+
+    /**
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getLearnpathCategories(\ArrayObject $context): array
+    {
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+
+        $none = new CLpCategory();
+        $none
+            ->setId(0)
+            ->setCId($course->getId())
+            ->setName($this->translator->trans('Without category.'))
+            ->setPosition(0);
+
+        $categories = \learnpath::getCategories($course->getId());
+
+        array_unshift($categories, $none);
+
+        return $categories;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index fa6697b78d..92f4c6f190 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -247,6 +247,20 @@ type CourseDocument {
   url: String
 }
 
+"A specific sequence of learning objects/experiences."
+type ToolLearningPath {
+    name: String
+    category: String
+    image: String
+    customIcon: String
+    categories: [CourseLearnpathCategory!]
+}
+
+type CourseLearnpathCategory {
+    id: Int
+    name: String
+}
+
 "A session registered on the platform."
 type Session {
     "The unique ID of the session."
@@ -278,7 +292,7 @@ type SessionCategory {
 
 # Unions
 
-union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums | ToolAgenda | ToolDocuments
+union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums | ToolAgenda | ToolDocuments | ToolLearningPath
 
 # Enums
 
@@ -318,6 +332,7 @@ enum CourseToolType {
     TOOL_FORUM
     TOOL_CALENDAR_EVENT
     TOOL_DOCUMENT
+    TOOL_LEARNPATH
 }
 
 # Scalars

From d35143964c2d3ffa5e9ae85a38e2e4891b7d9b37 Mon Sep 17 00:00:00 2001
From: Angel Fernando Quiroz Campos <angelfqc.18@gmail.com>
Date: Tue, 2 Oct 2018 16:46:45 -0500
Subject: [PATCH 31/32] GraphQL add query for learn paths #2644

---
 src/ApiBundle/GraphQL/Map/QueryMap.php        |  8 +++
 .../GraphQL/Resolver/CourseResolver.php       | 68 +++++++++++++++++++
 .../Resources/config/schema.types.graphql     |  7 ++
 3 files changed, 83 insertions(+)

diff --git a/src/ApiBundle/GraphQL/Map/QueryMap.php b/src/ApiBundle/GraphQL/Map/QueryMap.php
index 7245f73388..c48ba80cf9 100644
--- a/src/ApiBundle/GraphQL/Map/QueryMap.php
+++ b/src/ApiBundle/GraphQL/Map/QueryMap.php
@@ -14,6 +14,7 @@ use Chamilo\CourseBundle\Entity\CForumCategory;
 use Chamilo\CourseBundle\Entity\CForumForum;
 use Chamilo\CourseBundle\Entity\CForumPost;
 use Chamilo\CourseBundle\Entity\CForumThread;
+use Chamilo\CourseBundle\Entity\CLpCategory;
 use Chamilo\CourseBundle\Entity\CNotebook;
 use Chamilo\CourseBundle\Entity\CTool;
 use Chamilo\UserBundle\Entity\User;
@@ -336,6 +337,13 @@ class QueryMap extends ResolverMap implements ContainerAwareInterface
                     return $resolver->getLearnpathCategories($context);
                 },
             ],
+            'CourseLearnpathCategory' => [
+                'learnpaths' => function (CLpCategory $category, Argument $args, \ArrayObject $context) {
+                    $resolver = $this->container->get('chamilo_api.graphql.resolver.course');
+
+                    return $resolver->getLearnpathsByCategory($category, $context);
+                },
+            ],
             'Session' => [
                 self::RESOLVE_FIELD => function (
                     Session $session,
diff --git a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
index 775a3112ad..16a17f88bd 100644
--- a/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
+++ b/src/ApiBundle/GraphQL/Resolver/CourseResolver.php
@@ -492,4 +492,72 @@ class CourseResolver implements ContainerAwareInterface
 
         return $categories;
     }
+
+    /**
+     * @param CLpCategory  $category
+     * @param \ArrayObject $context
+     *
+     * @return array
+     */
+    public function getLearnpathsByCategory(CLpCategory $category, \ArrayObject $context): array
+    {
+        $user = $this->getCurrentUser();
+        /** @var Course $course */
+        $course = $context->offsetGet('course');
+        /** @var Session $session */
+        $session = $context->offsetGet('session');
+        $sessionId = $session ? $session->getId() : 0;
+
+        $lpList = new \LearnpathList(
+            $user->getId(),
+            $course->getCode(),
+            $sessionId,
+            null,
+            false,
+            $category->getId()
+        );
+
+        $flatList = $lpList->get_flat_list();
+        $lps = [];
+
+        foreach ($flatList as $lpId => $lpInfo) {
+            if (empty($lpInfo['lp_visibility'])) {
+                continue;
+            }
+
+            if (
+                !\learnpath::is_lp_visible_for_student($lpId, $user->getId(), $course->getCode(), $sessionId)
+            ) {
+                continue;
+            }
+
+            $timeLimits = !empty($lpInfo['expired_on']);
+
+            if ($timeLimits) {
+                if (!empty($lpInfo['publicated_on']) && !empty($lpInfo['expired_on'])) {
+                    $utc = new \DateTimeZone('UTC');
+
+                    $starTime = new \DateTime($lpInfo['publicated_on'], $utc);
+                    $endTime = new \DateTime($lpInfo['expired_on'], $utc);
+                    $now = new \DateTime('now', $utc);
+
+                    $isActived = $now > $starTime && $endTime > $now;
+
+                    if (!$isActived) {
+                        continue;
+                    }
+                }
+            }
+
+            $progress = \learnpath::getProgress($lpId, $user->getId(), $course->getId(), $sessionId);
+
+            $lps[] = [
+                'id' => $lpId,
+                'title' => \Security::remove_XSS($lpInfo['lp_name']),
+                'progress' => (int) $progress,
+            ];
+        }
+
+        return $lps;
+    }
 }
diff --git a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
index 92f4c6f190..b1a2d80c6a 100644
--- a/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
+++ b/src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
@@ -259,6 +259,13 @@ type ToolLearningPath {
 type CourseLearnpathCategory {
     id: Int
     name: String
+    learnpaths: [CourseLearnpath!]
+}
+
+type CourseLearnpath {
+    id: Int
+    title: String
+    progress: Int
 }
 
 "A session registered on the platform."

From dfc1dc0ec132d36e02b7c80ffa4878202e47d7b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Arag=C3=B3n?= <aragcar@gmail.com>
Date: Tue, 2 Oct 2018 17:30:46 -0500
Subject: [PATCH 32/32] update macro panel box.html.twig - refs #2681

---
 assets/css/scss/_base.scss                    |  6 ++-
 .../user_portal/course_categories.html.twig   |  4 +-
 .../grid_courses_without_category.html.twig   |  2 +-
 .../default/layout/hot_course_item.html.twig  |  2 +-
 .../Resources/views/Macros/box.html.twig      | 42 ++++++++++++++++---
 5 files changed, 46 insertions(+), 10 deletions(-)

diff --git a/assets/css/scss/_base.scss b/assets/css/scss/_base.scss
index ca2927fe07..066d24d307 100644
--- a/assets/css/scss/_base.scss
+++ b/assets/css/scss/_base.scss
@@ -260,7 +260,6 @@ footer {
 }
 
 .card{
-  border: none;
   transform: perspective(1px) translateZ(0);
   transition-property: box-shadow;
   transition-duration: 0.3s;
@@ -269,6 +268,11 @@ footer {
   &:focus{
     box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.20);
   }
+  &.card-category{
+    .card-subtitle{
+      font-size: 12px;
+    }
+  }
   /*&.card-line{
     &:hover{
       &:before{
diff --git a/main/template/default/user_portal/course_categories.html.twig b/main/template/default/user_portal/course_categories.html.twig
index 724fd7de20..bc3ffbd762 100644
--- a/main/template/default/user_portal/course_categories.html.twig
+++ b/main/template/default/user_portal/course_categories.html.twig
@@ -11,7 +11,7 @@
             {% set image %}
                 <a href="{{ _p.web_self ~ '?' ~ {'category':category.code}|url_encode }}">
                     <img src="{{ category.image ? _p.web_upload ~ category.image : 'session_default.png'|icon(128) }}"
-                         alt="{{ category.name }}" class="img-fluid">
+                         alt="{{ category.name }}" class="img-fluid mb-1">
                 </a>
             {% endset %}
 
@@ -36,7 +36,7 @@
                 </div>
             {% endset %}
 
-            {{ macro.rowpanel('category', title, subtitle, content, image) }}
+            {{ macro.panel_row('category', title, subtitle, content, image) }}
 
         {% endif %}
     </div>
diff --git a/main/template/default/user_portal/grid_courses_without_category.html.twig b/main/template/default/user_portal/grid_courses_without_category.html.twig
index 95dd7053c9..53bb6c499b 100644
--- a/main/template/default/user_portal/grid_courses_without_category.html.twig
+++ b/main/template/default/user_portal/grid_courses_without_category.html.twig
@@ -65,7 +65,7 @@
                         <div class="notifications">{{ item.notifications }}</div>
                     {% endif %}
                 {% endset %}
-                {{ macro.panel('', content, '', '', '', image) }}
+                {{ macro.panel_course('course', '', content, '', '', '', image) }}
             {% endif %}
             </div>
         {% endfor %}
diff --git a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
index 0c63010828..a8773fe7ac 100644
--- a/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
+++ b/src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
@@ -67,7 +67,7 @@
                     </div>
                 </div> -->
             {% endset %}
-            {{ macro.panel('', content, '', '', '', image) }}
+            {{ macro.panel_course('course', '', content, '', '', '', image) }}
     {% endif %}
     </div>
 {% endfor %}
diff --git a/src/ThemeBundle/Resources/views/Macros/box.html.twig b/src/ThemeBundle/Resources/views/Macros/box.html.twig
index 1c8af67390..0950a63dc0 100644
--- a/src/ThemeBundle/Resources/views/Macros/box.html.twig
+++ b/src/ThemeBundle/Resources/views/Macros/box.html.twig
@@ -154,14 +154,43 @@
 
 {% macro panel(header, content, title, footer, subtitle, top_image) %}
     {% autoescape false %}
-        <div class="card card-line mt-3 mb-3">
+        <div class="card mt-3 mb-3">
             {% if header %}
                 <div class="card-header"> {{ header }} </div>
             {% endif %}
             {% if top_image %}
                 {{ top_image }}
             {% endif %}
-            <div class="card-body pb-3">
+            <div class="card-body p-3">
+                {% if title %}
+                    <h5 class="card-title">{{ title }}</h5>
+                {% endif %}
+                {% if subtitle %}
+                    <h5 class="card-subtitle mb-2 text-muted">{{ subtitle }}</h5>
+                {% endif %}
+
+                {{ content }}
+
+            </div>
+            {% if footer %}
+                <div class="card-footer">
+                    {{ footer }}
+                </div>
+            {% endif %}
+        </div>
+    {% endautoescape %}
+{% endmacro %}
+
+{% macro panel_course(id, header, content, title, footer, subtitle, top_image) %}
+    {% autoescape false %}
+        <div class="card card-{{ id }} mt-3 mb-3">
+            {% if header %}
+                <div class="card-header"> {{ header }} </div>
+            {% endif %}
+            {% if top_image %}
+                {{ top_image }}
+            {% endif %}
+            <div class="card-body p-3">
                 {% if title %}
                     <h5 class="card-title">{{ title }}</h5>
                 {% endif %}
@@ -181,9 +210,9 @@
     {% endautoescape %}
 {% endmacro %}
 
-{% macro rowpanel(id, title, subtitle, content, image) %}
+{% macro panel_row(id, title, subtitle, content, image) %}
     {% autoescape false %}
-        <div class="card card-line card-{{ id }} mt-3 mb-3">
+        <div class="card card-{{ id }} p-3 mt-3 mb-3">
             <div class="card-body pb-3">
                 <div class="row">
                     <div class="col-sm-3">
@@ -196,7 +225,10 @@
                             <h5 class="card-title">{{ title }}</h5>
                         {% endif %}
                         {% if subtitle %}
-                            <p class="card-subtitle mb-2 text-muted">{{ subtitle }}</p>
+                            <p class="card-subtitle mb-2 text-muted">
+                                <strong>{{ 'Code'|get_lang }} :</strong>
+                                {{ subtitle }}
+                            </p>
                         {% endif %}
                         {% if content %}
                             <div class="description">