commit
9948f1d442
@ -1,150 +1,178 @@ |
||||
import { useI18n } from "vue-i18n" |
||||
import { ref, onMounted, watch } from "vue" |
||||
import { useSecurityStore } from "../store/securityStore" |
||||
import { usePlatformConfig } from "../store/platformConfig" |
||||
import { useEnrolledStore } from "../store/enrolledStore" |
||||
|
||||
export function useSidebarMenu() { |
||||
const { t } = useI18n() |
||||
const securityStore = useSecurityStore() |
||||
const platformConfigStore = usePlatformConfig() |
||||
|
||||
const enrolledStore = useEnrolledStore() |
||||
const showTabsSetting = platformConfigStore.getSetting("platform.show_tabs") |
||||
|
||||
const items = [] |
||||
const items = ref([]) |
||||
const coursesItems = ref([]) |
||||
|
||||
if (showTabsSetting.indexOf("campus_homepage") > -1) { |
||||
items.push({ |
||||
icon: "mdi mdi-home", |
||||
label: t("Home"), |
||||
to: { name: "Home" }, |
||||
}) |
||||
} |
||||
const updateItems = () => { |
||||
items.value = [] |
||||
|
||||
if (securityStore.isAuthenticated) { |
||||
if (showTabsSetting.indexOf("campus_homepage") > -1) { |
||||
items.push({ |
||||
icon: "mdi mdi-book-open-page-variant", |
||||
items.value.push({ |
||||
icon: "mdi mdi-home", |
||||
label: t("Home"), |
||||
to: { name: "Home" }, |
||||
}) |
||||
} |
||||
|
||||
if (securityStore.isAuthenticated) { |
||||
if (coursesItems.value.length > 0) { |
||||
const coursesMenu = { |
||||
icon: "mdi mdi-book-open-page-variant", |
||||
label: coursesItems.value.length > 1 ? t("Courses") : coursesItems.value[0].label, |
||||
items: coursesItems.value.length > 1 ? coursesItems.value : undefined, |
||||
to: coursesItems.value.length === 1 ? coursesItems.value[0].to : undefined, |
||||
} |
||||
items.value.push(coursesMenu) |
||||
} |
||||
|
||||
if (showTabsSetting.indexOf("my_agenda") > -1) { |
||||
items.value.push({ |
||||
icon: "mdi mdi-calendar-text", |
||||
label: t("Events"), |
||||
to: { name: "CCalendarEventList" }, |
||||
}) |
||||
} |
||||
|
||||
if (showTabsSetting.indexOf("reporting") > -1) { |
||||
let subItems = [] |
||||
|
||||
if (securityStore.isTeacher || securityStore.isHRM || securityStore.isSessionAdmin) { |
||||
subItems.push({ |
||||
label: securityStore.isHRM ? t("Course sessions") : t("Reporting"), |
||||
url: "/main/my_space/" + (securityStore.isHRM ? "session.php" : "index.php"), |
||||
}) |
||||
} else if (securityStore.isStudentBoss) { |
||||
subItems.push({ |
||||
label: t("Learners"), |
||||
url: "/main/my_space/student.php", |
||||
}) |
||||
} else { |
||||
subItems.push({ |
||||
label: t("Progress"), |
||||
url: "/main/auth/my_progress.php", |
||||
}) |
||||
} |
||||
|
||||
items.value.push({ |
||||
icon: "mdi mdi-chart-box", |
||||
label: t("Reporting"), |
||||
items: subItems, |
||||
}) |
||||
} |
||||
|
||||
if (showTabsSetting.indexOf("social") > -1) { |
||||
items.value.push({ |
||||
icon: "mdi mdi-sitemap-outline", |
||||
label: t("Social network"), |
||||
to: { name: "SocialWall" }, |
||||
}) |
||||
} |
||||
|
||||
if (platformConfigStore.plugins?.bbb?.show_global_conference_link) { |
||||
items.value.push({ |
||||
icon: "mdi mdi-video", |
||||
label: t("Videoconference"), |
||||
url: platformConfigStore.plugins.bbb.listingURL, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
if (securityStore.isStudentBoss || securityStore.isStudent) { |
||||
items.value.push({ |
||||
icon: "mdi mdi-text-box-search", |
||||
items: [ |
||||
{ |
||||
label: t("My courses"), |
||||
to: { name: "MyCourses" }, |
||||
label: t("Diagnosis Management"), |
||||
url: "/main/search/load_search.php", |
||||
visible: securityStore.isStudentBoss, |
||||
}, |
||||
{ |
||||
label: t("My sessions"), |
||||
to: { name: "MySessions" }, |
||||
label: t("Diagnostic Form"), |
||||
url: "/main/search/search.php", |
||||
}, |
||||
], |
||||
label: t("Courses"), |
||||
}) |
||||
} |
||||
|
||||
if (showTabsSetting.indexOf("my_agenda") > -1) { |
||||
items.push({ |
||||
icon: "mdi mdi-calendar-text", |
||||
label: t("Events"), |
||||
to: { name: "CCalendarEventList" }, |
||||
label: t("Diagnosis"), |
||||
}) |
||||
} |
||||
|
||||
if (showTabsSetting.indexOf("reporting") > -1) { |
||||
let subItems = [] |
||||
if (securityStore.isAdmin || securityStore.isSessionAdmin) { |
||||
const adminItems = [ |
||||
{ |
||||
label: t("Administration"), |
||||
to: { name: "AdminIndex" }, |
||||
}, |
||||
] |
||||
|
||||
if (securityStore.isTeacher || securityStore.isHRM || securityStore.isSessionAdmin) { |
||||
subItems.push({ |
||||
label: securityStore.isHRM ? t("Course sessions") : t("Reporting"), |
||||
url: "/main/my_space/" + (securityStore.isHRM ? "session.php" : "index.php"), |
||||
}) |
||||
} else if (securityStore.isStudentBoss) { |
||||
subItems.push({ |
||||
label: t("Learners"), |
||||
url: "/main/my_space/student.php", |
||||
if (securityStore.isSessionAdmin && 'true' === platformConfigStore.getSetting('session.limit_session_admin_list_users')) { |
||||
adminItems.push({ |
||||
label: t("Add user"), |
||||
url: "/main/admin/user_add.php", |
||||
}) |
||||
} else { |
||||
subItems.push({ |
||||
label: t("Progress"), |
||||
url: "/main/auth/my_progress.php", |
||||
adminItems.push({ |
||||
label: t("Users"), |
||||
url: "/main/admin/user_list.php", |
||||
}) |
||||
} |
||||
|
||||
items.push({ |
||||
icon: "mdi mdi-chart-box", |
||||
label: t("Reporting"), |
||||
items: subItems, |
||||
}) |
||||
} |
||||
if (securityStore.isAdmin) { |
||||
adminItems.push({ |
||||
label: t("Courses"), |
||||
url: "/main/admin/course_list.php", |
||||
}) |
||||
} |
||||
|
||||
if (showTabsSetting.indexOf("social") > -1) { |
||||
items.push({ |
||||
icon: "mdi mdi-sitemap-outline", |
||||
label: t("Social network"), |
||||
to: { name: "SocialWall" }, |
||||
adminItems.push({ |
||||
label: t("Sessions"), |
||||
url: "/main/session/session_list.php", |
||||
}) |
||||
} |
||||
|
||||
if (platformConfigStore.plugins?.bbb?.show_global_conference_link) { |
||||
items.push({ |
||||
icon: "mdi mdi-video", |
||||
label: t("Videoconference"), |
||||
url: platformConfigStore.plugins.bbb.listingURL, |
||||
items.value.push({ |
||||
icon: "mdi mdi-cog", |
||||
items: adminItems, |
||||
label: t("Administration"), |
||||
}) |
||||
} |
||||
} |
||||
|
||||
if (securityStore.isStudentBoss || securityStore.isStudent) { |
||||
items.push({ |
||||
icon: "mdi mdi-text-box-search", |
||||
items: [ |
||||
{ |
||||
label: t("Diagnosis Management"), |
||||
url: "/main/search/load_search.php", |
||||
visible: securityStore.isStudentBoss, |
||||
}, |
||||
{ |
||||
label: t("Diagnostic Form"), |
||||
url: "/main/search/search.php", |
||||
}, |
||||
], |
||||
label: t("Diagnosis"), |
||||
}) |
||||
} |
||||
|
||||
if (securityStore.isAdmin || securityStore.isSessionAdmin) { |
||||
const adminItems = [ |
||||
{ |
||||
label: t("Administration"), |
||||
to: { name: "AdminIndex" }, |
||||
}, |
||||
] |
||||
const updateCoursesItems = () => { |
||||
coursesItems.value = []; |
||||
|
||||
if (securityStore.isSessionAdmin && 'true' === platformConfigStore.getSetting('session.limit_session_admin_list_users')) { |
||||
adminItems.push({ |
||||
label: t("Add user"), |
||||
url: "/main/admin/user_add.php", |
||||
}) |
||||
} else { |
||||
adminItems.push({ |
||||
label: t("Users"), |
||||
url: "/main/admin/user_list.php", |
||||
if (enrolledStore.isEnrolledInCourses) { |
||||
coursesItems.value.push({ |
||||
label: t("My courses"), |
||||
to: { name: "MyCourses" }, |
||||
}) |
||||
} |
||||
|
||||
if (securityStore.isAdmin) { |
||||
adminItems.push({ |
||||
label: t("Courses"), |
||||
url: "/main/admin/course_list.php", |
||||
if (enrolledStore.isEnrolledInSessions) { |
||||
coursesItems.value.push({ |
||||
label: t("My sessions"), |
||||
to: { name: "MySessions" }, |
||||
}) |
||||
} |
||||
|
||||
adminItems.push({ |
||||
label: t("Sessions"), |
||||
url: "/main/session/session_list.php", |
||||
}) |
||||
|
||||
items.push({ |
||||
icon: "mdi mdi-cog", |
||||
items: adminItems, |
||||
label: t("Administration"), |
||||
}) |
||||
updateItems(); |
||||
} |
||||
|
||||
onMounted(async () => { |
||||
await enrolledStore.initialize(); |
||||
updateCoursesItems(); |
||||
}) |
||||
|
||||
watch(() => enrolledStore.isEnrolledInCourses, updateCoursesItems) |
||||
watch(() => enrolledStore.isEnrolledInSessions, updateCoursesItems) |
||||
|
||||
return items |
||||
} |
||||
|
@ -1,105 +1,28 @@ |
||||
import {mapFields} from 'vuex-map-fields'; |
||||
import Snackbar from "../components/Snackbar.vue"; |
||||
//import { useToast } from "vue-toastification";
|
||||
// inside of a Vue file
|
||||
|
||||
//import { useQuasar } from 'quasar'
|
||||
import { mapFields } from 'vuex-map-fields'; |
||||
|
||||
export default { |
||||
setup() { |
||||
}, |
||||
computed: { |
||||
...mapFields('notifications', ['color', 'show', 'subText', 'text', 'timeout']) |
||||
methods: { |
||||
showError(error) { |
||||
this.showMessage(error, 'error'); // Use 'error' for PrimeVue
|
||||
}, |
||||
methods: { |
||||
cleanState() { |
||||
/*setTimeout(() => { |
||||
this.show = false; |
||||
}, this.timeout |
||||
);*/ |
||||
}, |
||||
showError(error) { |
||||
this.showMessage(error, 'danger'); |
||||
}, |
||||
showMessage(message, type = 'success') { |
||||
/*const content = { |
||||
// Your component or JSX template
|
||||
component: Snackbar, |
||||
// Props are just regular props, but these won't be reactive
|
||||
props: { |
||||
message: message |
||||
}, |
||||
// Listeners will listen to and execute on event emission
|
||||
listeners: { |
||||
//click: () => console.log("Clicked!"),
|
||||
//myEvent: myEventHandler
|
||||
} |
||||
};*/ |
||||
|
||||
let color = 'primary'; |
||||
let icon = 'info'; |
||||
|
||||
switch (type) { |
||||
case 'info': |
||||
break; |
||||
case 'success': |
||||
color = 'green'; |
||||
break; |
||||
case 'error': |
||||
case 'danger': |
||||
color = 'red'; |
||||
icon: 'error'; |
||||
break; |
||||
case 'warning': |
||||
color = 'yellow'; |
||||
break; |
||||
|
||||
} |
||||
if ('danger' === type) { |
||||
type = 'error'; |
||||
} |
||||
|
||||
this.$q.notify({ |
||||
position: 'top', |
||||
timeout: 10000, |
||||
message: message, |
||||
color: color, |
||||
html: true, |
||||
multiLine: true, |
||||
}) |
||||
|
||||
/*const toast = useToast(); |
||||
console.log('toast'); |
||||
console.log(message); |
||||
console.log(content); |
||||
|
||||
toast(content, { |
||||
type: type, |
||||
position: 'top-center', |
||||
timeout: 10000, // 10 seconds
|
||||
closeOnClick: false, |
||||
pauseOnFocusLoss: true, |
||||
pauseOnHover: true, |
||||
draggable: true, |
||||
draggablePercent: 0.6, |
||||
showCloseButtonOnHover: false, |
||||
hideProgressBar: true, |
||||
closeButton: "button", |
||||
icon: true, |
||||
rtl: false |
||||
});*/ |
||||
|
||||
/*this.show = true; |
||||
this.color = color; |
||||
if (typeof message === 'string') { |
||||
this.text = message; |
||||
this.cleanState(); |
||||
|
||||
return; |
||||
} |
||||
this.text = message.message; |
||||
if (message.response) this.subText = message.response.data.message; |
||||
this.cleanState();*/ |
||||
} |
||||
showMessage(message, type = 'success') { |
||||
// Convert message type to PrimeVue's severity
|
||||
let severity = type; |
||||
if (type === 'danger') { |
||||
severity = 'error'; // PrimeVue uses 'error' instead of 'danger'
|
||||
} |
||||
|
||||
// Use PrimeVue's ToastService
|
||||
this.$toast.add({ |
||||
severity: severity, |
||||
summary: message, |
||||
detail: '', |
||||
life: 5000, // Message duration in milliseconds
|
||||
closable: true, // Whether the message can be closed manually
|
||||
}); |
||||
} |
||||
}, |
||||
computed: { |
||||
...mapFields('notifications', ['color', 'show', 'subText', 'text', 'timeout']) |
||||
}, |
||||
}; |
||||
|
@ -0,0 +1,33 @@ |
||||
import { defineStore } from "pinia" |
||||
import axios from "axios" |
||||
import { computed, ref } from "vue" |
||||
|
||||
export const useEnrolledStore = defineStore("enrolledStore", () => { |
||||
// Reactive state to track if the user is enrolled in courses or sessions
|
||||
const isEnrolledInCourses = ref(false) |
||||
const isEnrolledInSessions = ref(false) |
||||
|
||||
// Function to check enrollment status
|
||||
async function checkEnrollments() { |
||||
try { |
||||
const { data } = await axios.get("/course/check-enrollments") |
||||
console.log('Check enrollments data:', data) |
||||
isEnrolledInCourses.value = data.isEnrolledInCourses |
||||
isEnrolledInSessions.value = data.isEnrolledInSessions |
||||
} catch (error) { |
||||
console.error("Error verifying enrollments:", error) |
||||
} |
||||
} |
||||
|
||||
// Function to initialize the store
|
||||
async function initialize() { |
||||
await checkEnrollments() |
||||
} |
||||
|
||||
return { |
||||
// Computed properties for reactivity
|
||||
isEnrolledInCourses: computed(() => isEnrolledInCourses.value), |
||||
isEnrolledInSessions: computed(() => isEnrolledInSessions.value), |
||||
initialize |
||||
}; |
||||
}); |
@ -1,120 +1,137 @@ |
||||
<template> |
||||
<Toolbar :handle-send="onSendForm" /> |
||||
<CCalendarEventForm |
||||
ref="createForm" |
||||
:errors="violations" |
||||
:values="item" |
||||
/> |
||||
|
||||
<BaseButton |
||||
:label="t('Add')" |
||||
class="ml-auto" |
||||
icon="plus" |
||||
type="primary" |
||||
@click="onClickCreateEvent" |
||||
/> |
||||
<Loading :visible="isLoading" /> |
||||
</template> |
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style> |
||||
|
||||
<script> |
||||
import { mapActions, mapGetters, useStore } from "vuex" |
||||
import { createHelpers } from "vuex-map-fields" |
||||
<script setup> |
||||
import { useStore } from "vuex" |
||||
import CCalendarEventForm from "../../components/ccalendarevent/CCalendarEventForm.vue" |
||||
import Loading from "../../components/Loading.vue" |
||||
import Toolbar from "../../components/Toolbar.vue" |
||||
import CreateMixin from "../../mixins/CreateMixin" |
||||
import { computed, onMounted, ref } from "vue" |
||||
import useVuelidate from "@vuelidate/core" |
||||
import { onMounted, ref, watch } from "vue" |
||||
import { useRoute, useRouter } from "vue-router" |
||||
import isEmpty from "lodash/isEmpty" |
||||
import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility.js" |
||||
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||
import { useSecurityStore } from "../../store/securityStore" |
||||
import { useI18n } from "vue-i18n" |
||||
import { useNotification } from "../../composables/notification" |
||||
|
||||
const servicePrefix = "Message" |
||||
|
||||
const { mapFields } = createHelpers({ |
||||
getterType: "ccalendarevent/getField", |
||||
mutationType: "ccalendarevent/updateField", |
||||
}) |
||||
//const { DateTime } = require("luxon"); |
||||
export default { |
||||
name: "CCalendarEventCreate", |
||||
servicePrefix, |
||||
mixins: [CreateMixin], |
||||
components: { |
||||
CCalendarEventForm, |
||||
Loading, |
||||
Toolbar, |
||||
}, |
||||
setup() { |
||||
const users = ref([]) |
||||
const isLoadingSelect = ref(false) |
||||
const item = ref({}) |
||||
const store = useStore() |
||||
const route = useRoute() |
||||
const router = useRouter() |
||||
|
||||
let id = route.params.id |
||||
if (isEmpty(id)) { |
||||
id = route.query.id |
||||
|
||||
const item = ref({}) |
||||
const store = useStore() |
||||
const securityStore = useSecurityStore(); |
||||
const route = useRoute() |
||||
const router = useRouter() |
||||
const { t } = useI18n() |
||||
|
||||
const createForm = ref(null) |
||||
|
||||
let id = route.params.id |
||||
if (isEmpty(id)) { |
||||
id = route.query.id |
||||
} |
||||
|
||||
const isLoading = ref(true); |
||||
|
||||
onMounted(async () => { |
||||
isLoading.value = true |
||||
|
||||
const response = await store.dispatch("message/load", id) |
||||
const currentUser = securityStore.user; |
||||
item.value = await response |
||||
|
||||
isLoading.value = false; |
||||
|
||||
// Remove unused properties: |
||||
delete item.value["status"] |
||||
delete item.value["msgType"] |
||||
delete item.value["@type"] |
||||
delete item.value["@context"] |
||||
delete item.value["@id"] |
||||
delete item.value["id"] |
||||
delete item.value["firstReceiver"] |
||||
//delete item.value['receivers']; |
||||
delete item.value["sendDate"] |
||||
|
||||
item.value.parentResourceNodeId = currentUser.resourceNode["id"] |
||||
//item.value['startDate'] = date.now(); |
||||
//item.value['endDate'] = new Date(); |
||||
//item.value['originalSender'] = item.value['sender']; |
||||
// New sender. |
||||
//item.value['sender'] = currentUser['@id']; |
||||
|
||||
// Set new receivers, will be loaded by onSendMessageForm() |
||||
item.value.resourceLinkListFromEntity = [] |
||||
const receivers = [...item.value.receiversTo, ...item.value.receiversCc]; |
||||
let itemsAdded = [] |
||||
receivers.forEach((receiver) => { |
||||
// Skip current user. |
||||
if (currentUser["@id"] === receiver.receiver["@id"]) { |
||||
return |
||||
} |
||||
item.value.resourceLinkListFromEntity.push({ |
||||
uid: receiver.receiver["id"], |
||||
user: { username: receiver.receiver["username"] }, |
||||
visibility: RESOURCE_LINK_PUBLISHED, |
||||
}) |
||||
itemsAdded.push(receiver.receiver["username"]) |
||||
}) |
||||
|
||||
onMounted(async () => { |
||||
const response = await store.dispatch("message/load", id) |
||||
const currentUser = computed(() => store.getters["security/getUser"]) |
||||
item.value = await response |
||||
|
||||
// Remove unused properties: |
||||
delete item.value["status"] |
||||
delete item.value["msgType"] |
||||
delete item.value["@type"] |
||||
delete item.value["@context"] |
||||
delete item.value["@id"] |
||||
delete item.value["id"] |
||||
delete item.value["firstReceiver"] |
||||
//delete item.value['receivers']; |
||||
delete item.value["sendDate"] |
||||
|
||||
item.value["parentResourceNodeId"] = currentUser.value.resourceNode["id"] |
||||
//item.value['startDate'] = date.now(); |
||||
//item.value['endDate'] = new Date(); |
||||
//item.value['originalSender'] = item.value['sender']; |
||||
// New sender. |
||||
//item.value['sender'] = currentUser.value['@id']; |
||||
|
||||
// Set new receivers, will be loaded by onSendMessageForm() |
||||
item.value["resourceLinkListFromEntity"] = [] |
||||
let itemsAdded = [] |
||||
item.value["receivers"].forEach((receiver) => { |
||||
// Skip current user. |
||||
if (currentUser.value["@id"] === receiver.receiver["@id"]) { |
||||
return |
||||
} |
||||
item.value.resourceLinkListFromEntity.push({ |
||||
uid: receiver.receiver["id"], |
||||
user: { username: receiver.receiver["username"] }, |
||||
visibility: RESOURCE_LINK_PUBLISHED, |
||||
}) |
||||
itemsAdded.push(receiver.receiver["username"]) |
||||
}) |
||||
|
||||
// Sender is not added to the list. |
||||
if (!itemsAdded.includes(item.value["sender"]["username"])) { |
||||
// Set the sender too. |
||||
item.value["resourceLinkListFromEntity"].push({ |
||||
uid: item.value["sender"]["id"], |
||||
user: { username: item.value["sender"]["username"] }, |
||||
visibility: RESOURCE_LINK_PUBLISHED, |
||||
}) |
||||
} |
||||
|
||||
delete item.value["sender"] |
||||
// Sender is not added to the list. |
||||
if (!itemsAdded.includes(item.value["sender"]["username"])) { |
||||
// Set the sender too. |
||||
item.value["resourceLinkListFromEntity"].push({ |
||||
uid: item.value["sender"]["id"], |
||||
user: { username: item.value["sender"]["username"] }, |
||||
visibility: RESOURCE_LINK_PUBLISHED, |
||||
}) |
||||
} |
||||
|
||||
return { v$: useVuelidate(), users, isLoadingSelect, item } |
||||
}, |
||||
computed: { |
||||
...mapFields(["error", "isLoading", "created", "violations"]), |
||||
...mapGetters({ |
||||
isAuthenticated: "security/isAuthenticated", |
||||
currentUser: "security/getUser", |
||||
}), |
||||
}, |
||||
methods: { |
||||
...mapActions("ccalendarevent", ["create", "createWithFormData", "reset"]), |
||||
}, |
||||
delete item.value["sender"] |
||||
}) |
||||
|
||||
const onClickCreateEvent = async () => { |
||||
if (createForm.value.v$.$invalid) { |
||||
return; |
||||
} |
||||
|
||||
isLoading.value = true; |
||||
|
||||
const itemModel = createForm.value.v$.item.$model |
||||
|
||||
try { |
||||
await store.dispatch("ccalendarevent/create", itemModel) |
||||
} catch (e) { |
||||
isLoading.value = false; |
||||
|
||||
notification.showErrorNotification(e) |
||||
|
||||
return; |
||||
} |
||||
|
||||
await router.push({ name: 'CCalendarEventList' }) |
||||
} |
||||
|
||||
const notification = useNotification(); |
||||
|
||||
watch( |
||||
() => store.state.ccalendarevent.created, |
||||
(created) => { |
||||
notification.showSuccessNotification( |
||||
t("{resource} created", { resource: created.resourceNode.title }) |
||||
) |
||||
}, |
||||
) |
||||
</script> |
@ -0,0 +1,154 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Chamilo\CoreBundle\EventSubscriber; |
||||
|
||||
use Chamilo\CoreBundle\Entity\TrackELogin; |
||||
use Chamilo\CoreBundle\Entity\User; |
||||
use Chamilo\CoreBundle\Settings\SettingsManager; |
||||
use DateTime; |
||||
use Doctrine\ORM\EntityManagerInterface; |
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface; |
||||
use Symfony\Component\HttpKernel\Event\RequestEvent; |
||||
use Symfony\Component\HttpKernel\KernelEvents; |
||||
use Symfony\Component\Security\Core\Security; |
||||
|
||||
class AnonymousUserSubscriber implements EventSubscriberInterface |
||||
{ |
||||
private const MAX_ANONYMOUS_USERS = 5; |
||||
private Security $security; |
||||
private EntityManagerInterface $entityManager; |
||||
private SessionInterface $session; |
||||
private SettingsManager $settingsManager; |
||||
|
||||
public function __construct(Security $security, EntityManagerInterface $entityManager, SessionInterface $session, SettingsManager $settingsManager) |
||||
{ |
||||
$this->security = $security; |
||||
$this->entityManager = $entityManager; |
||||
$this->session = $session; |
||||
$this->settingsManager = $settingsManager; |
||||
} |
||||
|
||||
public function onKernelRequest(RequestEvent $event): void |
||||
{ |
||||
if (null !== $this->security->getUser()) { |
||||
return; |
||||
} |
||||
|
||||
$request = $event->getRequest(); |
||||
$userIp = $request->getClientIp(); |
||||
|
||||
$anonymousUserId = $this->getOrCreateAnonymousUserId($userIp); |
||||
if (null !== $anonymousUserId) { |
||||
$trackLoginRepository = $this->entityManager->getRepository(TrackELogin::class); |
||||
|
||||
// Check if a login record already exists for this user and IP |
||||
$existingLogin = $trackLoginRepository->findOneBy(['userIp' => $userIp, 'user' => $anonymousUserId]); |
||||
if (!$existingLogin) { |
||||
// Record the access if it does not exist |
||||
$trackLogin = new TrackELogin(); |
||||
$trackLogin->setUserIp($userIp) |
||||
->setLoginDate(new DateTime()) |
||||
->setUser($this->entityManager->getReference(User::class, $anonymousUserId)) |
||||
; |
||||
|
||||
$this->entityManager->persist($trackLogin); |
||||
$this->entityManager->flush(); |
||||
} |
||||
|
||||
$userRepository = $this->entityManager->getRepository(User::class); |
||||
$user = $userRepository->find($anonymousUserId); |
||||
|
||||
if ($user) { |
||||
// Store user information in the session |
||||
$userInfo = [ |
||||
'user_id' => $user->getId(), |
||||
'username' => $user->getUsername(), |
||||
'firstname' => $user->getFirstname(), |
||||
'lastname' => $user->getLastname(), |
||||
'firstName' => $user->getFirstname(), |
||||
'lastName' => $user->getLastname(), |
||||
'email' => $user->getEmail(), |
||||
'official_code' => $user->getOfficialCode(), |
||||
'picture_uri' => $user->getPictureUri(), |
||||
'status' => $user->getStatus(), |
||||
'active' => $user->getActive(), |
||||
'auth_source' => $user->getAuthSource(), |
||||
'theme' => $user->getTheme(), |
||||
'language' => $user->getLocale(), |
||||
'registration_date' => $user->getRegistrationDate()->format('Y-m-d H:i:s'), |
||||
'expiration_date' => $user->getExpirationDate() ? $user->getExpirationDate()->format('Y-m-d H:i:s') : null, |
||||
'last_login' => $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : null, |
||||
'is_anonymous' => true, |
||||
]; |
||||
|
||||
$this->session->set('_user', $userInfo); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static function getSubscribedEvents() |
||||
{ |
||||
return [ |
||||
KernelEvents::REQUEST => 'onKernelRequest', |
||||
]; |
||||
} |
||||
|
||||
private function getOrCreateAnonymousUserId(string $userIp): ?int |
||||
{ |
||||
$userRepository = $this->entityManager->getRepository(User::class); |
||||
$trackLoginRepository = $this->entityManager->getRepository(TrackELogin::class); |
||||
|
||||
$maxAnonymousUsers = (int) $this->settingsManager->getSetting('admin.max_anonymous_users'); |
||||
if (0 === $maxAnonymousUsers) { |
||||
$maxAnonymousUsers = self::MAX_ANONYMOUS_USERS; |
||||
} |
||||
$anonymousUsers = $userRepository->findBy(['status' => User::ANONYMOUS], ['registrationDate' => 'ASC']); |
||||
|
||||
// Check in TrackELogin if there is an anonymous user with the same IP |
||||
foreach ($anonymousUsers as $user) { |
||||
$loginRecord = $trackLoginRepository->findOneBy(['userIp' => $userIp, 'user' => $user]); |
||||
if ($loginRecord) { |
||||
error_log('Existing login found for user ID: '.$user->getId()); |
||||
|
||||
return $user->getId(); |
||||
} |
||||
} |
||||
|
||||
// Delete excess anonymous users |
||||
while (\count($anonymousUsers) >= $maxAnonymousUsers) { |
||||
$oldestAnonymousUser = array_shift($anonymousUsers); |
||||
if ($oldestAnonymousUser) { |
||||
error_log('Deleting oldest anonymous user: '.$oldestAnonymousUser->getId()); |
||||
$this->entityManager->remove($oldestAnonymousUser); |
||||
$this->entityManager->flush(); |
||||
} |
||||
} |
||||
|
||||
// Create a new anonymous user |
||||
$uniqueId = uniqid(); |
||||
$anonymousUser = (new User()) |
||||
->setSkipResourceNode(true) |
||||
->setLastname('Joe') |
||||
->setFirstname('Anonymous') |
||||
->setUsername('anon_'.$uniqueId) |
||||
->setStatus(User::ANONYMOUS) |
||||
->setPlainPassword('anon') |
||||
->setEmail('anon_'.$uniqueId.'@localhost.local') |
||||
->setOfficialCode('anonymous') |
||||
->setCreatorId(1) |
||||
->addRole('ROLE_ANONYMOUS') |
||||
; |
||||
|
||||
$this->entityManager->persist($anonymousUser); |
||||
$this->entityManager->flush(); |
||||
|
||||
error_log('New anonymous user created: '.$anonymousUser->getId()); |
||||
|
||||
return $anonymousUser->getId(); |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
<?php |
||||
|
||||
/* For licensing terms, see /license.txt */ |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Chamilo\CoreBundle\Security\Authorization\Voter; |
||||
|
||||
use Chamilo\CoreBundle\Entity\User; |
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter; |
||||
|
||||
class AnonymousVoter extends Voter |
||||
{ |
||||
protected function supports(string $attribute, $subject): bool |
||||
{ |
||||
return 'ROLE_ANONYMOUS' === $attribute; |
||||
} |
||||
|
||||
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool |
||||
{ |
||||
$user = $token->getUser(); |
||||
|
||||
if (!$user instanceof User) { |
||||
return false; |
||||
} |
||||
|
||||
return User::ANONYMOUS === $user->getStatus(); |
||||
} |
||||
} |
Loading…
Reference in new issue