Merge branch 'master' of github.com:chamilo/chamilo-lms

pull/2715/head
Julio Montoya 7 years ago
commit 8213a13688
  1. 25
      assets/css/base.css
  2. 95
      assets/css/scss/_base.scss
  3. 8
      assets/css/scss/_sidebar.scss
  4. 7
      assets/css/scss/_variables.scss
  5. 13
      assets/css/themes/chamilo/default.css
  6. 35
      config/packages/graphql.yaml
  7. 1
      config/packages/security.yaml
  8. 2
      config/packages/sonata_media.yaml
  9. 3
      config/services.yaml
  10. 10
      main/admin/skills_gradebook.php
  11. 2
      main/course_info/about.php
  12. 2
      main/exercise/upload_exercise.php
  13. 14
      main/forum/forumfunction.inc.php
  14. 41
      main/inc/lib/AnnouncementManager.php
  15. 2
      main/lp/learnpath.class.php
  16. 188
      main/template/default/course_home/about.html.twig
  17. 58
      main/template/default/user_portal/course_categories.html.twig
  18. 156
      main/template/default/user_portal/grid_courses_without_category.html.twig
  19. 2
      main/webservices/cm_webservice_forum.php
  20. BIN
      public/img/session_default.png
  21. 28
      src/ApiBundle/GraphQL/ApiGraphQLTrait.php
  22. 51
      src/ApiBundle/GraphQL/Map/EnumMap.php
  23. 42
      src/ApiBundle/GraphQL/Map/EnumResolverMap.php
  24. 115
      src/ApiBundle/GraphQL/Map/MutationMap.php
  25. 482
      src/ApiBundle/GraphQL/Map/QueryMap.php
  26. 277
      src/ApiBundle/GraphQL/Map/RootResolverMap.php
  27. 2
      src/ApiBundle/GraphQL/Map/ScalarMap.php
  28. 48
      src/ApiBundle/GraphQL/Map/UnionMap.php
  29. 49
      src/ApiBundle/GraphQL/Mutation/RootMutation.php
  30. 88
      src/ApiBundle/GraphQL/Mutation/UserMutation.php
  31. 88
      src/ApiBundle/GraphQL/Resolver/CourseAnnouncementResolver.php
  32. 54
      src/ApiBundle/GraphQL/Resolver/CourseDescriptionResolver.php
  33. 541
      src/ApiBundle/GraphQL/Resolver/CourseResolver.php
  34. 59
      src/ApiBundle/GraphQL/Resolver/CourseToolResolver.php
  35. 51
      src/ApiBundle/GraphQL/Resolver/CourseToolResolverTrait.php
  36. 78
      src/ApiBundle/GraphQL/Resolver/SessionResolver.php
  37. 73
      src/ApiBundle/GraphQL/Resolver/ToolAnnouncementsResolver.php
  38. 60
      src/ApiBundle/GraphQL/Resolver/ToolDescriptionResolver.php
  39. 99
      src/ApiBundle/GraphQL/Resolver/UserResolver.php
  40. 9
      src/ApiBundle/GraphQL/Resources/config/Enum.types.yaml
  41. 45
      src/ApiBundle/GraphQL/Resources/config/Mutation.types.yaml
  42. 89
      src/ApiBundle/GraphQL/Resources/config/Query.types.yaml
  43. 228
      src/ApiBundle/GraphQL/Resources/config/schema.types.graphql
  44. 4
      src/ClassificationBundle/Resources/config/doctrine/Context.orm.xml
  45. 18
      src/CoreBundle/Entity/Course.php
  46. 16
      src/CoreBundle/Entity/Message.php
  47. 39
      src/CoreBundle/Migrations/Schema/V200/Version20180927172830.php
  48. 94
      src/CoreBundle/Resources/views/default/layout/hot_course_item.html.twig
  49. 6
      src/CoreBundle/Resources/views/default/layout/hot_courses.html.twig
  50. 2
      src/CourseBundle/Entity/CDocument.php
  51. 55
      src/CourseBundle/Entity/CForumCategory.php
  52. 68
      src/CourseBundle/Entity/CForumForum.php
  53. 25
      src/CourseBundle/Entity/CForumPost.php
  54. 64
      src/CourseBundle/Entity/CForumThread.php
  55. 40
      src/CourseBundle/Entity/CGroupInfo.php
  56. 2
      src/CourseBundle/Entity/Manager/AnnouncementManager.php
  57. 119
      src/CourseBundle/Repository/CForumCategoryRepository.php
  58. 119
      src/CourseBundle/Repository/CForumForumRepository.php
  59. 80
      src/CourseBundle/Repository/CForumPostRepository.php
  60. 129
      src/CourseBundle/Repository/CForumThreadRepository.php
  61. 161
      src/ThemeBundle/Resources/views/Layout/topbar.html.twig
  62. 67
      src/ThemeBundle/Resources/views/Macros/box.html.twig

@ -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;

@ -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,93 @@ footer {
display: block;
padding: 10px;
position: relative;
}
}
.carousel-item img{
max-width: 100%;
height: auto;
}
.card{
transform: perspective(1px) translateZ(0);
transition-property: box-shadow;
transition-duration: 0.3s;
box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.15);
&:hover,
&:focus{
box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.20);
}
&.card-category{
.card-subtitle{
font-size: 12px;
}
}
/*&.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;
background-color: $default-yellow;
color: $default-black;
font-weight: bold;
padding: 0.2rem 0.8rem;
top: -0.8rem;
left: 0.5rem;
font-size: 12px;
}
.card-body{
padding: 0.5rem 0.25rem;
.card-title{
margin-bottom: 0.5rem;
.title{
font-size: 16px;
font-weight: bold;
line-height: 22px;
}
}
.card-author{
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: " ";
}
}
}
}
}
}

@ -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;

@ -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;

@ -8,16 +8,3 @@
body {
}
a {
color: #337AB7;
text-decoration: none;
}
a:hover,
a:focus {
color: #2E75A3;
text-decoration: none;
}
a:focus {
outline: none;
}

@ -2,13 +2,40 @@ 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_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%'
- '%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.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' ]
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\Map\:
resource: "../../src/ApiBundle/GraphQL/Map/*"
arguments:
- "@service_container"

@ -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

@ -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

@ -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:

@ -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.'\';
}
}';
?>

@ -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);

@ -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;
}

@ -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();
@ -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)
;
@ -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)

@ -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] : '';
@ -381,6 +376,10 @@ class AnnouncementManager
]
);
if (empty($result)) {
return [];
}
return [
'announcement' => $result[0],
'item_property' => $result[1],

@ -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();
}
}

@ -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>

@ -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 mb-1">
</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.panel_row('category', title, subtitle, content, image) }}
{% endif %}
</div>
{% endfor %}
</ul>
</div>
{% endautoescape %}

@ -1,3 +1,79 @@
{% 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-3 col-md-3">
{% 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 %}
{% 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="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"
data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
{{ teacher.firstname }} {{ teacher.lastname }}
</a>
</div>
{% endfor %}
</div>
{% else %}
{% for teacher in item.teachers %}
{% if item.teachers | length <= 2 %}
<a href="{{ teacher.url }}" class="ajax name"
data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
{{ teacher.firstname }} {{ teacher.lastname }}
</a>
{% endif %}
{% endfor %}
{% endif %}
</div>
{% if item.notifications %}
<div class="notifications">{{ item.notifications }}</div>
{% endif %}
{% endset %}
{{ macro.panel_course('course', '', content, '', '', '', image) }}
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if not courses is empty %}
<div class="grid-courses">
<div class="row">
@ -5,20 +81,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.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>
{% endif %}
{% if item.edit_actions != '' %}
<div class="admin-actions">
@ -38,66 +103,9 @@
{% 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 %}
{% 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">

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -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
*

@ -0,0 +1,51 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\ApiBundle\GraphQL\Map;
use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
use Chamilo\UserBundle\Entity\User;
use Overblog\GraphQLBundle\Resolver\ResolverMap;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
* Class EnumResolverMap.
*
* @package Chamilo\ApiBundle\GraphQL\Map
*/
class EnumMap extends ResolverMap implements ContainerAwareInterface
{
use ApiGraphQLTrait;
/**
* @return array
*/
protected function map()
{
return [
'UserStatus' => [
'TEACHER' => User::TEACHER,
'SESSION_ADMIN' => User::SESSION_ADMIN,
'DRH' => User::DRH,
'STUDENT' => User::STUDENT,
],
'ImageSize' => [
'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,
'TOOL_ANNOUNCEMENT' => TOOL_ANNOUNCEMENT,
'TOOL_NOTEBOOK' => TOOL_NOTEBOOK,
'TOOL_FORUM' => TOOL_FORUM,
'TOOL_CALENDAR_EVENT' => TOOL_CALENDAR_EVENT,
'TOOL_DOCUMENT' => TOOL_DOCUMENT,
'TOOL_LEARNPATH' => TOOL_LEARNPATH,
],
];
}
}

@ -1,42 +0,0 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\ApiBundle\GraphQL\Map;
use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
use Chamilo\UserBundle\Entity\User;
use Overblog\GraphQLBundle\Resolver\ResolverMap;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
* Class EnumResolverMap.
*
* @package Chamilo\ApiBundle\GraphQL\Map
*/
class EnumResolverMap extends ResolverMap implements ContainerAwareInterface
{
use ApiGraphQLTrait;
/**
* @return array
*/
protected function map()
{
return [
'UserStatus' => [
'TEACHER' => User::TEACHER,
'SESSION_ADMIN' => User::SESSION_ADMIN,
'DRH' => User::DRH,
'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,
],
];
}
}

@ -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 MutationMap 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;
}
}

@ -0,0 +1,482 @@
<?php
/* For licensing terms, see /license.txt */
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;
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\CLpCategory;
use Chamilo\CourseBundle\Entity\CNotebook;
use Chamilo\CourseBundle\Entity\CTool;
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 QueryMap.
*
* @package Chamilo\ApiBundle\GraphQL\Map
*/
class QueryMap extends ResolverMap implements ContainerAwareInterface
{
use ApiGraphQLTrait;
/**
* @return array
*/
protected function map()
{
return [
'Query' => [
self::RESOLVE_FIELD => function ($value, Argument $args, \ArrayObject $context, ResolveInfo $info) {
$context->offsetSet('course', null);
$context->offsetSet('session', null);
$method = 'resolve'.ucfirst($info->fieldName);
return $this->$method($args, $context);
},
],
'User' => [
self::RESOLVE_FIELD => function (
User $user,
Argument $args,
\ArrayObject $context,
ResolveInfo $info
) {
$context->offsetSet('user', $user);
$resolver = $this->container->get('chamilo_api.graphql.resolver.user');
return $this->resolveField($info->fieldName, $user, $resolver, $args, $context);
},
],
'UserMessage' => [
self::RESOLVE_FIELD => function (
Message $message,
Argument $args,
\ArrayObject $context,
ResolveInfo $info
) {
if ('sender' === $info->fieldName) {
return $message->getUserSender();
}
if ('hasAttachments' === $info->fieldName) {
return $message->getAttachments()->count() > 0;
}
return $this->resolveField($info->fieldName, $message);
},
],
'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' => [
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 $this->resolveField($info->fieldName, $tool);
},
],
//'CourseDescription' => [],
'ToolAnnouncements' => [
self::RESOLVE_FIELD => function (
CTool $tool,
Argument $args,
\ArrayObject $context,
ResolveInfo $info
) {
$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);
},
],
'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
);
},
],
'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();
},
],
'ToolForums' => [
self::RESOLVE_FIELD => function (
CTool $tool,
Argument $args,
\ArrayObject $context,
ResolveInfo $info
) {
$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);
}
if ('thread' === $info->fieldName) {
return $resolver->getThread($args['id'], $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();
},
'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, 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();
},
'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;
},
],
'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'));
},
],
'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' => [],
'ToolLearningPath' => [
'categories' => function (CTool $tool, Argument $args, \ArrayObject $context) {
$resolver = $this->container->get('chamilo_api.graphql.resolver.course');
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,
Argument $args,
\ArrayObject $context,
ResolveInfo $info
) {
$context->offsetSet('session', $session);
$resolver = $this->container->get('chamilo_api.graphql.resolver.session');
return $this->resolveField($info->fieldName, $session, $resolver, $args, $context);
},
],
'SessionCategory' => [
self::RESOLVE_FIELD => function (
SessionCategory $category,
Argument $args,
\ArrayObject $context,
ResolveInfo $info
) {
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
*/
protected function resolveViewer()
{
$this->checkAuthorization();
return $this->getCurrentUser();
}
/**
* @param Argument $args
*
* @return Course
*/
protected function resolveCourse(Argument $args)
{
$this->checkAuthorization();
$id = (int) $args['id'];
$courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
$course = $courseRepo->find($id);
if (!$course) {
throw new UserError($this->translator->trans('Course not found.'));
}
$checker = $this->container->get('security.authorization_checker');
if (false === $checker->isGranted(CourseVoter::VIEW, $course)) {
throw new UserError($this->translator->trans('Not allowed'));
}
return $course;
}
/**
* @param Argument $args
*
* @return Session
*/
protected function resolveSession(Argument $args)
{
$this->checkAuthorization();
$sessionRepo = $this->em->getRepository('ChamiloCoreBundle:Session');
/** @var Session $session */
$session = $sessionRepo->find($args['id']);
if (!$session) {
throw new UserError($this->translator->trans('Session not found.'));
}
return $session;
}
/**
* @param Argument $args
*
* @return SessionCategory
*/
protected function resolveSessionCategory(Argument $args)
{
$this->checkAuthorization();
$repo = $this->em->getRepository('ChamiloCoreBundle:SessionCategory');
/** @var SessionCategory $category */
$category = $repo->find($args['id']);
if (!$category) {
throw new UserError($this->translator->trans('Session category not found.'));
}
return $category;
}
}

@ -1,277 +0,0 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\ApiBundle\GraphQL\Map;
use Chamilo\ApiBundle\GraphQL\ApiGraphQLTrait;
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\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 RootResolverMap
* @package Chamilo\ApiBundle\GraphQL\Map
*/
class RootResolverMap extends ResolverMap implements ContainerAwareInterface
{
use ApiGraphQLTrait;
/**
* @return array
*/
protected function map()
{
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']);
}
},
],
'UserMessage' => [
self::RESOLVE_FIELD => function (
Message $message,
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' => [
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;
}
}
$courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
$teachers = $courseRepo
->getSubscribedTeachers($course)
->getQuery()
->getResult();
return $teachers;
default:
$method = 'get'.ucfirst($info->fieldName);
if (method_exists($course, $method)) {
return $course->$method();
}
return null;
}
}
],
'Session' => [
self::RESOLVE_FIELD => function (
Session $session,
Argument $args,
\ArrayObject $context,
ResolveInfo $info
) {
$context->offsetSet('session', $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;
}
},
],
'SessionCategory' => [
self::RESOLVE_FIELD => function (
SessionCategory $category,
Argument $args,
\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;
}
}
],
];
}
/**
* @return User
*/
private function resolveViewer()
{
$this->checkAuthorization();
return $this->getCurrentUser();
}
/**
* @param int $id
*
* @return Course
*/
private function resolveCourse($id)
{
$this->checkAuthorization();
$courseRepo = $this->em->getRepository('ChamiloCoreBundle:Course');
$course = $courseRepo->find((int) $id);
if (!$course) {
throw new UserError($this->translator->trans('Course not found.'));
}
$checker = $this->container->get('security.authorization_checker');
if (false === $checker->isGranted(CourseVoter::VIEW, $course)) {
throw new UserError($this->translator->trans('Not allowed'));
}
return $course;
}
/**
* @param int $id
*
* @return Session
*/
private function resolveSession($id)
{
$this->checkAuthorization();
$sessionRepo = $this->em->getRepository('ChamiloCoreBundle:Session');
/** @var Session $session */
$session = $sessionRepo->find((int) $id);
if (!$session) {
throw new UserError($this->translator->trans('Session not found.'));
}
return $session;
}
/**
* @param int $id
*
* @return SessionCategory
*/
private function resolveSessionCategory($id)
{
$this->checkAuthorization();
$repo = $this->em->getRepository('ChamiloCoreBundle:SessionCategory');
/** @var SessionCategory $category */
$category = $repo->find((int) $id);
if (!$category) {
throw new UserError($this->translator->trans('Session category not found.'));
}
return $category;
}
}

@ -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;

@ -0,0 +1,48 @@
<?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:
return 'ToolAnnouncements';
case TOOL_NOTEBOOK:
return 'ToolNotebook';
case TOOL_FORUM:
return 'ToolForums';
case TOOL_CALENDAR_EVENT:
return 'ToolAgenda';
case TOOL_DOCUMENT:
return 'ToolDocuments';
case TOOL_LEARNPATH:
return 'ToolLearningPath';
}
},
],
];
}
}

@ -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,
];
}
}

@ -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;
}
}

@ -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();
}
}

@ -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();
}
}

@ -7,9 +7,18 @@ 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\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;
use Doctrine\Common\Collections\Criteria;
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
use Overblog\GraphQLBundle\Error\UserError;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
@ -17,10 +26,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 +48,516 @@ class CourseResolver implements ResolverInterface, ContainerAwareInterface
*
* @return array
*/
public function resolveTools(Course $course, Argument $args, \ArrayObject $context)
public function getTeachers(Course $course, Argument $args, \ArrayObject $context): array
{
$sessionId = 0;
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
{
$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) {
$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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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;
}
}

@ -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');
}
}
}

@ -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();
}
}

@ -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;
}
}

@ -1,73 +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 Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
* Class ToolAnnouncementsResolver.
*
* @package Chamilo\ApiBundle\GraphQL\Resolver
*/
class ToolAnnouncementsResolver implements ResolverInterface, 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
{
/** @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;
}
}

@ -1,60 +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 Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
* Class ToolDescriptionResolver.
*
* @package Chamilo\ApiBundle\GraphQL\Resolver
*/
class ToolDescriptionResolver implements ResolverInterface, 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
{
/** @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');
$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();
}
}

@ -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 UserResolverMap 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 UserResolverMap 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 UserResolverMap 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 UserResolverMap 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 [];

@ -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")'

@ -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!'

@ -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'

@ -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."
@ -21,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."
@ -67,6 +92,180 @@ 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
}
"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
category: String
image: String
customIcon: String
announcements: [CourseAnnouncement!]
announcement(id: Int!): CourseAnnouncement
}
"A course announcement."
type CourseAnnouncement {
id: Int
title: String
content: String
author: User!
lastUpdateDate: DateTime
}
"Course forum tool."
type ToolForums {
name: String
category: String
image: String
customIcon: String
categories: [CourseForumCategory!]
forum(id: Int!): CourseForum
thread(id: Int!): CourseForumThread
}
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 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
}
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 specific sequence of learning objects/experiences."
type ToolLearningPath {
name: String
category: String
image: String
customIcon: String
categories: [CourseLearnpathCategory!]
}
type CourseLearnpathCategory {
id: Int
name: String
learnpaths: [CourseLearnpath!]
}
type CourseLearnpath {
id: Int
title: String
progress: Int
}
"A session registered on the platform."
@ -98,6 +297,10 @@ type SessionCategory {
endDate: DateTime
}
# Unions
union CourseTool = ToolDescription | ToolAnnouncements | ToolNotebook | ToolForums | ToolAgenda | ToolDocuments | ToolLearningPath
# Enums
"One of the statuses for the user."
@ -115,17 +318,28 @@ 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"
enum CourseToolType {
TOOL_COURSE_DESCRIPTION
TOOL_ANNOUNCEMENT
TOOL_NOTEBOOK
TOOL_FORUM
TOOL_CALENDAR_EVENT
TOOL_DOCUMENT
TOOL_LEARNPATH
}
# Scalars

@ -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>

@ -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);
}
/**

@ -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);
}
}

@ -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)
{
}
}

@ -1,72 +1,58 @@
{% 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 %}
<img src="{{ item.course_image_large }}" class="card-img-top" alt="{{ item.title }}">
{% 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>
{% 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>
<h5 class="title">
<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 %}
<h5 class="title" title="{{ item.title }}">
{{ item.title_cut}}
</h5>
{% 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>
<div class="ranking">
{{ item.rating_html }}
</div>
<div class="block-author">
{% 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 }}">
<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 }}">
{{ teacher.firstname }} {{ teacher.lastname }}
</a>
</h5>
<p>{{ 'Teacher' | get_lang }}</p>
</div>
{% endif %}
{% endfor %}
</div>
<!--
<div class="toolbar row">
<div class="col-sm-4">
{#{% if item.price %}#}
@ -75,16 +61,14 @@
</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) }}
{{ macro.panel_course('course', '', content, '', '', '', image) }}
{% endif %}
</div>
{% endfor %}
{% endautoescape %}

@ -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 %}

@ -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")
*/

@ -3,6 +3,7 @@
namespace Chamilo\CourseBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
@ -15,7 +16,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 +78,28 @@ class CForumCategory
*/
protected $catId;
/**
* @var CItemProperty
*/
private $itemProperty;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Chamilo\CourseBundle\Entity\CForumForum", mappedBy="forumCategory")
*/
private $forums;
/**
* Get iid.
*
* @return int
*/
public function getIid()
{
return $this->iid;
}
/**
* Set catTitle.
*
@ -244,4 +267,34 @@ class CForumCategory
{
return $this->cId;
}
/**
* Get forums.
*
* @return ArrayCollection
*/
public function getForums()
{
return $this->forums;
}
/**
* @param CItemProperty $itemProperty
*
* @return CForumCategory
*/
public function setItemProperty(CItemProperty $itemProperty)
{
$this->itemProperty = $itemProperty;
return $this;
}
/**
* @return CItemProperty
*/
public function getItemProperty()
{
return $this->itemProperty;
}
}

@ -3,6 +3,7 @@
namespace Chamilo\CourseBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
@ -14,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
{
@ -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,18 @@ class CForumForum
*/
protected $moderated;
/**
* @var CItemProperty
*/
protected $itemProperty;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Chamilo\CourseBundle\Entity\CForumThread", mappedBy="forum")
*/
protected $threads;
/**
* Set forumTitle.
*
@ -318,11 +332,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 +346,7 @@ class CForumForum
/**
* Get forumCategory.
*
* @return int
* @return CForumCategory|null
*/
public function getForumCategory()
{
@ -762,4 +776,46 @@ class CForumForum
return $this;
}
/**
* Get iid.
*
* @return int
*/
public function getIid()
{
return $this->iid;
}
/**
* Get threads.
*
* @return ArrayCollection
*/
public function getThreads()
{
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;
}
}

@ -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
{
@ -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;
}
/**

@ -3,6 +3,7 @@
namespace Chamilo\CourseBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
@ -15,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
{
@ -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,18 @@ class CForumThread
*/
protected $lpItemId;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Chamilo\CourseBundle\Entity\CForumPost", mappedBy="thread")
*/
protected $posts;
/**
* @var CItemProperty
*/
protected $itemProperty;
/**
* Constructor.
*/
@ -218,15 +232,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 +248,11 @@ class CForumThread
/**
* Get forumId.
*
* @return int
* @return CForumForum|null
*/
public function getForumId()
public function getForum()
{
return $this->forumId;
return $this->forum;
}
/**
@ -634,4 +648,32 @@ class CForumThread
{
return $this->iid;
}
/**
* @return ArrayCollection
*/
public function getPosts()
{
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;
}
}

@ -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;
/**
@ -188,6 +190,16 @@ class CGroupInfo
*/
protected $tutors;
/**
* Get iid.
*
* @return int
*/
public function getIid()
{
return $this->iid;
}
/**
* Set name.
*
@ -675,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;
}
}

@ -29,8 +29,6 @@ class AnnouncementManager extends BaseEntityManager
* @param string $titleToSearch
* @param User|null $userToSearch
*
* @throws \Doctrine\ORM\NonUniqueResultException
*
* @return mixed
*/
public function getAnnouncements(

@ -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;
}
}

@ -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\CForumForum;
use Chamilo\CourseBundle\Entity\CItemProperty;
use Doctrine\ORM\EntityRepository;
/**
* Class CForumForumRepository.
*
* @package Chamilo\CourseBundle\Repository
*/
class CForumForumRepository extends EntityRepository
{
/**
* @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 findAllInCourseByCategory(
$isAllowedToEdit,
CForumCategory $category,
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 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
$conditionVisibility
$conditionGroups
ORDER BY f.forumOrder ASC";
$result = $this
->_em
->createQuery($dql)
->setParameters(['category' => $category, 'course' => $course, 'tool' => TOOL_FORUM])
->getResult();
$forums = [];
for ($i = 0; $i < count($result); $i += 2) {
/** @var CForumForum $f */
$f = $result[$i];
/** @var CItemProperty $ip */
$ip = $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 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 AND
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 CForumForum $f */
$f = $result[0];
/** @var CItemProperty $ip */
$ip = $result[1];
$f->setItemProperty($ip);
return $f;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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>

@ -154,14 +154,14 @@
{% macro panel(header, content, title, footer, subtitle, top_image) %}
{% autoescape false %}
<div class="card" style="width: 20rem;">
<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">
<div class="card-body p-3">
{% if title %}
<h5 class="card-title">{{ title }}</h5>
{% endif %}
@ -179,4 +179,65 @@
{% endif %}
</div>
{% endautoescape %}
{% endmacro %}
{% 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 %}
{% 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_row(id, title, subtitle, content, image) %}
{% autoescape false %}
<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">
{% 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">
<strong>{{ 'Code'|get_lang }} :</strong>
{{ subtitle }}
</p>
{% endif %}
{% if content %}
<div class="description">
{{ content }}
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endautoescape %}
{% endmacro %}

Loading…
Cancel
Save