Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into chamiloGH-6048
Browse files Browse the repository at this point in the history
  • Loading branch information
christianbeeznest committed Feb 12, 2025
2 parents 9a76174 + 2117d33 commit e1a1089
Show file tree
Hide file tree
Showing 11 changed files with 1,180 additions and 1,078 deletions.
925 changes: 0 additions & 925 deletions .yarn/releases/yarn-4.4.1.cjs

This file was deleted.

934 changes: 934 additions & 0 deletions .yarn/releases/yarn-4.6.0.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.4.1.cjs
yarnPath: .yarn/releases/yarn-4.6.0.cjs
16 changes: 12 additions & 4 deletions assets/vue/views/user/courses/StickyCourses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@
v-if="courses.length"
class="mb-6"
>
<h2 class="mb-2">{{ $t("Sticky courses") }}</h2>
<SectionHeader :title="t('Sticky courses')" />
<div
v-if="courses.length"
class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mt-2"
>
<CourseCardList :courses="courses" />
</div>

<BaseDivider />
</div>
</template>

<script setup>
import CourseCardList from "../../../components/course/CourseCardList.vue"
import { computed, ref, watchEffect } from "vue"
import { GET_STICKY_COURSES } from "../../../graphql/queries/Course"
import { useQuery } from "@vue/apollo-composable"
import CourseCardList from "../../../components/course/CourseCardList.vue"
import SectionHeader from "../../../components/layout/SectionHeader.vue"
import BaseDivider from "../../../components/basecomponents/BaseDivider.vue"
import { useSecurityStore } from "../../../store/securityStore"
import { useQuery } from "@vue/apollo-composable"
import { GET_STICKY_COURSES } from "../../../graphql/queries/Course"
const securityStore = useSecurityStore()
const queryResponse = ref({})
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,5 @@
"autoprefixer": "10.4.5"
},
"license": "GPL-3.0",
"packageManager": "yarn@4.4.1"
"packageManager": "yarn@4.6.0"
}
254 changes: 133 additions & 121 deletions public/main/admin/questions.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
<?php
/* For licensing terms, see /license.txt */

use Chamilo\CoreBundle\Entity\ResourceNode;
use Chamilo\CoreBundle\Framework\Container;
use Chamilo\CourseBundle\Entity\CQuiz;
use Chamilo\CourseBundle\Entity\CQuizQuestion;
use ChamiloSession as Session;
use Doctrine\Common\Collections\Criteria;
use Knp\Component\Pager\Event\Subscriber\Paginate\PaginationSubscriber;
use Knp\Component\Pager\Event\Subscriber\Sortable\SortableSubscriber;
use Knp\Component\Pager\Paginator;
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
use Symfony\Component\EventDispatcher\EventDispatcher;

$cidReset = true;
require_once __DIR__.'/../inc/global.inc.php';
Expand Down Expand Up @@ -140,9 +144,15 @@
$criteria->orWhere($criteria->expr()->contains('question', "%$title%"));
}

// if (-1 !== $selectedCourse) {
// $criteria->andWhere($criteria->expr()->eq('cId', $selectedCourse));
// }
if (-1 !== $selectedCourse) {
/** @var \Chamilo\CoreBundle\Repository\ResourceNodeRepository $resourceNodeRepo */
$resourceNodeRepo = $em->getRepository(ResourceNode::class);
$resourceNodes = $resourceNodeRepo->findByResourceTypeAndCourse('questions', api_get_course_entity($selectedCourse));

$criteria->andWhere(
Criteria::expr()->in('resourceNode', $resourceNodes)
);
}

if (-1 !== $questionLevel) {
$criteria->andWhere($criteria->expr()->eq('level', $questionLevel));
Expand All @@ -161,7 +171,11 @@
$length = $questionCount;
}

$paginator = new Paginator(Container::$container->get('event_dispatcher'));
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new PaginationSubscriber());
$dispatcher->addSubscriber(new SortableSubscriber());

$paginator = new Paginator($dispatcher);
$pagination = $paginator->paginate($questions, $page, $length);
$pagination->setItemNumberPerPage($length);
$pagination->setCurrentPageNumber($page);
Expand All @@ -180,134 +194,132 @@
return $render;
};

if ($pagination) {
$urlExercise = api_get_path(WEB_CODE_PATH).'exercise/admin.php?';
$exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/exercise.php?';
$warningText = addslashes(api_htmlentities(get_lang('Please confirm your choice')));
$urlExercise = api_get_path(WEB_CODE_PATH).'exercise/admin.php?';
$exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/exercise.php?';
$warningText = addslashes(api_htmlentities(get_lang('Please confirm your choice')));

/** @var CQuizQuestion $question */
for ($i = 0; $i < $length; $i++) {
$index = $i;
if (!empty($page)) {
$index = ($page - 1) * $length + $i;
}
if (0 === $i) {
$start = $index;
}
if (!isset($pagination[$index])) {
continue;
}
/** @var CQuizQuestion $question */
for ($i = 0; $i < $length; $i++) {
$index = $i;
if (!empty($page)) {
$index = ($page - 1) * $length + $i;
}
if (0 === $i) {
$start = $index;
}
if (!isset($pagination[$index])) {
continue;
}

if ($i < $length) {
$end = $index;
}
$question = &$pagination[$index];
$courseId = $question->getCId();
$courseInfo = api_get_course_info_by_id($courseId);
$courseCode = $courseInfo['code'];
$question->courseCode = $courseCode;
// Creating empty exercise
$exercise = new Exercise($courseId);
$questionObject = Question::read($question->getIid(), $courseInfo);

ob_start();
ExerciseLib::showQuestion(
$exercise,
$question->getIid(),
false,
null,
null,
false,
true,
false,
true,
true
);
$question->questionData = ob_get_contents();

if ('export_pdf' === $action) {
$pdfContent .= '<span style="color:#000; font-weight:bold; font-size:x-large;">#'.$question->getIid().'. '.$question->getQuestion().'</span><br />';
$pdfContent .= '<span style="color:#444;">('.$questionTypesList[$question->getType()].') ['.get_lang('Source').': '.$courseCode.']</span><br />';
$pdfContent .= $question->getDescription().'<br />';
$pdfContent .= $question->questionData;
continue;
}
if ($i < $length) {
$end = $index;
}
$question = &$pagination[$index];
$courseId = $question->getFirstResourceLink()->getCourse()->getId();
$courseInfo = api_get_course_info_by_id($courseId);
$courseCode = $courseInfo['code'];
$question->courseCode = $courseCode;
// Creating empty exercise
$exercise = new Exercise($courseId);
$questionObject = Question::read($question->getIid(), $courseInfo);

ob_start();
ExerciseLib::showQuestion(
$exercise,
$question->getIid(),
false,
null,
null,
false,
true,
false,
true,
true
);
$question->questionData = ob_get_contents();

if ('export_pdf' === $action) {
$pdfContent .= '<span style="color:#000; font-weight:bold; font-size:x-large;">#'.$question->getIid().'. '.$question->getQuestion().'</span><br />';
$pdfContent .= '<span style="color:#444;">('.$questionTypesList[$question->getType()].') ['.get_lang('Source').': '.$courseCode.']</span><br />';
$pdfContent .= $question->getDescription().'<br />';
$pdfContent .= $question->questionData;
continue;
}

$deleteUrl = $url.'&'.http_build_query([
'courseId' => $question->getCId(),
'questionId' => $question->getId(),
'action' => 'delete',
]);
$exerciseData = '';
$exerciseId = 0;
if (!empty($questionObject->exerciseList)) {
// Question exists in a valid exercise
$exerciseData .= get_lang('Tests').'<br />';
foreach ($questionObject->exerciseList as $exerciseId) {
$exercise = new Exercise($question->getCId());
$exercise->course_id = $question->getCId();
$exercise->read($exerciseId);
$exerciseData .= $exercise->title.'&nbsp;';
$exerciseData .= Display::url(
Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit')),
$urlExercise.http_build_query(
[
'cidReq' => $courseCode,
'id_session' => $exercise->sessionId,
'exerciseId' => $exerciseId,
'type' => $question->getType(),
'editQuestion' => $question->getId(),
]
),
['target' => '_blank']
).'<br />';
}
$question->questionData .= '<br />'.$exerciseData;
} else {
// Question exists but it's orphan or it belongs to a deleted exercise

// This means the question is added in a deleted exercise
if ($questionObject->getCountExercise() > 0) {
$exerciseList = $questionObject->getExerciseListWhereQuestionExists();
if (!empty($exerciseList)) {
$question->questionData .= '<br />'.get_lang('Tests').'<br />';
/** @var CQuiz $exercise */
foreach ($exerciseList as $exercise) {
$question->questionData .= $exercise->getTitle();
if (-1 == $exercise->getActive()) {
$question->questionData .= '- ('.get_lang('The test has been deleted').' #'.$exercise->getIid().') ';
}
$question->questionData .= '<br />';
}
}
} else {
// This question is orphan :(
$question->questionData .= '&nbsp;'.get_lang('Orphan question');
}
$question->questionData .= Display::url(
$deleteUrl = $url.'&'.http_build_query([
'courseId' => $courseId,
'questionId' => $question->getIid(),
'action' => 'delete',
]);
$exerciseData = '';
$exerciseId = 0;
if (!empty($questionObject->exerciseList)) {
// Question exists in a valid exercise
$exerciseData .= '<p><strong>'.get_lang('Tests').'</strong></p>';
foreach ($questionObject->exerciseList as $exerciseId) {
$exercise = new Exercise($courseId);
$exercise->course_id = $courseId;
$exercise->read($exerciseId);
$exerciseData .= $exercise->title.'&nbsp;';
$exerciseData .= Display::url(
Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit')),
$urlExercise.http_build_query(
[
'cidReq' => $courseCode,
'id_session' => 0, //$exercise->sessionId,
'exerciseId' => $exerciseId,
'type' => $question->getType(),
'editQuestion' => $question->getId(),
'cidReq' => $courseCode,
'id_session' => $exercise->sessionId,
'exerciseId' => $exerciseId,
'type' => $question->getType(),
'editQuestion' => $question->getIid(),
]
),
['target' => '_blank']
);
).'<br />';
}
$question->questionData .= '<div class="pull-right">'.Display::url(
get_lang('Delete'),
$deleteUrl,
[
'class' => 'btn btn--danger',
'onclick' => 'javascript: if(!confirm(\''.$warningText.'\')) return false',
]
).'</div>';
ob_end_clean();
$question->questionData .= '<br />'.$exerciseData;
} else {
// Question exists but it's orphan or it belongs to a deleted exercise

// This means the question is added in a deleted exercise
if ($questionObject->getCountExercise() > 0) {
$exerciseList = $questionObject->getExerciseListWhereQuestionExists();
if (!empty($exerciseList)) {
$question->questionData .= '<p><strong>'.get_lang('Tests').'</strong></p>';
/** @var CQuiz $exercise */
foreach ($exerciseList as $exercise) {
$question->questionData .= $exercise->getTitle();
if (-1 == $exercise->getActive()) {
$question->questionData .= '- ('.get_lang('The test has been deleted').' #'.$exercise->getIid().') ';
}
$question->questionData .= '<br />';
}
}
} else {
// This question is orphan :(
$question->questionData .= '&nbsp;'.get_lang('Orphan question');
}
$question->questionData .= Display::url(
Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit')),
$urlExercise.http_build_query(
[
'cidReq' => $courseCode,
'id_session' => 0, //$exercise->sessionId,
'exerciseId' => $exerciseId,
'type' => $question->getType(),
'editQuestion' => $question->getIid(),
]
),
['target' => '_blank']
);
}
$question->questionData .= '<div class="float-right">'.Display::url(
get_lang('Delete'),
$deleteUrl,
[
'class' => 'btn btn--danger',
'onclick' => 'javascript: if(!confirm(\''.$warningText.'\')) return false',
]
).'</div>';
ob_end_clean();
}
}

Expand Down
29 changes: 29 additions & 0 deletions public/main/inc/lib/formvalidator/FormValidator.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/* For licensing terms, see /license.txt */

use Chamilo\CoreBundle\Component\HTMLPurifier\Filter\RemoveOnAttributes;

/**
* Class FormValidator
* create/manipulate/validate user input.
Expand Down Expand Up @@ -1988,3 +1990,30 @@ function mobile_phone_number_filter($mobilePhoneNumber)

return ltrim($mobilePhoneNumber, '0');
}

/**
* Cleans JS from a URL.
*
* @param string $html URL to clean
* @param int $mode (optional)
*
* @return string The cleaned URL
*/
function plain_url_filter($html, $mode = NO_HTML)
{
$allowed_tags = HTML_QuickForm_Rule_HTML::get_allowed_tags($mode);
$html = kses_no_null($html);
$html = kses_js_entities($html);
$allowed_html_fixed = kses_array_lc($allowed_tags);

return kses_split($html, $allowed_html_fixed, ['http', 'https']);
}

/**
* Prevent execution of event handlers in HTML elements.
*/
function attr_on_filter(string $html): string
{
return RemoveOnAttributes::filter($html);
}

Loading

0 comments on commit e1a1089

Please sign in to comment.