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

pull/5209/head
Yannick Warnier 2 years ago
commit aaa5ed7558
  1. 10
      assets/css/scss/_exercise.scss
  2. 168
      assets/vue/components/Breadcrumb.vue
  3. 23
      assets/vue/components/layout/DashboardLayout.vue
  4. 29
      public/main/exercise/question_list_admin.inc.php
  5. 3
      public/main/inc/ajax/exercise.ajax.php
  6. 2
      public/main/inc/lib/display.lib.php
  7. 2
      public/main/inc/lib/template.lib.php
  8. 53
      src/CoreBundle/Component/Editor/CkEditor/Toolbar/Basic.php
  9. 20
      src/CoreBundle/Controller/SecurityController.php
  10. 1
      src/CoreBundle/Entity/Listener/SessionListener.php
  11. 1
      src/CoreBundle/EventListener/CourseAccessListener.php
  12. 1
      src/CoreBundle/EventListener/OnlineListener.php
  13. 1
      src/CoreBundle/Migrations/Schema/V200/Version20.php
  14. 2
      src/CoreBundle/Migrations/Schema/V200/Version20170625145000.php
  15. 1
      src/CoreBundle/Repository/SequenceRepository.php
  16. 6
      src/CoreBundle/Repository/TrackEDownloadsRepository.php
  17. 30
      var/vue_templates/components/layout/DashboardLayout.vue

@ -62,6 +62,16 @@
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(102, 175, 233, 0.6); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(102, 175, 233, 0.6);
} }
.ui-state-highlight {
height: 3.5em;
line-height: 2.2em;
background-color: #fafafa;
border: 1px dashed #ccc;
margin-top: 10px;
margin-bottom: 10px;
padding: 5px;
}
.question_menu { .question_menu {
@apply p-4 flex flex-row gap-1; @apply p-4 flex flex-row gap-1;
} }

@ -28,7 +28,7 @@
</router-link> </router-link>
<a <a
v-else v-else
:href="item.url" :href="item.url !== '#' ? item.url : undefined"
v-bind="props.action" v-bind="props.action"
> >
<span>{{ item.label }}</span> <span>{{ item.label }}</span>
@ -46,24 +46,14 @@
</template> </template>
<script setup> <script setup>
import { computed } from "vue" import { ref, watch } from "vue"
import { useRoute } from "vue-router" import { useRoute } from "vue-router"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import Breadcrumb from "primevue/breadcrumb" import Breadcrumb from "primevue/breadcrumb"
import { useCidReqStore } from "../store/cidReq" import { useCidReqStore } from "../store/cidReq"
import { storeToRefs } from "pinia" import { storeToRefs } from "pinia"
// eslint-disable-next-line no-undef const legacyItems = ref(window.breadcrumb)
const componentProps = defineProps({
layoutClass: {
type: String,
default: null,
},
legacy: {
type: Array,
default: () => [],
},
})
const cidReqStore = useCidReqStore() const cidReqStore = useCidReqStore()
const route = useRoute() const route = useRoute()
@ -71,84 +61,96 @@ const { t } = useI18n()
const { course, session } = storeToRefs(cidReqStore) const { course, session } = storeToRefs(cidReqStore)
const itemList = computed(() => { const specialRouteNames = [
const list = [ "MyCourses",
"MyCourses", "MySessions",
"MySessions", "MySessionsUpcoming",
"MySessionsUpcoming", "MySessionsPast",
"MySessionsPast", "Home",
"Home", "MessageList",
"MessageList", "MessageNew",
"MessageNew", "MessageShow",
"MessageShow", "MessageCreate",
"MessageCreate", ]
]
const itemList = ref([])
const items = []
watch(
if (route.name && route.name.includes("Page")) { route,
items.push({ () => {
label: t("Pages"), if ("/" === route.fullPath) {
to: "/resources/pages", return
}) }
}
itemList.value = []
if (route.name && route.name.includes("Message")) {
items.push({ if (route.name && route.name.includes("Page")) {
label: t("Messages"), itemList.value.push({
//disabled: route.path === path || lastItem.path === route.path, label: t("Pages"),
to: "/resources/messages", to: "/resources/pages",
})
}
if (list.includes(route.name)) {
return items
}
if (course.value) {
if (session.value) {
items.push({
label: t("My sessions"),
route: { name: "MySessions" },
})
} else {
items.push({
label: t("My courses"),
route: { name: "MyCourses" },
}) })
} }
}
if (componentProps.legacy.length > 0) { if (route.name && route.name.includes("Message")) {
const mainUrl = window.location.href itemList.value.push({
const mainPath = mainUrl.indexOf("main/") label: t("Messages"),
//disabled: route.path === path || lastItem.path === route.path,
to: "/resources/messages",
})
}
componentProps.legacy.forEach((item) => { if (specialRouteNames.includes(route.name)) {
let url = item.url.toString() return
let newUrl = url }
if (url.indexOf("main/") > 0) { if (course.value) {
newUrl = "/" + url.substring(mainPath, url.length) if (session.value) {
itemList.value.push({
label: t("My sessions"),
route: { name: "MySessions" },
})
} else {
itemList.value.push({
label: t("My courses"),
route: { name: "MyCourses" },
})
} }
}
if (newUrl === "/") { if (legacyItems.value.length > 0) {
newUrl = "#" const mainUrl = window.location.href
} const mainPath = mainUrl.indexOf("main/")
items.push({ legacyItems.value.forEach((item) => {
label: item["name"], let url = item.url.toString()
url: newUrl, let newUrl = url
})
}) if (url.indexOf("main/") > 0) {
} else { newUrl = "/" + url.substring(mainPath, url.length)
if (course.value) { }
items.push({
label: course.value.title, if (newUrl === "/") {
route: { name: "CourseHome", params: { id: course.value.id }, query: route.query }, newUrl = "#"
}
itemList.value.push({
label: item["name"],
url: newUrl,
})
}) })
}
}
return items legacyItems.value = []
}) } else {
if (course.value) {
itemList.value.push({
label: course.value.title,
route: { name: "CourseHome", params: { id: course.value.id }, query: route.query },
})
}
}
},
{
immediate: true,
},
)
</script> </script>

@ -5,19 +5,16 @@
class="app-main" class="app-main"
:class="{ 'app-main--no-sidebar': !securityStore.isAuthenticated }" :class="{ 'app-main--no-sidebar': !securityStore.isAuthenticated }"
> >
<Breadcrumb <Breadcrumb v-if="showBreadcrumb" />
v-if="showBreadcrumb"
:legacy="breadcrumb"
/>
<slot /> <slot />
<router-view /> <router-view />
</div> </div>
</template> </template>
<script setup> <script setup>
import Breadcrumb from '../../components/Breadcrumb.vue'; import Breadcrumb from "../../components/Breadcrumb.vue"
import Topbar from '../../components/layout/Topbar.vue'; import Topbar from "../../components/layout/Topbar.vue"
import Sidebar from '../../components/layout/Sidebar.vue'; import Sidebar from "../../components/layout/Sidebar.vue"
import { useSecurityStore } from "../../store/securityStore" import { useSecurityStore } from "../../store/securityStore"
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -26,17 +23,7 @@ defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
}); })
const securityStore = useSecurityStore() const securityStore = useSecurityStore()
let breadcrumb = [];
try {
if (window.breadcrumb) {
breadcrumb = window.breadcrumb;
}
} catch (e) {
console.log(e.message);
}
</script> </script>

@ -57,6 +57,7 @@ $ajax_url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&
} }
}); });
var isDragging = false;
$("#question_list").accordion({ $("#question_list").accordion({
icons: null, icons: null,
heightStyle: "content", heightStyle: "content",
@ -64,6 +65,10 @@ $ajax_url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&
collapsible: true, collapsible: true,
header: ".header_operations", header: ".header_operations",
beforeActivate: function (e, ui) { beforeActivate: function (e, ui) {
if (isDragging) {
e.preventDefault();
isDragging = false;
}
var data = ui.newHeader.data(); var data = ui.newHeader.data();
if (typeof data === 'undefined') { if (typeof data === 'undefined') {
return; return;
@ -95,19 +100,29 @@ $ajax_url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&
}) })
.sortable({ .sortable({
cursor: "move", // works? cursor: "move", // works?
axis: "y",
placeholder: "ui-state-highlight", //defines the yellow highlight
handle: ".moved", //only the class "moved"
start: function(event, ui) {
isDragging = true;
},
stop: function(event, ui) {
stop = true;
setTimeout(function() {
isDragging = false;
}, 50);
},
update: function (event, ui) { update: function (event, ui) {
var order = $(this).sortable("serialize") + "&a=update_question_order&exercise_id=<?php echo $exerciseId; ?>"; var order = $(this).sortable("serialize") + "&a=update_question_order&exercise_id=<?php echo $exerciseId; ?>";
$.post("<?php echo $ajax_url; ?>", order, function (result) { $.post("<?php echo $ajax_url; ?>", order, function (result) {
$("#message").html(result); $("#message").html(result);
}); });
},
axis: "y",
placeholder: "ui-state-highlight", //defines the yellow highlight
handle: ".moved", //only the class "moved"
stop: function () {
stop = true;
} }
}); });
$(".moved").on('click', function(event) {
event.stopImmediatePropagation();
});
}); });
</script> </script>
<?php <?php
@ -246,7 +261,7 @@ if (!$inATest) {
$title = strip_tags($title); $title = strip_tags($title);
$move = '&nbsp;'; $move = '&nbsp;';
if ($allowQuestionOrdering) { if ($allowQuestionOrdering) {
$move = Display::getMdiIcon('cursor-move'); $move = Display::getMdiIcon('cursor-move', 'moved');
} }
// Question name // Question name

@ -347,9 +347,8 @@ switch ($action) {
$TBL_QUESTIONS, $TBL_QUESTIONS,
['question_order' => $counter], ['question_order' => $counter],
[ [
'question_id = ? AND c_id = ? AND quiz_id = ? ' => [ 'question_id = ? AND quiz_id = ? ' => [
(int) $new_order_id, (int) $new_order_id,
$course_id,
$exercise_id, $exercise_id,
], ],
] ]

@ -143,7 +143,7 @@ class Display
} }
$params['legacy_javascript'] = $htmlHeadXtra; $params['legacy_javascript'] = $htmlHeadXtra;
$params['legacy_breadcrumb'] = json_encode($interbreadcrumb); $params['legacy_breadcrumb'] = json_encode(array_values($interbreadcrumb));
Template::setVueParams($params); Template::setVueParams($params);
$content = Container::getTwig()->render($tpl, $params); $content = Container::getTwig()->render($tpl, $params);

@ -1062,7 +1062,7 @@ class Template
} }
} }
$this->params['legacy_breadcrumb'] = json_encode($interbreadcrumb); $this->params['legacy_breadcrumb'] = json_encode(array_values($interbreadcrumb));
global $htmlHeadXtra; global $htmlHeadXtra;
$this->params['legacy_javascript'] = $htmlHeadXtra; $this->params['legacy_javascript'] = $htmlHeadXtra;
} }

@ -191,13 +191,10 @@ class Basic extends Toolbar
$config['file_picker_callback'] = '[browser]'; $config['file_picker_callback'] = '[browser]';
$iso = api_get_language_isocode(); $iso = api_get_language_isocode();
$url = api_get_path(WEB_PATH); $languageConfig = $this->getLanguageConfig($iso);
// Language list: https://www.tiny.cloud/get-tiny/language-packages/ // Merge the language configuration
if ('en_US' !== $iso) { $config = array_merge($config, $languageConfig);
$config['language'] = $iso;
$config['language_url'] = "$url/libs/editor/langs/$iso.js";
}
/*if (isset($this->config)) { /*if (isset($this->config)) {
$this->config = array_merge($config, $this->config); $this->config = array_merge($config, $this->config);
@ -301,4 +298,48 @@ class Basic extends Toolbar
['Toolbarswitch', 'Source'], ['Toolbarswitch', 'Source'],
]; ];
} }
/**
* Determines the appropriate language configuration for the editor.
* Tries to load a specific language file based on the ISO code. If not found, it attempts to load a general language file.
* Falls back to English if neither specific nor general language files are available.
*/
private function getLanguageConfig(string $iso): array
{
$url = api_get_path(WEB_PATH);
$sysUrl = api_get_path(SYS_PATH);
$defaultLang = 'en';
$defaultLangFile = "libs/editor/langs/{$defaultLang}.js";
$specificLangFile = "libs/editor/langs/{$iso}.js";
$generalLangFile = null;
// Default configuration set to English
$config = [
'language' => $defaultLang,
'language_url' => $defaultLangFile,
];
if ('en_US' !== $iso) {
// Check for a specific variant of the language (e.g., de_german2)
if (str_contains($iso, '_')) {
// Extract the general language code (e.g., de)
list($generalLangCode) = explode('_', $iso, 2);
$generalLangFile = "libs/editor/langs/{$generalLangCode}.js";
}
// Attempt to load the specific language file
if (file_exists($sysUrl.$specificLangFile)) {
$config['language'] = $iso;
$config['language_url'] = $url.$specificLangFile;
}
// Fallback to the general language file if specific is not available
elseif (null !== $generalLangFile && file_exists($sysUrl.$generalLangFile)) {
$config['language'] = $generalLangCode;
$config['language_url'] = $url.$generalLangFile;
}
}
return $config;
}
} }

@ -24,24 +24,14 @@ use Symfony\Component\Serializer\SerializerInterface;
class SecurityController extends AbstractController class SecurityController extends AbstractController
{ {
private $entityManager;
private $settingsManager;
private $tokenStorage;
private $authorizationChecker;
public function __construct( public function __construct(
private SerializerInterface $serializer, private SerializerInterface $serializer,
private TrackELoginRecordRepository $trackELoginRecordRepository, private TrackELoginRecordRepository $trackELoginRecordRepository,
EntityManagerInterface $entityManager, private EntityManagerInterface $entityManager,
SettingsManager $settingsManager, private SettingsManager $settingsManager,
TokenStorageInterface $tokenStorage, private TokenStorageInterface $tokenStorage,
AuthorizationCheckerInterface $authorizationChecker private AuthorizationCheckerInterface $authorizationChecker
) { ) {}
$this->entityManager = $entityManager;
$this->settingsManager = $settingsManager;
$this->tokenStorage = $tokenStorage;
$this->authorizationChecker = $authorizationChecker;
}
#[Route('/login_json', name: 'login_json', methods: ['POST'])] #[Route('/login_json', name: 'login_json', methods: ['POST'])]
public function loginJson(Request $request, EntityManager $entityManager, SettingsManager $settingsManager, TokenStorageInterface $tokenStorage): Response public function loginJson(Request $request, EntityManager $entityManager, SettingsManager $settingsManager, TokenStorageInterface $tokenStorage): Response

@ -15,7 +15,6 @@ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
/** /**
* Class SessionListener
* Session entity listener, when a session is created/updated. * Session entity listener, when a session is created/updated.
*/ */
class SessionListener class SessionListener

@ -13,7 +13,6 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
/** /**
* Class CourseAccessListener
* In and outs of a course * In and outs of a course
* This listeners is always called when user enters the course home. * This listeners is always called when user enters the course home.
*/ */

@ -14,7 +14,6 @@ use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/** /**
* Class OnlineListener
* Adds objects into the session like the old global.inc. * Adds objects into the session like the old global.inc.
*/ */
class OnlineListener class OnlineListener

@ -11,7 +11,6 @@ use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
/** /**
* Class Version20
* Migrate file to updated to Chamilo 2.0. * Migrate file to updated to Chamilo 2.0.
*/ */
class Version20 extends AbstractMigrationChamilo class Version20 extends AbstractMigrationChamilo

@ -49,7 +49,7 @@ class Version20170625145000 extends AbstractMigrationChamilo
} }
if (!$table->hasColumn('invitation_type')) { if (!$table->hasColumn('invitation_type')) {
$this->addSql("ALTER TABLE c_calendar_event ADD invitation_type VARCHAR(255) DEFAULT NULL"); $this->addSql('ALTER TABLE c_calendar_event ADD invitation_type VARCHAR(255) DEFAULT NULL');
} }
if (!$table->hasColumn('collective')) { if (!$table->hasColumn('collective')) {

@ -17,7 +17,6 @@ use Doctrine\Persistence\ManagerRegistry;
use SessionManager; use SessionManager;
/** /**
* Class SequenceRepository
* The functions inside this class should return an instance of QueryBuilder. * The functions inside this class should return an instance of QueryBuilder.
*/ */
class SequenceRepository extends ServiceEntityRepository class SequenceRepository extends ServiceEntityRepository

@ -20,11 +20,7 @@ class TrackEDownloadsRepository extends ServiceEntityRepository
} }
/** /**
* Save record of a resource being downloaded in track_e_downloads * Save record of a resource being downloaded in track_e_downloads.
* @param int $userId
* @param int $resourceLinkId
* @param string $documentUrl
* @return int
*/ */
public function saveDownload(int $userId, int $resourceLinkId, string $documentUrl): int public function saveDownload(int $userId, int $resourceLinkId, string $documentUrl): int
{ {

@ -10,37 +10,23 @@ defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
}); })
const securityStore = useSecurityStore() const securityStore = useSecurityStore()
let breadcrumb = [];
try {
if (window.breadcrumb) {
breadcrumb = window.breadcrumb;
}
} catch (e) {
console.log(e.message);
}
</script> </script>
<template> <template>
<Topbar /> <Topbar />
<Sidebar <Sidebar v-if="securityStore.isAuthenticated" />
v-if="securityStore.isAuthenticated" <SidebarNotLoggedIn v-else />
/>
<SidebarNotLoggedIn
v-else
/>
<div <div
class="app-main" class="app-main"
:class="{ 'app-main--no-sidebar': !securityStore.isAuthenticated, 'app-main--no-loggedin': !securityStore.isAuthenticated }" :class="{
'app-main--no-sidebar': !securityStore.isAuthenticated,
'app-main--no-loggedin': !securityStore.isAuthenticated,
}"
> >
<Breadcrumb <Breadcrumb v-if="showBreadcrumb" />
v-if="showBreadcrumb"
:legacy="breadcrumb"
/>
<slot /> <slot />
<router-view /> <router-view />
</div> </div>

Loading…
Cancel
Save