From 6e94642113557fce2b9ff690627c4fb806e3af8f Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 27 Mar 2024 09:25:18 -0500 Subject: [PATCH] Replacing admin blocks (#5296) * Admin: Refactoring and improve index blocks #2642 * Admin: Include link to Colors page in the AdminBlocks response payload #2642 * Minor: Format code #2642 * Display: Add BaseDivider component #2642 * Internal: Add page categories for admin blocks #2642 * Admin: Allow edit blocks #2642 --- assets/css/scss/atoms/_divider.scss | 25 +++ assets/css/scss/index.scss | 1 + assets/vue/components/admin/AdminBlock.vue | 95 ++++++++--- .../admin/AdminBlockExtraContent.vue | 102 ++++++++++++ .../components/basecomponents/BaseDivider.vue | 32 ++++ assets/vue/composables/admin/indexBlocks.js | 92 +++++++++++ assets/vue/services/adminService.js | 40 +++++ assets/vue/services/pageService.js | 32 ++++ assets/vue/views/admin/AdminIndex.vue | 149 +++++++----------- .../Component/Utils/CreateDefaultPages.php | 23 +++ .../Admin/IndexBlocksController.php | 62 +++++++- src/CoreBundle/Entity/Page.php | 6 +- 12 files changed, 543 insertions(+), 116 deletions(-) create mode 100644 assets/css/scss/atoms/_divider.scss create mode 100644 assets/vue/components/admin/AdminBlockExtraContent.vue create mode 100644 assets/vue/components/basecomponents/BaseDivider.vue create mode 100644 assets/vue/composables/admin/indexBlocks.js create mode 100644 assets/vue/services/adminService.js create mode 100644 assets/vue/services/pageService.js diff --git a/assets/css/scss/atoms/_divider.scss b/assets/css/scss/atoms/_divider.scss new file mode 100644 index 0000000000..ca7e5a31bc --- /dev/null +++ b/assets/css/scss/atoms/_divider.scss @@ -0,0 +1,25 @@ +.divider { + @apply bg-white; + + &[aria-orientation="vertical"] { + @apply before:absolute before:block before:left-1/2 before:top-0 before:h-full before:content-[""] before:border-l before:border-solid before:border-gray-25 + flex min-h-full mx-2 py-2 relative justify-center; + + div { + @apply first:py-2; + } + } + + &[aria-orientation="horizontal"] { + @apply before:absolute before:block before:left-0 before:w-full before:top-1/2 before:content-[""] before:border-t before:border-solid before:border-gray-25 + flex w-full relative items-center my-2 px-2; + + div { + @apply first:px-2; + } + } + + div { + @apply first:z-[1] first:bg-white first:text-gray-50 first:font-semibold first:text-caption; + } +} diff --git a/assets/css/scss/index.scss b/assets/css/scss/index.scss index 578d7eb90c..6398720486 100755 --- a/assets/css/scss/index.scss +++ b/assets/css/scss/index.scss @@ -19,6 +19,7 @@ @import "atoms/buttons"; @import "atoms/calendar"; @import "atoms/checkbox"; +@import "atoms/divider"; @import "atoms/dropdown"; @import "atoms/fieldset"; @import "atoms/inline_message"; diff --git a/assets/vue/components/admin/AdminBlock.vue b/assets/vue/components/admin/AdminBlock.vue index d68413f9d7..8c96cf04d5 100644 --- a/assets/vue/components/admin/AdminBlock.vue +++ b/assets/vue/components/admin/AdminBlock.vue @@ -1,5 +1,8 @@ diff --git a/assets/vue/components/admin/AdminBlockExtraContent.vue b/assets/vue/components/admin/AdminBlockExtraContent.vue new file mode 100644 index 0000000000..4b5a082ab0 --- /dev/null +++ b/assets/vue/components/admin/AdminBlockExtraContent.vue @@ -0,0 +1,102 @@ + + + diff --git a/assets/vue/components/basecomponents/BaseDivider.vue b/assets/vue/components/basecomponents/BaseDivider.vue new file mode 100644 index 0000000000..3e18e44edd --- /dev/null +++ b/assets/vue/components/basecomponents/BaseDivider.vue @@ -0,0 +1,32 @@ + + + diff --git a/assets/vue/composables/admin/indexBlocks.js b/assets/vue/composables/admin/indexBlocks.js new file mode 100644 index 0000000000..b8663d69d5 --- /dev/null +++ b/assets/vue/composables/admin/indexBlocks.js @@ -0,0 +1,92 @@ +import { onMounted, ref } from "vue" +import { usePlatformConfig } from "../../store/platformConfig" +import adminService from "../../services/adminService" +import { useToast } from "primevue/usetoast" +import { useSecurityStore } from "../../store/securityStore" +import { useI18n } from "vue-i18n" + +export function useIndexBlocks() { + const { t } = useI18n() + + const toast = useToast() + + const platformConfigStore = usePlatformConfig() + const securityStore = useSecurityStore() + + const blockVersionStatusEl = ref() + + onMounted(() => { + if (!securityStore.isAdmin) { + return + } + + if ("false" === platformConfigStore.getSetting("admin.admin_chamilo_announcements_disable")) { + adminService.findAnnouncements().then((announcement) => toast.add({ severity: "info", detail: announcement })) + } + + if ("false" === platformConfigStore.getSetting("platform.registered")) { + blockVersionStatusEl.value = null + } else { + loadVersion() + } + }) + + /** + * @param {boolean} doNotListCampus + */ + function checkVersion(doNotListCampus) { + adminService.registerCampus(doNotListCampus).then(() => { + loadVersion() + + toast.add({ + severity: "success", + detail: t("Version check enabled"), + }) + }) + } + + async function loadVersion() { + blockVersionStatusEl.value = t("Loading") + + blockVersionStatusEl.value = await adminService.findVersion() + } + + const blockUsers = ref(null) + const blockCourses = ref(null) + const blockSessions = ref(null) + const blockGradebook = ref(null) + const blockSkills = ref(null) + const blockPrivacy = ref(null) + const blockSettings = ref(null) + const blockPlatform = ref(null) + const blockChamilo = ref(null) + + async function loadBlocks() { + const blocks = await adminService.findBlocks() + + blockUsers.value = blocks.users || null + blockCourses.value = blocks.courses || null + blockSessions.value = blocks.sessions || null + blockGradebook.value = blocks.gradebook || null + blockSkills.value = blocks.skills || null + blockPrivacy.value = blocks.data_privacy || null + blockSettings.value = blocks.settings || null + blockPlatform.value = blocks.platform || null + blockChamilo.value = blocks.chamilo || null + } + + return { + blockVersionStatusEl, + checkVersion, + blockUsers, + blockCourses, + blockSessions, + blockGradebook, + blockSkills, + blockPrivacy, + blockSettings, + blockPlatform, + blockChamilo, + loadBlocks, + } +} diff --git a/assets/vue/services/adminService.js b/assets/vue/services/adminService.js new file mode 100644 index 0000000000..799aedf1b9 --- /dev/null +++ b/assets/vue/services/adminService.js @@ -0,0 +1,40 @@ +import axios from "axios" + +export default { + /** + * @param {boolean} doNotListCampus + * @returns {Promise} + */ + registerCampus: async (doNotListCampus) => { + await axios.post("/admin/register-campus", { + donotlistcampus: doNotListCampus, + }) + }, + + /** + * @returns {Promise} + */ + findAnnouncements: async () => { + const { data } = await axios.get("/main/inc/ajax/admin.ajax.php?a=get_latest_news") + + return data + }, + + /** + * @returns {Promise} + */ + findVersion: async () => { + const { data } = await axios.get("/main/inc/ajax/admin.ajax.php?a=version") + + return data + }, + + /** + * @returns {Promise} + */ + findBlocks: async () => { + const { data } = await axios.get("/admin/index") + + return data + }, +} diff --git a/assets/vue/services/pageService.js b/assets/vue/services/pageService.js new file mode 100644 index 0000000000..0b4e07923d --- /dev/null +++ b/assets/vue/services/pageService.js @@ -0,0 +1,32 @@ +import api from "../config/api" + +export default { + /** + * @param {Object} params + * @returns {Promise} + */ + async post(params) { + const { data } = await api.post("/api/pages", params) + + return data + }, + + /** + * @param {string} iri + * @param {Object} params + * @returns {Promise} + */ + async update(iri, params) { + const { data } = await api.put(iri, params) + + return data + }, + + /** + * @param {string} iri + * @returns {Promise} + */ + async delete(iri) { + await api.delete(iri) + }, +} diff --git a/assets/vue/views/admin/AdminIndex.vue b/assets/vue/views/admin/AdminIndex.vue index dcc2da0b6a..873b85b89b 100644 --- a/assets/vue/views/admin/AdminIndex.vue +++ b/assets/vue/views/admin/AdminIndex.vue @@ -16,78 +16,97 @@ >

+
-
- - + /> @@ -176,83 +183,43 @@ diff --git a/src/CoreBundle/Component/Utils/CreateDefaultPages.php b/src/CoreBundle/Component/Utils/CreateDefaultPages.php index 0b2dab7581..982d45b569 100644 --- a/src/CoreBundle/Component/Utils/CreateDefaultPages.php +++ b/src/CoreBundle/Component/Utils/CreateDefaultPages.php @@ -99,6 +99,29 @@ class CreateDefaultPages $this->pageCategoryRepository->update($footerPrivateCategory); + // Categories for extra content in admin blocks + + $adminBlocks = [ + 'block-admin-users', + 'block-admin-courses', + 'block-admin-sessions', + 'block-admin-gradebook', + 'block-admin-skills', + 'block-admin-privacy', + 'block-admin-settings', + 'block-admin-platform', + 'block-admin-chamilo', + ]; + + foreach ($adminBlocks as $nameBlock) { + $usersAdminBlock = (new PageCategory()) + ->setTitle($nameBlock) + ->setType('grid') + ->setCreator($user) + ; + $this->pageCategoryRepository->update($usersAdminBlock); + } + return true; } } diff --git a/src/CoreBundle/Controller/Admin/IndexBlocksController.php b/src/CoreBundle/Controller/Admin/IndexBlocksController.php index afac854f11..9acf909594 100644 --- a/src/CoreBundle/Controller/Admin/IndexBlocksController.php +++ b/src/CoreBundle/Controller/Admin/IndexBlocksController.php @@ -7,10 +7,15 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Controller\Admin; use Chamilo\CoreBundle\Controller\BaseController; +use Chamilo\CoreBundle\Entity\Page; +use Chamilo\CoreBundle\Entity\PageCategory; +use Chamilo\CoreBundle\Repository\PageCategoryRepository; +use Chamilo\CoreBundle\Repository\PageRepository; use Chamilo\CoreBundle\Settings\SettingsManager; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\Translation\TranslatorInterface; #[Security("is_granted('ROLE_ADMIN') or is_granted('ROLE_SESSION_MANAGER')")] @@ -23,7 +28,10 @@ class IndexBlocksController extends BaseController public function __construct( private readonly TranslatorInterface $translator, - private readonly SettingsManager $settingsManager + private readonly SettingsManager $settingsManager, + private readonly PageRepository $pageRepository, + private readonly PageCategoryRepository $pageCategoryRepository, + private readonly SerializerInterface $serializer ) { $this->extAuthSource = [ 'extldap' => [], @@ -38,70 +46,110 @@ class IndexBlocksController extends BaseController $json = []; $json['users'] = [ + 'id' => 'block-admin-users', 'searchUrl' => $this->generateUrl('legacy_main', ['name' => 'admin/user_list.php']), 'editable' => $this->isAdmin, 'items' => $this->getItemsUsers(), + 'extraContent' => $this->getExtraContent('block-admin-users'), ]; if ($this->isAdmin) { $json['courses'] = [ + 'id' => 'block-admin-courses', 'searchUrl' => $this->generateUrl('legacy_main', ['name' => 'admin/course_list.php']), 'editable' => true, 'items' => $this->getItemsCourses(), + 'extraContent' => $this->getExtraContent('block-admin-courses'), ]; $json['platform'] = [ + 'id' => 'block-admin-platform', 'searchUrl' => $this->generateUrl('chamilo_platform_settings_search'), 'editable' => true, 'items' => $this->getItemsPlatform(), + 'extraContent' => $this->getExtraContent('block-admin-platform'), ]; /* Settings */ $json['settings'] = [ + 'id' => 'block-admin-settings', 'editable' => false, 'items' => $this->getItemsSettings(), + 'extraContent' => $this->getExtraContent('block-admin-settings'), ]; // Skills if ('true' === $this->settingsManager->getSetting('skill.allow_skills_tool')) { $json['skills'] = [ + 'id' => 'block-admin-skills', 'editable' => false, 'items' => $this->getItemsSkills(), + 'extraContent' => $this->getExtraContent('block-admin-skills'), ]; } if ('true' === $this->settingsManager->getSetting('gradebook.gradebook_dependency')) { $json['gradebook'] = [ + 'id' => 'block-admin-gradebook', 'editable' => false, 'items' => $this->getItemsGradebook(), + 'extraContent' => $this->getExtraContent('block-admin-gradebook'), ]; } // Data protection if ('true' !== $this->settingsManager->getSetting('profile.disable_gdpr')) { $json['data_privacy'] = [ + 'id' => 'block-admin-privacy', 'editable' => false, 'items' => $this->getItemsPrivacy(), + 'extraContent' => $this->getExtraContent('block-admin-privacy'), ]; } /* Chamilo.org */ $json['chamilo'] = [ + 'id' => 'block-admin-chamilo', 'editable' => false, 'items' => $this->getItemsChamilo(), + 'extraContent' => $this->getExtraContent('block-admin-chamilo'), ]; } /* Sessions */ $json['sessions'] = [ + 'id' => 'block-admin-sessions', 'searchUrl' => $this->generateUrl('legacy_main', ['name' => 'session/session_list.php']), 'editable' => $this->isAdmin, 'items' => $this->getItemsSessions(), + 'extraContent' => $this->getExtraContent('block-admin-sessions'), ]; return $this->json($json); } + private function getExtraContent(string $title): ?array + { + /** @var Page|null $page */ + $page = $this->pageRepository->findOneBy(['title' => $title]); + + $pageJsonld = $this->serializer->serialize($page, 'jsonld', ['groups' => ['adminblock:read']]); + $pageArray = json_decode($pageJsonld, true); + + if ($page) { + return $pageArray; + } + + /** @var PageCategory $category */ + $category = $this->pageCategoryRepository->findOneBy(['title' => $title]); + $categoryJsonld = $this->serializer->serialize($category, 'jsonld', ['groups' => ['page:read']]); + $categoryArray = json_decode($categoryJsonld, true); + + return [ + 'category' => $categoryArray['@id'], + ]; + } + private function getItemsUsers(): array { $items = []; @@ -339,7 +387,7 @@ class IndexBlocksController extends BaseController ]; $items[] = [ 'class' => 'item-global-agenda', - 'url' => '/resources/ccalendarevent?type=global', + 'route' => ['name' => 'CCalendarEventList', 'query' => ['type' => 'global']], 'label' => $this->translator->trans('Global agenda'), ]; @@ -353,7 +401,7 @@ class IndexBlocksController extends BaseController $items[] = [ 'class' => 'item-pages-list', - 'url' => '/resources/pages', + 'route' => ['name' => 'PageList'], 'label' => $this->translator->trans('Pages'), ]; $items[] = [ @@ -414,7 +462,7 @@ class IndexBlocksController extends BaseController if ('true' === $this->settingsManager->getSetting('registration.allow_terms_conditions')) { $items[] = [ 'class' => 'item-terms-and-conditions', - 'url' => '/resources/terms-conditions', + 'route' => ['name' => 'TermsConditions'], 'label' => $this->translator->trans('Terms and Conditions'), ]; } @@ -608,6 +656,12 @@ class IndexBlocksController extends BaseController ]; } + $items[] = [ + 'class' => 'item-colors', + 'route' => ['name' => 'AdminConfigurationColors'], + 'label' => $this->translator->trans('Colors'), + ]; + return $items; } diff --git a/src/CoreBundle/Entity/Page.php b/src/CoreBundle/Entity/Page.php index 8898cae4e8..fb21ad2fd8 100644 --- a/src/CoreBundle/Entity/Page.php +++ b/src/CoreBundle/Entity/Page.php @@ -31,7 +31,7 @@ use Symfony\Component\Validator\Constraints as Assert; new Post(security: 'is_granted(\'ROLE_ADMIN\')'), ], normalizationContext: [ - 'groups' => ['page:read', 'timestampable_created:read', 'timestampable_updated:read'], + 'groups' => ['page:read', 'timestampable_created:read', 'timestampable_updated:read', 'adminblock:read'], ], denormalizationContext: [ 'groups' => ['page:write'], @@ -61,7 +61,7 @@ class Page #[Groups(['page:read', 'page:write'])] #[ORM\Column(name: 'title', type: 'string', length: 255)] protected string $title; - #[Groups(['page:read', 'page:write'])] + #[Groups(['page:read', 'page:write', 'adminblock:read'])] #[Assert\NotBlank] #[ORM\Column(name: 'content', type: 'text')] protected string $content; @@ -88,7 +88,7 @@ class Page #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(name: 'creator_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] protected User $creator; - #[Groups(['page:read', 'page:write'])] + #[Groups(['page:read', 'page:write', 'adminblock:read'])] #[Gedmo\SortableGroup] #[ORM\ManyToOne(targetEntity: PageCategory::class, inversedBy: 'pages')] #[ORM\JoinColumn(name: 'category_id', referencedColumnName: 'id', onDelete: 'SET NULL')]