diff --git a/composer.json b/composer.json index 41893e2e6b..76bb29a348 100755 --- a/composer.json +++ b/composer.json @@ -114,7 +114,7 @@ "sensio/framework-extra-bundle": "~6.1", "simpod/doctrine-utcdatetime": "^0.1.2", "sonata-project/exporter": "^2.2", - "stof/doctrine-extensions-bundle": "~1.4", + "stof/doctrine-extensions-bundle": "^1.10", "sunra/php-simple-html-dom-parser": "~1.5", "symfony/apache-pack": "^1.0", "symfony/asset": "5.4.*", diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 4a001d214e..76f888d272 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -59,3 +59,7 @@ doctrine: string_functions: MONTH: DoctrineExtensions\Query\Mysql\Month YEAR: DoctrineExtensions\Query\Mysql\Year + filters: + softdeleteable: + class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter + enabled: true diff --git a/config/packages/stof_doctrine_extensions.yaml b/config/packages/stof_doctrine_extensions.yaml index af7234d1b0..bb6723f291 100644 --- a/config/packages/stof_doctrine_extensions.yaml +++ b/config/packages/stof_doctrine_extensions.yaml @@ -8,5 +8,6 @@ stof_doctrine_extensions: tree: true timestampable: true sluggable: true + softdeleteable: true sortable: true translatable: true diff --git a/public/main/announcements/announcements.php b/public/main/announcements/announcements.php index f8f820159a..94b6f7c20d 100644 --- a/public/main/announcements/announcements.php +++ b/public/main/announcements/announcements.php @@ -26,9 +26,12 @@ api_protect_course_group(GroupManager::GROUP_TOOL_ANNOUNCEMENT); $token = Security::get_existing_token(); $courseId = api_get_course_int_id(); +$course = api_get_course_entity(); $_course = api_get_course_info_by_id($courseId); $group_id = api_get_group_id(); +$group = api_get_group_entity(); $sessionId = api_get_session_id(); +$session = api_get_session_entity(); $current_course_tool = TOOL_ANNOUNCEMENT; $this_section = SECTION_COURSES; $nameTools = get_lang('Announcements'); @@ -115,16 +118,21 @@ switch ($action) { $sortDirection = 'up'; } + /** @var CAnnouncement $currentAnnouncement */ $currentAnnouncement = $repo->find($thisAnnouncementId); if ($currentAnnouncement) { $resourceNode = $currentAnnouncement->getResourceNode(); - $currentDisplayOrder = $resourceNode->getDisplayOrder(); + $link = $resourceNode->getResourceLinkByContext($course, $session, $group); - $newPosition = $currentDisplayOrder + ($sortDirection === 'down' ? 1 : -1); - $newPosition = max(0, $newPosition); + if ($link) { + if ('down' === $sortDirection) { + $link->moveDownPosition(); + } else { + $link->moveUpPosition(); + } - $resourceNode->setDisplayOrder($newPosition); - $em->flush(); + $em->flush(); + } } header('Location: '.$homeUrl); diff --git a/public/main/course_progress/index.php b/public/main/course_progress/index.php index d03d1f8be6..7dbb278a6c 100644 --- a/public/main/course_progress/index.php +++ b/public/main/course_progress/index.php @@ -595,7 +595,8 @@ switch ($action) { ['class' => 'btn btn--plain'] ); if (0 == api_get_session_id()) { - $currentOrder = $thematic->getResourceNode()->getDisplayOrder(); + $link = $thematic->getResourceNode()->getResourceLinkByContext($course, $session); + $currentOrder = $link ? $link->getDisplayOrder() : 0; $moveButtons = $thematicManager->getMoveActions($id, $currentOrder, count($thematic_data)); $toolbarThematic .= $moveButtons; } diff --git a/public/main/exercise/exercise.class.php b/public/main/exercise/exercise.class.php index 7c4abade85..23d425462a 100644 --- a/public/main/exercise/exercise.class.php +++ b/public/main/exercise/exercise.class.php @@ -9,6 +9,7 @@ use Chamilo\CoreBundle\Entity\TrackEExercise; use Chamilo\CoreBundle\Entity\TrackEExerciseConfirmation; use Chamilo\CoreBundle\Entity\TrackEHotspot; use Chamilo\CoreBundle\Framework\Container; +use Chamilo\CoreBundle\Repository\ResourceLinkRepository; use Chamilo\CourseBundle\Entity\CExerciseCategory; use Chamilo\CourseBundle\Entity\CQuiz; use Chamilo\CourseBundle\Entity\CQuizRelQuestionCategory; @@ -1805,7 +1806,9 @@ class Exercise $exerciseId = $this->iId; $repo = Container::getQuizRepository(); + /** @var CQuiz $exercise */ $exercise = $repo->find($exerciseId); + $linksRepo = Container::$container->get(ResourceLinkRepository::class); if (null === $exercise) { return false; @@ -1825,7 +1828,10 @@ class Exercise WHERE iid = $exerciseId"; Database::query($sql); - $repo->softDelete($exercise); + $course = api_get_course_entity(); + $session = api_get_session_entity(); + + $linksRepo->removeByResourceInContext($exercise, $course, $session); SkillModel::deleteSkillsFromItem($exerciseId, ITEM_TYPE_EXERCISE); diff --git a/public/main/forum/forumfunction.inc.php b/public/main/forum/forumfunction.inc.php index 6ac845b556..dc11024a9e 100644 --- a/public/main/forum/forumfunction.inc.php +++ b/public/main/forum/forumfunction.inc.php @@ -2,8 +2,10 @@ /* For licensing terms, see /license.txt */ +use Chamilo\CoreBundle\Entity\AbstractResource; use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\GradebookLink; +use Chamilo\CoreBundle\Entity\ResourceLink; use Chamilo\CoreBundle\Entity\Session as SessionEntity; use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Framework\Container; @@ -30,6 +32,10 @@ function handleForum($url) $id = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : null; if (api_is_allowed_to_edit(false, true)) { + $course = api_get_course_entity(); + $session = api_get_session_entity(); + $linksRepo = Database::getManager()->getRepository(ResourceLink::class); + //if is called from a learning path lp_id $lp_id = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : null; $content = $_REQUEST['content'] ?? ''; @@ -47,6 +53,7 @@ function handleForum($url) break; } + /** @var AbstractResource|null $resource */ $resource = null; if ($repo && $id) { $resource = $repo->find($id); @@ -143,7 +150,7 @@ function handleForum($url) break; case 'delete_category': if ($resource) { - $repo->delete($resource); + $linksRepo->removeByResourceInContext($resource, $course, $session); Display::addFlash( Display::return_message(get_lang('Forum category deleted'), 'confirmation', false) @@ -154,8 +161,8 @@ function handleForum($url) break; case 'delete_forum': if ($resource) { - $resource = Container::getForumRepository()->find($id); - $repo->delete($resource); + $linksRepo->removeByResourceInContext($resource, $course, $session); + Display::addFlash(Display::return_message(get_lang('Forum deleted'), 'confirmation', false)); } @@ -165,7 +172,8 @@ function handleForum($url) case 'delete_thread': $locked = api_resource_is_locked_by_gradebook($id, LINK_FORUM_THREAD); if ($resource && false === $locked) { - $repo->delete($resource); + $linksRepo->removeByResourceInContext($resource, $course, $session); + SkillModel::deleteSkillsFromItem($id, ITEM_TYPE_FORUM_THREAD); $link_info = GradebookUtils::isResourceInCourseGradebook( api_get_course_id(), @@ -576,7 +584,6 @@ function saveForumCategory(array $values, array $courseInfo = [], bool $showMess { $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo; $course_id = $courseInfo['real_id']; - $new_max = 1; $session_id = api_get_session_id(); $clean_cat_title = $values['forum_category_title']; $repo = Container::getForumCategoryRepository(); @@ -609,7 +616,6 @@ function saveForumCategory(array $values, array $courseInfo = [], bool $showMess $category ->setTitle($clean_cat_title) ->setCatComment($values['forum_category_comment'] ?? '') - ->setCatOrder($new_max) ->setParent($course) ->addCourseLink($course, $session) ; @@ -666,8 +672,6 @@ function store_forum(array $values, array $courseInfo = [], bool $returnId = fal $new_max = $row['sort_max'] + 1;*/ } - $new_max = 0; - // Forum images $has_attachment = false; $image_moved = true; @@ -714,7 +718,6 @@ function store_forum(array $values, array $courseInfo = [], bool $returnId = fal if (!isset($values['forum_id'])) { $forum = new CForum(); - $forum->setForumOrder($new_max ?? null); } else { /** @var CForum $forum */ $forum = $repo->find($values['forum_id']); @@ -1025,7 +1028,6 @@ function moveUpDown(string $content, string $direction, int $id): string { $em = Database::getManager(); - $entity = null; if ('forumcategory' === $content) { $entityRepo = $em->getRepository(CForumCategory::class); } elseif ('forum' === $content) { @@ -1046,12 +1048,21 @@ function moveUpDown(string $content, string $direction, int $id): string return false; } - $currentDisplayOrder = $resourceNode->getDisplayOrder(); + $course = api_get_course_entity(); + $session = api_get_session_entity(); + + $link = $resourceNode->getResourceLinkByContext($course, $session); - $newPosition = $currentDisplayOrder + ($direction === 'down' ? 1 : -1); - $newPosition = max(0, $newPosition); + if (!$link) { + return false; + } + + if ('down' === $direction) { + $link->moveDownPosition(); + } else { + $link->moveUpPosition(); + } - $resourceNode->setDisplayOrder($newPosition); $em->flush(); Display::addFlash(Display::return_message(get_lang('Updated'))); @@ -1083,6 +1094,8 @@ function get_forum_categories(int $courseId = 0, int $sessionId = 0): Array /** * This function retrieves all the fora in a given forum category. * + * @todo: Fixes error if there forums with no category. + * * @param int $categoryId the id of the forum category * @param int $courseId Optional. The course ID * @@ -1097,11 +1110,10 @@ function get_forums_in_category(int $categoryId, int $courseId = 0) $repo = Container::getForumRepository(); $course = api_get_course_entity($courseId); - $qb = $repo->getResourcesByCourse($course, null); + $qb = $repo->getResourcesByCourse($course, null, null, null, true, true); $qb ->andWhere('resource.forumCategory = :catId') ->setParameter('catId', $categoryId) - ->orderBy('node.displayOrder') ; return $qb->getQuery()->getResult(); diff --git a/public/main/inc/ajax/course_home.ajax.php b/public/main/inc/ajax/course_home.ajax.php index a8768d4cc0..7c94c8aae2 100644 --- a/public/main/inc/ajax/course_home.ajax.php +++ b/public/main/inc/ajax/course_home.ajax.php @@ -222,7 +222,7 @@ switch ($action) { api_get_user_id(), api_get_course_info($item['code']), $session_id, - 'lp.publishedOn DESC' + 'resource.publishedOn DESC' ); $flat_list = $list->get_flat_list(); $lps[$item['code']] = $flat_list; diff --git a/public/main/inc/lib/AnnouncementManager.php b/public/main/inc/lib/AnnouncementManager.php index 2cd8c94c29..a567430f67 100644 --- a/public/main/inc/lib/AnnouncementManager.php +++ b/public/main/inc/lib/AnnouncementManager.php @@ -1394,7 +1394,7 @@ class AnnouncementManager $announcements = $qb->getQuery()->getResult(); - $iterator = 1; + $iterator = 0; $bottomAnnouncement = $announcement_number; $displayed = []; @@ -1582,7 +1582,7 @@ class AnnouncementManager .$iconVisibility.""; // Move up action - if ($iterator == 1) { + if ($iterator == 0 ) { $move1 = $iconUpDisabled; } else { $move1 = "".$iconUp.""; @@ -1590,7 +1590,7 @@ class AnnouncementManager $modify_icons .= $move1; // Move down action - if ($iterator == 4) { + if ($iterator === count($announcements) - 1) { $move2 = $iconDownDisabled; } else { $move2 = "".$iconDown."";; diff --git a/public/main/inc/lib/document.lib.php b/public/main/inc/lib/document.lib.php index b5a9dd10ec..488e79f913 100644 --- a/public/main/inc/lib/document.lib.php +++ b/public/main/inc/lib/document.lib.php @@ -581,7 +581,7 @@ class DocumentManager docs.filetype = 'folder' AND $groupCondition AND n.path NOT LIKE '%shared_folder%' AND - l.visibility NOT IN ('".ResourceLink::VISIBILITY_DELETED."') + l.deleted_at IS NULL $condition_session "; if (0 != $groupIid) { @@ -681,7 +681,7 @@ class DocumentManager WHERE docs.filetype = 'folder' AND $groupCondition AND - l.visibility NOT IN ('".ResourceLink::VISIBILITY_DELETED."') + l.deleted_at IS NULL $condition_session AND l.c_id = $courseId "; $folder_in_invisible_result = Database::query($sql); @@ -3058,7 +3058,7 @@ class DocumentManager l.c_id = $course_id AND docs.filetype = 'folder' AND n.path IN ('".$folder_sql."') AND - l.visibility NOT IN ('".ResourceLink::VISIBILITY_DELETED."') + l.deleted_at IS NULL "; /*$sql = "SELECT path, title diff --git a/public/main/inc/lib/groupmanager.lib.php b/public/main/inc/lib/groupmanager.lib.php index 3883e33834..4018b4851d 100644 --- a/public/main/inc/lib/groupmanager.lib.php +++ b/public/main/inc/lib/groupmanager.lib.php @@ -1068,6 +1068,9 @@ class GroupManager { $em = Database::getManager(); + $course = api_get_course_entity(); + $session = api_get_session_entity(); + $groupCategoryRepo = $em->getRepository(CGroupCategory::class); $cat1 = $groupCategoryRepo->find($id1); $cat2 = $groupCategoryRepo->find($id2); @@ -1077,11 +1080,16 @@ class GroupManager $node2 = $cat2->getResourceNode(); if ($node1 && $node2) { - $order1 = $node1->getDisplayOrder(); - $order2 = $node2->getDisplayOrder(); + $link1 = $node1->getResourceLinkByContext($course, $session); + $link2 = $node2->getResourceLinkByContext($course, $session); + + if ($link1 && $link2) { + $order1 = $link1->getDisplayOrder(); + $order2 = $link2->getDisplayOrder(); - $node1->setDisplayOrder($order2); - $node2->setDisplayOrder($order1); + $link1->setDisplayOrder($order2); + $link2->setDisplayOrder($order1); + } $em->flush(); } diff --git a/public/main/inc/lib/message.lib.php b/public/main/inc/lib/message.lib.php index 4e57341d54..ff225bc44d 100644 --- a/public/main/inc/lib/message.lib.php +++ b/public/main/inc/lib/message.lib.php @@ -469,17 +469,6 @@ class MessageManager return $result; } - public static function softDeleteAttachments(Message $message): void - { - $attachments = $message->getAttachments(); - if (!empty($attachments)) { - $repo = Container::getMessageAttachmentRepository(); - foreach ($attachments as $file) { - $repo->softDelete($file); - } - } - } - /** * Saves a message attachment files. * diff --git a/public/main/inc/lib/thematic.lib.php b/public/main/inc/lib/thematic.lib.php index 3ab187c701..7a79b8cd6d 100644 --- a/public/main/inc/lib/thematic.lib.php +++ b/public/main/inc/lib/thematic.lib.php @@ -5,6 +5,7 @@ use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Framework\Container; +use Chamilo\CoreBundle\Repository\ResourceLinkRepository; use Chamilo\CourseBundle\Entity\CAttendance; use Chamilo\CourseBundle\Entity\CThematic; use Chamilo\CourseBundle\Entity\CThematicAdvance; @@ -82,12 +83,18 @@ class Thematic return false; } - $currentDisplayOrder = $resourceNode->getDisplayOrder(); + $link = $resourceNode->getResourceLinkByContext($course, $session); - $newPosition = $currentDisplayOrder + ($direction === 'down' ? 1 : -1); - $newPosition = max(0, $newPosition); + if (!$link) { + return false; + } + + if ('down' === $direction) { + $link->moveDownPosition(); + } else { + $link->moveUpPosition(); + } - $resourceNode->setDisplayOrder($newPosition); $em->flush(); // update done advances with de current thematic list @@ -151,8 +158,6 @@ class Thematic $thematic ->setTitle($title) ->setContent($content) - //->setDisplayOrder($max_thematic_item + 1) - ->setDisplayOrder(0) ->setParent($course) ->addCourseLink($course, $session) ; @@ -172,25 +177,24 @@ class Thematic return $thematic; } - /** - * Delete logically (set active field to 0) a thematic. - * - * @param int|array $thematicId One or many thematic ids - * - * @return void - */ public function delete(int|array $thematicId): void { $repo = Container::getThematicRepository(); + $linksRepo = Container::$container->get(ResourceLinkRepository::class); + + $course = api_get_course_entity(); + $session = api_get_session_entity(); if (is_array($thematicId)) { foreach ($thematicId as $id) { + /** @var CThematic $resource */ $resource = $repo->find($id); - $repo->delete($resource); + $linksRepo->removeByResourceInContext($resource, $course, $session); } } else { + /** @var CThematic $resource */ $resource = $repo->find($thematicId); - $repo->delete($resource); + $linksRepo->removeByResourceInContext($resource, $course, $session); }; } diff --git a/public/main/inc/lib/tracking.lib.php b/public/main/inc/lib/tracking.lib.php index 0c71c2fa91..bef53dfde0 100644 --- a/public/main/inc/lib/tracking.lib.php +++ b/public/main/inc/lib/tracking.lib.php @@ -5690,7 +5690,7 @@ class Tracking api_get_user_id(), ['real_id' => $courseId], $sessionId, - 'lp.publishedOn ASC', + 'resource.publishedOn ASC', true, null, true @@ -6931,7 +6931,7 @@ class Tracking $userId, $courseInfo, 0, - 'lp.publishedOn ASC', + 'resource.publishedOn ASC', true, null, true @@ -7871,7 +7871,7 @@ class Tracking api_get_user_id(), $courseInfo, $sessionId, - 'lp.publishedOn ASC', + 'resource.publishedOn ASC', true, null, true diff --git a/public/main/inc/lib/webservices/Rest.php b/public/main/inc/lib/webservices/Rest.php index 1acdac6200..3bb6a097e0 100644 --- a/public/main/inc/lib/webservices/Rest.php +++ b/public/main/inc/lib/webservices/Rest.php @@ -821,7 +821,6 @@ class Rest extends WebService $categoryNone = new CLpCategory(); $categoryNone->setTitle(get_lang('WithOutCategory')); - $categoryNone->setPosition(0); $categories = array_merge([$categoryNone], $categoriesTempList); $categoryData = []; diff --git a/public/main/lp/learnpath.class.php b/public/main/lp/learnpath.class.php index 311aee5dda..f708f027c1 100644 --- a/public/main/lp/learnpath.class.php +++ b/public/main/lp/learnpath.class.php @@ -3,6 +3,7 @@ /* For licensing terms, see /license.txt */ use Chamilo\CoreBundle\Entity\Course; +use Chamilo\CoreBundle\Entity\ResourceLink; use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Entity\Session as SessionEntity; use Chamilo\CourseBundle\Entity\CLpRelUser; @@ -603,7 +604,6 @@ class learnpath $dsp = $row[0] + 1; }*/ - $dsp = 1; $category = null; if (!empty($categoryId)) { $category = Container::getLpCategoryRepository()->find($categoryId); @@ -615,7 +615,6 @@ class learnpath ->setLpType($type) ->setTitle($name) ->setDescription($description) - ->setDisplayOrder($dsp) ->setCategory($category) ->setPublishedOn($published_on) ->setExpiredOn($expired_on) @@ -787,39 +786,40 @@ class learnpath return false; } - $lp_item = Database::get_course_table(TABLE_LP_ITEM); - $lp_view = Database::get_course_table(TABLE_LP_VIEW); - $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $course = api_get_course_entity(); + $session = api_get_session_entity(); + + //$lp_item = Database::get_course_table(TABLE_LP_ITEM); + //$lp_view = Database::get_course_table(TABLE_LP_VIEW); + //$lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); // Delete lp item id. - foreach ($this->items as $lpItemId => $dummy) { - $sql = "DELETE FROM $lp_item_view - WHERE lp_item_id = '".$lpItemId."'"; - Database::query($sql); - } + //foreach ($this->items as $lpItemId => $dummy) { + // $sql = "DELETE FROM $lp_item_view + // WHERE lp_item_id = '".$lpItemId."'"; + // Database::query($sql); + //} // Proposed by Christophe (nickname: clefevre) - $sql = "DELETE FROM $lp_item - WHERE lp_id = ".$this->lp_id; - Database::query($sql); + //$sql = "DELETE FROM $lp_item + // WHERE lp_id = ".$this->lp_id; + //Database::query($sql); - $sql = "DELETE FROM $lp_view - WHERE lp_id = ".$this->lp_id; - Database::query($sql); + //$sql = "DELETE FROM $lp_view + // WHERE lp_id = ".$this->lp_id; + //Database::query($sql); - $table = Database::get_course_table(TABLE_LP_REL_USERGROUP); - $sql = "DELETE FROM $table - WHERE - lp_id = {$this->lp_id}"; - Database::query($sql); + //$table = Database::get_course_table(TABLE_LP_REL_USERGROUP); + //$sql = "DELETE FROM $table + // WHERE + // lp_id = {$this->lp_id}"; + //Database::query($sql); - $repo = Container::getLpRepository(); - $lp = $repo->find($this->lp_id); - Database::getManager()->remove($lp); - Database::getManager()->flush(); + $lp = Container::getLpRepository()->find($this->lp_id); - // Updates the display order of all lps. - $this->update_display_order(); + Database::getManager() + ->getRepository(ResourceLink::class) + ->removeByResourceInContext($lp, $course, $session); $link_info = GradebookUtils::isResourceInCourseGradebook( api_get_course_id(), @@ -832,9 +832,9 @@ class learnpath GradebookUtils::remove_resource_from_course_gradebook($link_info['id']); } - if ('true' === api_get_setting('search_enabled')) { - delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id); - } + //if ('true' === api_get_setting('search_enabled')) { + // delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id); + //} } /** @@ -7138,37 +7138,41 @@ class learnpath } } - /** - * @param int $id - */ - public static function moveUpCategory($id) + public static function moveUpCategory(int $id): void { - $id = (int) $id; $em = Database::getManager(); /** @var CLpCategory $item */ $item = $em->find(CLpCategory::class, $id); if ($item) { - $position = $item->getPosition() - 1; - $item->setPosition($position); - $em->persist($item); - $em->flush(); + $course = api_get_course_entity(); + $session = api_get_session_entity(); + + $link = $item->resourceNode->getResourceLinkByContext($course, $session); + + if ($link) { + $link->moveUpPosition(); + + $em->flush(); + } } } - /** - * @param int $id - */ - public static function moveDownCategory($id) + public static function moveDownCategory(int $id): void { - $id = (int) $id; $em = Database::getManager(); /** @var CLpCategory $item */ $item = $em->find(CLpCategory::class, $id); if ($item) { - $position = $item->getPosition() + 1; - $item->setPosition($position); - $em->persist($item); - $em->flush(); + $course = api_get_course_entity(); + $session = api_get_session_entity(); + + $link = $item->resourceNode->getResourceLinkByContext($course, $session); + + if ($link) { + $link->moveDownPosition(); + + $em->flush(); + } } } @@ -7198,7 +7202,7 @@ class learnpath { // Using doctrine extensions $repo = Container::getLpCategoryRepository(); - $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity()); + $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity(), null, null, true, true); return $qb->getQuery()->getResult(); } @@ -7222,10 +7226,7 @@ class learnpath return $sessionId; } - /** - * @param int $id - */ - public static function deleteCategory($id): bool + public static function deleteCategory(int $id): bool { $repo = Container::getLpCategoryRepository(); /** @var CLpCategory $category */ @@ -7236,12 +7237,14 @@ class learnpath foreach ($lps as $lp) { $lp->setCategory(null); - $em->persist($lp); } - // Removing category. - $em->remove($category); - $em->flush(); + $em->persist($lp); + + $course = api_get_course_entity(); + $session = api_get_session_entity(); + + $em->getRepository(ResourceLink::class)->removeByResourceInContext($category, $course, $session); return true; } @@ -8616,21 +8619,20 @@ class learnpath /** @var CLp $lp */ $lp = Container::getLpRepository()->find($lpId); if ($lp) { - $resourceNode = $lp->getResourceNode(); - $position = $resourceNode->getDisplayOrder(); - $resourceNodeId = $resourceNode->getId(); + $course = api_get_course_entity(); + $session = api_get_session_entity(); + $group = api_get_group_entity(); - $item = $em->find(ResourceNode::class, $resourceNodeId); - if ($item) { - $newPosition = 0; + $link = $lp->getResourceNode()->getResourceLinkByContext($course, $session, $group); + + if ($link) { if ('down' === $direction) { - $newPosition = $position + 1; + $link->moveDownPosition(); } if ('up' === $direction) { - $newPosition = $position - 1; + $link->moveUpPosition(); } - $item->setDisplayOrder($newPosition); - $em->persist($item); + $em->flush(); } } diff --git a/public/main/lp/learnpathList.class.php b/public/main/lp/learnpathList.class.php index 38e34ac988..a9c97c13a9 100644 --- a/public/main/lp/learnpathList.class.php +++ b/public/main/lp/learnpathList.class.php @@ -55,16 +55,15 @@ class LearnpathList $course_id = $courseInfo['real_id']; $this->user_id = $user_id; - $order = ' ORDER BY lp.displayOrder ASC, lp.title ASC'; - if (isset($order_by)) { - $order = Database::parse_conditions(['order' => $order_by]); - } - $repo = Container::getLpRepository(); $course = api_get_course_entity($course_id); $session = api_get_session_entity($session_id); - $qb = $repo->getResourcesByCourse($course, $session); + $qb = $repo->getResourcesByCourse($course, $session, null, null, true, true); + + if (!empty($order_by)) { + $qb->addOrderBy($order_by); + } $now = api_get_utc_datetime(); if ($check_publication_dates) { @@ -94,7 +93,6 @@ class LearnpathList $order ";*/ //$learningPaths = Database::getManager()->createQuery($dql)->getResult(); - $qb->addOrderBy('node.displayOrder', 'ASC'); $showBlockedPrerequisite = ('true' === api_get_setting('lp.show_prerequisite_as_blocked')); $names = []; @@ -138,6 +136,8 @@ class LearnpathList } $link = $lp->getFirstResourceLink(); + $resourceNode = $lp->getResourceNode(); + $this->list[$lp->getIid()] = [ 'lp_type' => $lp->getLpType(), 'lp_session' => $link && $link->getSession() ? (int) $link->getSession()->getId() : 0, @@ -154,7 +154,6 @@ class LearnpathList 'lp_prevent_reinit' => $lp->getPreventReinit(), 'seriousgame_mode' => $lp->getSeriousgameMode(), 'lp_scorm_debug' => $lp->getDebug(), - 'lp_display_order' => $lp->getResourceNode()->getDisplayOrder() + 1, 'autolaunch' => $lp->getAutolaunch(), 'created_on' => $lp->getCreatedOn() ? $lp->getCreatedOn()->format('Y-m-d H:i:s') : null, 'modified_on' => $lp->getModifiedOn() ? $lp->getModifiedOn()->format('Y-m-d H:i:s') : null, diff --git a/public/main/lp/lp_controller.php b/public/main/lp/lp_controller.php index 99abebe51f..e2b6c1204d 100644 --- a/public/main/lp/lp_controller.php +++ b/public/main/lp/lp_controller.php @@ -440,7 +440,7 @@ switch ($action) { api_not_allowed(true); } if (isset($_REQUEST['id'])) { - learnpath::moveUpCategory($_REQUEST['id']); + learnpath::moveUpCategory((int) $_REQUEST['id']); } require 'lp_list.php'; break; @@ -449,7 +449,7 @@ switch ($action) { api_not_allowed(true); } if (isset($_REQUEST['id'])) { - learnpath::moveDownCategory($_REQUEST['id']); + learnpath::moveDownCategory((int) $_REQUEST['id']); } require 'lp_list.php'; break; @@ -458,7 +458,7 @@ switch ($action) { api_not_allowed(true); } if (isset($_REQUEST['id'])) { - $result = learnpath::deleteCategory($_REQUEST['id']); + $result = learnpath::deleteCategory((int) $_REQUEST['id']); if ($result) { Display::addFlash(Display::return_message(get_lang('Deleted'))); } diff --git a/public/main/lp/lp_list.php b/public/main/lp/lp_list.php index 4197dd06ba..d84da41640 100644 --- a/public/main/lp/lp_list.php +++ b/public/main/lp/lp_list.php @@ -111,7 +111,6 @@ if ($allowCategory) { $categoryTest = new CLpCategory(); $categoryTest->setTitle(get_lang('Without category')); -$categoryTest->setPosition(0); $categories = [$categoryTest]; if (!empty($categoriesTempList)) { @@ -708,7 +707,7 @@ foreach ($categories as $category) { /* COLUMN ORDER */ // Only active while session mode is not active if (0 == $sessionId) { - if (1 == $details['lp_display_order'] && 1 != $max) { + if (0 == $current && 1 != $max) { $dsp_order .= Display::url( Display::getMdiIcon('arrow-down-bold', 'ch-tool-icon', '', 22), "lp_controller.php?$cidReq&action=move_lp_down&lp_id=$id&category_id=$categoryId", diff --git a/public/main/lp/scorm.class.php b/public/main/lp/scorm.class.php index 97dc23c647..7912b6c9b9 100644 --- a/public/main/lp/scorm.class.php +++ b/public/main/lp/scorm.class.php @@ -267,7 +267,6 @@ class scorm extends learnpath $row = Database::fetch_array($res_max); $dsp = $row[0] + 1; }*/ - $dsp = 1; $name = $oOrganization->get_name(); $lp = (new CLp()) @@ -277,7 +276,6 @@ class scorm extends learnpath ->setPath($this->subdir) ->setDefaultEncoding($this->manifest_encoding) ->setJsLib('scorm_api.php') - ->setDisplayOrder($dsp) ->setUseMaxScore($userMaxScore) ->setAsset($this->asset) ->setParent($course) diff --git a/public/main/my_space/myStudents.php b/public/main/my_space/myStudents.php index d50ad2a101..3a48aae476 100644 --- a/public/main/my_space/myStudents.php +++ b/public/main/my_space/myStudents.php @@ -1607,7 +1607,6 @@ if (empty($details)) { $categoriesTempList = learnpath::getCategories($courseId); $categoryTest = new CLpCategory(); $categoryTest->setTitle(get_lang('Without category')); - $categoryTest->setPosition(0); $categories = [ $categoryTest, ]; diff --git a/public/main/session/index.php b/public/main/session/index.php index ff45eb45fe..0a892bff20 100644 --- a/public/main/session/index.php +++ b/public/main/session/index.php @@ -115,7 +115,7 @@ if (!empty($courseList)) { api_get_user_id(), api_get_course_info($course_data['code']), $session_id, - 'lp.publishedOn ASC', + 'resource.publishedOn ASC', true, null, true diff --git a/public/plugin/customcertificate/src/export_pdf_all_in_one.php b/public/plugin/customcertificate/src/export_pdf_all_in_one.php index 2b55849fb5..b8e7f6ef9c 100644 --- a/public/plugin/customcertificate/src/export_pdf_all_in_one.php +++ b/public/plugin/customcertificate/src/export_pdf_all_in_one.php @@ -485,7 +485,6 @@ foreach ($userList as $userInfo) { $categoryTest = new CLpCategory(); $categoryTest->setId(0); $categoryTest->setTitle($plugin->get_lang('WithOutCategory')); - $categoryTest->setPosition(0); $categories = [$categoryTest]; if (!empty($categoriesTempList)) { diff --git a/public/plugin/customcertificate/src/print_certificate.php b/public/plugin/customcertificate/src/print_certificate.php index 3fc48369c4..2bf24094da 100644 --- a/public/plugin/customcertificate/src/print_certificate.php +++ b/public/plugin/customcertificate/src/print_certificate.php @@ -372,7 +372,6 @@ foreach ($userList as $userInfo) { $categoryTest = new CLpCategory(); $categoryTest->setId(0); $categoryTest->setTitle($plugin->get_lang('WithOutCategory')); - $categoryTest->setPosition(0); $categories = [$categoryTest]; if (!empty($categoriesTempList)) { diff --git a/src/CoreBundle/Controller/Api/GetLinksCollectionController.php b/src/CoreBundle/Controller/Api/GetLinksCollectionController.php index bf1319fdc5..0b8359369d 100644 --- a/src/CoreBundle/Controller/Api/GetLinksCollectionController.php +++ b/src/CoreBundle/Controller/Api/GetLinksCollectionController.php @@ -43,6 +43,8 @@ class GetLinksCollectionController extends BaseResourceFileAction if ($links) { /** @var CLink $link */ foreach ($links as $link) { + $resourceNode = $link->getResourceNode(); + $dataResponse['linksWithoutCategory'][] = [ 'id' => $link->getIid(), @@ -51,7 +53,7 @@ class GetLinksCollectionController extends BaseResourceFileAction 'url' => $link->getUrl(), 'iid' => $link->getIid(), 'linkVisible' => $link->getFirstResourceLink()->getVisibility(), - 'position' => $link->getResourceNode()->getDisplayOrder(), + 'position' => $resourceNode->getResourceLinkByContext($course, $session)?->getDisplayOrder(), ]; } } @@ -78,6 +80,8 @@ class GetLinksCollectionController extends BaseResourceFileAction /** @var CLink $link */ foreach ($links as $link) { + $resourceNode = $link->getResourceNode(); + $items[] = [ 'id' => $link->getIid(), 'title' => $link->getTitle(), @@ -85,7 +89,7 @@ class GetLinksCollectionController extends BaseResourceFileAction 'url' => $link->getUrl(), 'iid' => $link->getIid(), 'linkVisible' => $link->getFirstResourceLink()->getVisibility(), - 'position' => $link->getResourceNode()->getDisplayOrder(), + 'position' => $resourceNode->getResourceLinkByContext($course, $session)?->getDisplayOrder(), ]; $dataResponse['categories'][$categoryId]['links'] = $items; diff --git a/src/CoreBundle/Controller/Api/UpdatePositionLink.php b/src/CoreBundle/Controller/Api/UpdatePositionLink.php index d47a843b5b..3996a7f150 100644 --- a/src/CoreBundle/Controller/Api/UpdatePositionLink.php +++ b/src/CoreBundle/Controller/Api/UpdatePositionLink.php @@ -23,7 +23,7 @@ class UpdatePositionLink extends AbstractController $resourceNode = $link->getResourceNode(); if ($resourceNode) { - $resourceNode->setDisplayOrder($newPosition); + // $resourceNode->setDisplayOrder($newPosition); $em->flush(); } diff --git a/src/CoreBundle/DataProvider/Extension/CToolExtension.php b/src/CoreBundle/DataProvider/Extension/CToolExtension.php index c7b2d60d32..04ea59be9a 100644 --- a/src/CoreBundle/DataProvider/Extension/CToolExtension.php +++ b/src/CoreBundle/DataProvider/Extension/CToolExtension.php @@ -9,7 +9,6 @@ namespace Chamilo\CoreBundle\DataProvider\Extension; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Operation; -use Chamilo\CoreBundle\Entity\ResourceLink; use Chamilo\CoreBundle\Traits\ControllerTrait; use Chamilo\CoreBundle\Traits\CourseControllerTrait; use Chamilo\CourseBundle\Entity\CTool; @@ -39,8 +38,6 @@ class CToolExtension implements QueryCollectionExtensionInterface ->andWhere( $queryBuilder->expr()->notIn("$alias.title", ['course_tool', 'course_homepage']) ) - ->andWhere('resource_links.visibility != :visibility_deleted') - ->setParameter('visibility_deleted', ResourceLink::VISIBILITY_DELETED) ; } } diff --git a/src/CoreBundle/DataProvider/Extension/CourseLinkExtensionTrait.php b/src/CoreBundle/DataProvider/Extension/CourseLinkExtensionTrait.php index 54256ae451..4a725f230a 100644 --- a/src/CoreBundle/DataProvider/Extension/CourseLinkExtensionTrait.php +++ b/src/CoreBundle/DataProvider/Extension/CourseLinkExtensionTrait.php @@ -37,12 +37,6 @@ trait CourseLinkExtensionTrait protected function addVisibilityCondition(QueryBuilder $queryBuilder): void { - // Do not show deleted resources. - $queryBuilder - ->andWhere('links.visibility != :visibilityDeleted') - ->setParameter('visibilityDeleted', ResourceLink::VISIBILITY_DELETED) - ; - $allowDraft = $this->security->isGranted('ROLE_ADMIN') || $this->security->isGranted('ROLE_CURRENT_COURSE_TEACHER'); diff --git a/src/CoreBundle/DataProvider/Extension/PersonalFileExtension.php b/src/CoreBundle/DataProvider/Extension/PersonalFileExtension.php index 6483d42d40..bc5762f88f 100644 --- a/src/CoreBundle/DataProvider/Extension/PersonalFileExtension.php +++ b/src/CoreBundle/DataProvider/Extension/PersonalFileExtension.php @@ -71,11 +71,6 @@ final class PersonalFileExtension implements QueryCollectionExtensionInterface / if ($isShared) { $queryBuilder->leftJoin('node.resourceLinks', 'links'); - /*$queryBuilder - ->andWhere('links.visibility != :visibilityDeleted') - ->setParameter('visibilityDeleted', ResourceLink::VISIBILITY_DELETED) - ;*/ - $queryBuilder ->andWhere('links.visibility = :visibility') ->setParameter('visibility', ResourceLink::VISIBILITY_PUBLISHED) diff --git a/src/CoreBundle/Entity/ResourceLink.php b/src/CoreBundle/Entity/ResourceLink.php index 9f5ae0a711..6c5c7071c5 100644 --- a/src/CoreBundle/Entity/ResourceLink.php +++ b/src/CoreBundle/Entity/ResourceLink.php @@ -7,27 +7,31 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Entity; use ApiPlatform\Metadata\ApiResource; +use Chamilo\CoreBundle\Repository\ResourceLinkRepository; use Chamilo\CoreBundle\Traits\TimestampableTypedEntity; use Chamilo\CourseBundle\Entity\CGroup; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Gedmo\Mapping\Annotation as Gedmo; +use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity; use LogicException; use Stringable; use Symfony\Component\Serializer\Annotation\Groups; #[ApiResource] #[ORM\Table(name: 'resource_link')] -#[ORM\Entity] +#[ORM\Entity(repositoryClass: ResourceLinkRepository::class)] +#[Gedmo\SoftDeleteable(fieldName: 'deletedAt', timeAware: false, hardDelete: true)] class ResourceLink implements Stringable { + use SoftDeleteableEntity; use TimestampableTypedEntity; public const VISIBILITY_DRAFT = 0; public const VISIBILITY_PENDING = 1; public const VISIBILITY_PUBLISHED = 2; - public const VISIBILITY_DELETED = 3; #[ORM\Id] #[ORM\Column(type: 'integer')] @@ -38,22 +42,27 @@ class ResourceLink implements Stringable #[ORM\JoinColumn(name: 'resource_node_id', referencedColumnName: 'id', onDelete: 'CASCADE')] protected ResourceNode $resourceNode; + #[Gedmo\SortableGroup] #[ORM\ManyToOne(targetEntity: Course::class)] #[ORM\JoinColumn(name: 'c_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] protected ?Course $course = null; + #[Gedmo\SortableGroup] #[ORM\ManyToOne(targetEntity: Session::class, inversedBy: 'resourceLinks')] #[ORM\JoinColumn(name: 'session_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] protected ?Session $session = null; - #[ORM\ManyToOne(targetEntity: CGroup::class)] - #[ORM\JoinColumn(name: 'group_id', referencedColumnName: 'iid', nullable: true, onDelete: 'CASCADE')] - protected ?CGroup $group = null; - + #[Gedmo\SortableGroup] #[ORM\ManyToOne(targetEntity: Usergroup::class)] #[ORM\JoinColumn(name: 'usergroup_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] protected ?Usergroup $userGroup = null; + #[Gedmo\SortableGroup] + #[ORM\ManyToOne(targetEntity: CGroup::class)] + #[ORM\JoinColumn(name: 'group_id', referencedColumnName: 'iid', nullable: true, onDelete: 'CASCADE')] + protected ?CGroup $group = null; + + #[Gedmo\SortableGroup] #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')] protected ?User $user = null; @@ -85,6 +94,14 @@ class ResourceLink implements Stringable #[ORM\Column(name: 'end_visibility_at', type: 'datetime', nullable: true)] protected ?DateTimeInterface $endVisibilityAt = null; + #[Gedmo\SortablePosition] + #[ORM\Column] + private int $displayOrder; + + #[Gedmo\SortableGroup] + #[ORM\Column] + private ?int $resourceTypeGroup; + public function __construct() { $this->resourceRights = new ArrayCollection(); @@ -277,7 +294,6 @@ class ResourceLink implements Stringable 'Draft' => self::VISIBILITY_DRAFT, 'Pending' => self::VISIBILITY_PENDING, 'Published' => self::VISIBILITY_PUBLISHED, - 'Deleted' => self::VISIBILITY_DELETED, ]; } @@ -295,4 +311,42 @@ class ResourceLink implements Stringable { return array_flip(static::getVisibilityList())[$this->getVisibility()]; } + + public function getDisplayOrder(): int + { + return $this->displayOrder; + } + + public function setDisplayOrder(int $displayOrder): static + { + $this->displayOrder = $displayOrder; + + return $this; + } + + public function getResourceTypeGroup(): ?int + { + return $this->resourceTypeGroup; + } + + public function setResourceTypeGroup(int $resourceTypeGroup): static + { + $this->resourceTypeGroup = $resourceTypeGroup; + + return $this; + } + + public function moveUpPosition(): static + { + $this->displayOrder--; + + return $this; + } + + public function moveDownPosition(): static + { + $this->displayOrder++; + + return $this; + } } diff --git a/src/CoreBundle/Entity/ResourceNode.php b/src/CoreBundle/Entity/ResourceNode.php index b36de8c3f5..aa6b73d8ec 100644 --- a/src/CoreBundle/Entity/ResourceNode.php +++ b/src/CoreBundle/Entity/ResourceNode.php @@ -20,10 +20,12 @@ use Chamilo\CoreBundle\Entity\Listener\ResourceNodeListener; use Chamilo\CoreBundle\Repository\ResourceNodeRepository; use Chamilo\CoreBundle\Traits\TimestampableAgoTrait; use Chamilo\CoreBundle\Traits\TimestampableTypedEntity; +use Chamilo\CourseBundle\Entity\CGroup; use Chamilo\CourseBundle\Entity\CShortcut; use DateTime; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; use InvalidArgumentException; @@ -97,7 +99,6 @@ class ResourceNode implements Stringable #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: ResourceType::class, inversedBy: 'resourceNodes')] #[ORM\JoinColumn(name: 'resource_type_id', referencedColumnName: 'id', nullable: false)] - #[Gedmo\SortableGroup] protected ResourceType $resourceType; #[ORM\ManyToOne(targetEntity: ResourceFormat::class, inversedBy: 'resourceNodes')] @@ -130,7 +131,6 @@ class ResourceNode implements Stringable #[ORM\JoinColumn(name: 'parent_id', onDelete: 'CASCADE')] #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[Gedmo\TreeParent] - #[Gedmo\SortableGroup] protected ?ResourceNode $parent = null; /** @@ -189,10 +189,6 @@ class ResourceNode implements Stringable #[ORM\Column(type: 'uuid', unique: true)] protected ?UuidV4 $uuid = null; - #[ORM\Column(name: 'display_order', type: 'integer', nullable: false)] - #[Gedmo\SortablePosition] - protected int $displayOrder; - public function __construct() { $this->public = false; @@ -202,7 +198,6 @@ class ResourceNode implements Stringable $this->comments = new ArrayCollection(); $this->createdAt = new DateTime(); $this->fileEditableText = false; - $this->displayOrder = 0; } public function __toString(): string @@ -414,6 +409,57 @@ class ResourceNode implements Stringable return $this->resourceLinks; } + public function getResourceLinkByContext( + ?Course $course = null, + ?Session $session = null, + ?CGroup $group = null, + ?Usergroup $usergroup = null, + ?User $user = null, + ): ?ResourceLink { + $criteria = Criteria::create(); + $criteria->where( + Criteria::expr()->eq('resourceTypeGroup', $this->resourceType->getId()) + ); + + if ($course) { + $criteria->andWhere( + Criteria::expr()->eq('course', $course) + ); + } + + if ($session) { + $criteria->andWhere( + Criteria::expr()->eq('session', $session) + ); + } + + if ($usergroup) { + $criteria->andWhere( + Criteria::expr()->eq('userGroup', $usergroup) + ); + } + + if ($group) { + $criteria->andWhere( + Criteria::expr()->eq('group', $group) + ); + } + + if ($user) { + $criteria->andWhere( + Criteria::expr()->eq('user', $user) + ); + } + + $first = $this + ->resourceLinks + ->matching($criteria) + ->first() + ; + + return $first ?: null; + } + public function setResourceLinks(Collection $resourceLinks): self { $this->resourceLinks = $resourceLinks; @@ -423,7 +469,10 @@ class ResourceNode implements Stringable public function addResourceLink(ResourceLink $link): self { - $link->setResourceNode($this); + $link + ->setResourceNode($this) + ->setResourceTypeGroup($this->resourceType->getId()) + ; $this->resourceLinks->add($link); return $this; @@ -582,16 +631,4 @@ class ResourceNode implements Stringable return $this; } - - public function getDisplayOrder(): int - { - return $this->displayOrder; - } - - public function setDisplayOrder(int $displayOrder): self - { - $this->displayOrder = $displayOrder; - - return $this; - } } diff --git a/src/CoreBundle/Migrations/AbstractMigrationChamilo.php b/src/CoreBundle/Migrations/AbstractMigrationChamilo.php index 59f3b39304..2933c3e7c2 100644 --- a/src/CoreBundle/Migrations/AbstractMigrationChamilo.php +++ b/src/CoreBundle/Migrations/AbstractMigrationChamilo.php @@ -9,8 +9,10 @@ namespace Chamilo\CoreBundle\Migrations; use Chamilo\CoreBundle\Entity\AbstractResource; use Chamilo\CoreBundle\Entity\AccessUrl; use Chamilo\CoreBundle\Entity\Admin; +use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\ResourceInterface; use Chamilo\CoreBundle\Entity\ResourceLink; +use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Entity\SettingsCurrent; use Chamilo\CoreBundle\Entity\SettingsOptions; use Chamilo\CoreBundle\Entity\User; @@ -18,6 +20,8 @@ use Chamilo\CoreBundle\Repository\Node\UserRepository; use Chamilo\CoreBundle\Repository\ResourceRepository; use Chamilo\CoreBundle\Repository\SessionRepository; use Chamilo\CourseBundle\Repository\CGroupRepository; +use DateTime; +use DateTimeZone; use Doctrine\Migrations\AbstractMigration; use Doctrine\ORM\EntityManager; use Symfony\Component\DependencyInjection\ContainerAwareInterface; @@ -260,6 +264,7 @@ abstract class AbstractMigrationChamilo extends AbstractMigration implements Con $userId = (int) $item['insert_user_id']; $sessionId = $item['session_id'] ?? 0; $groupId = $item['to_group_id'] ?? 0; + $lastUpdatedAt = new DateTime($item['lastedit_date'], new DateTimeZone('UTC')); $newVisibility = ResourceLink::VISIBILITY_DRAFT; @@ -273,11 +278,6 @@ abstract class AbstractMigrationChamilo extends AbstractMigration implements Con case 1: $newVisibility = ResourceLink::VISIBILITY_PUBLISHED; - break; - - case 2: - $newVisibility = ResourceLink::VISIBILITY_DELETED; - break; } @@ -321,6 +321,12 @@ abstract class AbstractMigrationChamilo extends AbstractMigration implements Con $em->persist($resourceNode); } $resource->addCourseLink($course, $session, $group, $newVisibility); + + if (2 === $visibility) { + $link = $resource->getResourceNode()->getResourceLinkByContext($course, $session, $group); + $link->setDeletedAt($lastUpdatedAt); + } + $em->persist($resource); } @@ -331,4 +337,22 @@ abstract class AbstractMigrationChamilo extends AbstractMigration implements Con { return file_exists($filePath) && !is_dir($filePath) && is_readable($filePath); } + + public function findCourse(int $id): ?Course + { + if (0 === $id) { + return null; + } + + return $this->getEntityManager()->find(Course::class, $id); + } + + public function findSession(int $id): ?Session + { + if (0 === $id) { + return null; + } + + return $this->getEntityManager()->find(Session::class, $id); + } } diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20170525122900.php b/src/CoreBundle/Migrations/Schema/V200/Version20170525122900.php index 1dcecf5f7b..6918d27981 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20170525122900.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20170525122900.php @@ -33,14 +33,11 @@ class Version20170525122900 extends AbstractMigrationChamilo ); $this->addSql('CREATE UNIQUE INDEX UNIQ_8A5F48FFD17F50A6 ON resource_node (uuid)'); $this->addSql('ALTER TABLE resource_node ADD public TINYINT(1) NOT NULL'); - $this->addSql( - 'ALTER TABLE resource_node ADD display_order INT NOT NULL' - ); } if (!$schema->hasTable('resource_link')) { $this->addSql( - "CREATE TABLE resource_link (id INT AUTO_INCREMENT NOT NULL, resource_node_id INT DEFAULT NULL, session_id INT DEFAULT NULL, user_id INT DEFAULT NULL, c_id INT DEFAULT NULL, group_id INT DEFAULT NULL, usergroup_id INT DEFAULT NULL, visibility INT NOT NULL, start_visibility_at DATETIME DEFAULT NULL, end_visibility_at DATETIME DEFAULT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime)', updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetime)', INDEX IDX_398C394B1BAD783F (resource_node_id), INDEX IDX_398C394B613FECDF (session_id), INDEX IDX_398C394BA76ED395 (user_id), INDEX IDX_398C394B91D79BD3 (c_id), INDEX IDX_398C394BFE54D947 (group_id), INDEX IDX_398C394BD2112630 (usergroup_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT = DYNAMIC;" + "CREATE TABLE resource_link (id INT AUTO_INCREMENT NOT NULL, resource_node_id INT DEFAULT NULL, c_id INT DEFAULT NULL, session_id INT DEFAULT NULL, usergroup_id INT DEFAULT NULL, group_id INT DEFAULT NULL, user_id INT DEFAULT NULL, visibility INT NOT NULL, start_visibility_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime)', end_visibility_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime)', display_order INT NOT NULL, resource_type_group INT NOT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime)', updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetime)', deleted_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime)', INDEX IDX_398C394B1BAD783F (resource_node_id), INDEX IDX_398C394B91D79BD3 (c_id), INDEX IDX_398C394B613FECDF (session_id), INDEX IDX_398C394BD2112630 (usergroup_id), INDEX IDX_398C394BFE54D947 (group_id), INDEX IDX_398C394BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB ROW_FORMAT = DYNAMIC;" ); } diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20230615213500.php b/src/CoreBundle/Migrations/Schema/V200/Version20230615213500.php index 5c1501cc20..64f6ecc88f 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20230615213500.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20230615213500.php @@ -7,9 +7,7 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Migrations\Schema\V200; use Chamilo\CoreBundle\Entity\Course; -use Chamilo\CoreBundle\Entity\ResourceNode; use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo; -use Chamilo\CoreBundle\Repository\Node\CourseRepository; use Chamilo\CourseBundle\Entity\CLp; use Chamilo\CourseBundle\Repository\CLpRepository; use Doctrine\DBAL\Connection; @@ -32,7 +30,6 @@ final class Version20230615213500 extends AbstractMigrationChamilo $connection = $em->getConnection(); $lpRepo = $container->get(CLpRepository::class); - $courseRepo = $container->get(CourseRepository::class); $q = $em->createQuery('SELECT c FROM Chamilo\CoreBundle\Entity\Course c'); @@ -52,13 +49,15 @@ final class Version20230615213500 extends AbstractMigrationChamilo $resource = $lpRepo->find($lpId); if ($resource->hasResourceNode()) { $resourceNode = $resource->getResourceNode(); - $resourceNodeId = $resourceNode->getId(); - $item = $em->find(ResourceNode::class, $resourceNodeId); - if ($item) { - $item->setDisplayOrder($position); - $em->persist($item); - } + $course = $this->findCourse((int) $lp['c_id']); + $session = $this->findSession((int) ($lp['session_id'] ?? 0)); + + $link = $resourceNode->getResourceLinkByContext($course, $session); + + $link?->setDisplayOrder( + $position > 0 ? $position - 1 : 0 + ); } } } diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20240112191200.php b/src/CoreBundle/Migrations/Schema/V200/Version20240112191200.php index 8a2006d4af..4f73ff99c5 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20240112191200.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20240112191200.php @@ -6,6 +6,7 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Migrations\Schema\V200; +use Chamilo\CoreBundle\Entity\AbstractResource; use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo; use Chamilo\CourseBundle\Repository\CAnnouncementRepository; use Chamilo\CourseBundle\Repository\CGlossaryRepository; @@ -14,7 +15,6 @@ use Chamilo\CourseBundle\Repository\CLinkCategoryRepository; use Chamilo\CourseBundle\Repository\CLinkRepository; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; -use Exception; final class Version20240112191200 extends AbstractMigrationChamilo { @@ -38,44 +38,53 @@ final class Version20240112191200 extends AbstractMigrationChamilo $glossaryRepo = $container->get(CGlossaryRepository::class); $announcementRepo = $container->get(CAnnouncementRepository::class); - $this->updateResourceNodeDisplayOrder($linkCategoryRepo, 'c_link_category', $em); - $this->updateResourceNodeDisplayOrder($linkRepo, 'c_link', $em); - $this->updateResourceNodeDisplayOrder($groupCategoryRepo, 'c_group_category', $em); - $this->updateResourceNodeDisplayOrder($glossaryRepo, 'c_glossary', $em); - $this->updateResourceNodeDisplayOrder($announcementRepo, 'c_announcement', $em); + $this->updateResourceNodeDisplayOrder($linkCategoryRepo, 'c_link_category', $em, $schema); + $this->updateResourceNodeDisplayOrder($linkRepo, 'c_link', $em, $schema); + $this->updateResourceNodeDisplayOrder($groupCategoryRepo, 'c_group_category', $em, $schema); + $this->updateResourceNodeDisplayOrder($glossaryRepo, 'c_glossary', $em, $schema); + $this->updateResourceNodeDisplayOrder($announcementRepo, 'c_announcement', $em, $schema); } - private function updateResourceNodeDisplayOrder($resourceRepo, $tableName, $em): void + private function updateResourceNodeDisplayOrder($resourceRepo, $tableName, $em, Schema $schema): void { /** @var Connection $connection */ $connection = $em->getConnection(); - try { - $testResult = $connection->executeQuery("SELECT display_order FROM $tableName LIMIT 1"); - $columnExists = true; - } catch (Exception $e) { - $columnExists = false; + $table = $schema->getTable($tableName); + + if (!$table->hasColumn('display_order')) { + return; } - if ($columnExists) { - $sql = "SELECT * FROM $tableName ORDER BY display_order"; - $result = $connection->executeQuery($sql); - $resources = $result->fetchAllAssociative(); - - foreach ($resources as $resourceData) { - $resourceId = (int) $resourceData['iid']; - $resourcePosition = (int) $resourceData['display_order']; - - $resource = $resourceRepo->find($resourceId); - if ($resource && $resource->hasResourceNode()) { - $resourceNode = $resource->getResourceNode(); - if ($resourceNode) { - $resourceNode->setDisplayOrder($resourcePosition); - } - } + $sql = "SELECT * FROM $tableName ORDER BY display_order"; + $result = $connection->executeQuery($sql); + $resources = $result->fetchAllAssociative(); + + foreach ($resources as $resourceData) { + $resourceId = (int) $resourceData['iid']; + $resourcePosition = (int) $resourceData['display_order']; + + /** @var AbstractResource $resource */ + $resource = $resourceRepo->find($resourceId); + + if (!$resource || !$resource->hasResourceNode()) { + continue; } - $em->flush(); + $resourceNode = $resource->getResourceNode(); + + if ($resourceNode) { + $course = $this->findCourse((int) $resourceData['c_id']); + $session = $this->findSession((int) ($resourceData['session_id'] ?? 0)); + + $link = $resourceNode->getResourceLinkByContext($course, $session); + + $link?->setDisplayOrder( + $resourcePosition > 0 ? $resourcePosition - 1 : 0 + ); + } } + + $em->flush(); } } diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20240313111800.php b/src/CoreBundle/Migrations/Schema/V200/Version20240313111800.php new file mode 100644 index 0000000000..5312a045ef --- /dev/null +++ b/src/CoreBundle/Migrations/Schema/V200/Version20240313111800.php @@ -0,0 +1,42 @@ + 'display_order', + 'c_lp_category' => 'position', + 'c_forum_category' => 'cat_order', + 'c_forum_forum' => 'forum_order', + 'c_thematic' => 'display_order', + 'c_announcement' => 'display_order', + 'c_glossary' => 'display_order', + ]; + + public function getDescription(): string + { + $tables = array_keys($this->changes); + $columns = array_values($this->changes); + + return sprintf( + 'Removing %s columns from % tables', + implode(', ', $columns), + implode(', ', $tables) + ); + } + + public function up(Schema $schema): void + { + foreach ($this->changes as $table => $column) { + $this->addSql("ALTER TABLE $table DROP $column"); + } + } +} diff --git a/src/CoreBundle/Repository/ResourceLinkRepository.php b/src/CoreBundle/Repository/ResourceLinkRepository.php new file mode 100644 index 0000000000..99d3b8fb54 --- /dev/null +++ b/src/CoreBundle/Repository/ResourceLinkRepository.php @@ -0,0 +1,53 @@ +getClassMetadata(ResourceLink::class)); + } + + public function remove(ResourceLink $resourceLink): void + { + $em = $this->getEntityManager(); + + // To move the resource link at the end to reorder the list + $resourceLink->setDisplayOrder(-1); + + $em->flush(); + // soft delete handled by Gedmo\SoftDeleteable + $em->remove($resourceLink); + $em->flush(); + } + + public function removeByResourceInContext( + AbstractResource $resource, + Course $course, + ?Session $session = null, + ?CGroup $group = null, + ?Usergroup $usergroup = null, + ?User $user = null, + ): void { + $link = $resource->getResourceNode()->getResourceLinkByContext($course, $session, $group, $usergroup, $user); + + if ($link) { + $this->remove($link); + } + } +} diff --git a/src/CoreBundle/Repository/ResourceNodeRepository.php b/src/CoreBundle/Repository/ResourceNodeRepository.php index c40aaa1177..a344558ef5 100644 --- a/src/CoreBundle/Repository/ResourceNodeRepository.php +++ b/src/CoreBundle/Repository/ResourceNodeRepository.php @@ -8,7 +8,6 @@ namespace Chamilo\CoreBundle\Repository; use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\ResourceFile; -use Chamilo\CoreBundle\Entity\ResourceLink; use Chamilo\CoreBundle\Entity\ResourceNode; use Chamilo\CoreBundle\Entity\ResourceType; use Chamilo\CoreBundle\Entity\Session; @@ -145,7 +144,6 @@ class ResourceNodeRepository extends MaterializedPathRepository ->innerJoin('node.resourceLinks', 'l') ->where('node.resourceType = :type') ->andWhere('node.parent = :parentNode') - ->andWhere('l.visibility <> :visibility') ->andWhere('file IS NOT NULL') ; @@ -154,7 +152,6 @@ class ResourceNodeRepository extends MaterializedPathRepository $qb->andWhere('l.course = :course'); $params['course'] = $course; } - $params['visibility'] = ResourceLink::VISIBILITY_DELETED; $params['parentNode'] = $resourceNode; $params['type'] = $type; diff --git a/src/CoreBundle/Repository/ResourceRepository.php b/src/CoreBundle/Repository/ResourceRepository.php index d7db842457..cdabeeab55 100644 --- a/src/CoreBundle/Repository/ResourceRepository.php +++ b/src/CoreBundle/Repository/ResourceRepository.php @@ -282,12 +282,6 @@ abstract class ResourceRepository extends ServiceEntityRepository $checker->isGranted('ROLE_ADMIN') || $checker->isGranted('ROLE_CURRENT_COURSE_TEACHER'); - // Do not show deleted resources. - $qb - ->andWhere('links.visibility != :visibilityDeleted') - ->setParameter('visibilityDeleted', ResourceLink::VISIBILITY_DELETED, Types::INTEGER) - ; - if ($displayOnlyPublished) { if (!$isAdminOrTeacher || ($checkStudentView && 'studentview' === $sessionStudentView) @@ -397,7 +391,7 @@ abstract class ResourceRepository extends ServiceEntityRepository $this->addCourseSessionGroupQueryBuilder($course, $session, $group, $qb); if ($displayOrder) { - $qb->orderBy('node.displayOrder', 'ASC'); + $qb->orderBy('links.displayOrder', 'ASC'); } return $qb; @@ -605,14 +599,6 @@ abstract class ResourceRepository extends ServiceEntityRepository // } } - /** - * Change all links visibility to DELETED. - */ - public function softDelete(AbstractResource $resource): void - { - $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DELETED); - } - public function toggleVisibilityPublishedDraft(AbstractResource $resource): void { $firstLink = $resource->getFirstResourceLink(); @@ -638,11 +624,6 @@ abstract class ResourceRepository extends ServiceEntityRepository $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DRAFT); } - public function setVisibilityDeleted(AbstractResource $resource): void - { - $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DELETED); - } - public function setVisibilityPending(AbstractResource $resource): void { $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_PENDING); @@ -729,12 +710,10 @@ abstract class ResourceRepository extends ServiceEntityRepository ->innerJoin('node.resourceLinks', 'l') ->innerJoin('node.resourceFile', 'file') ->where('l.course = :course') - ->andWhere('l.visibility <> :visibility') ->andWhere('file IS NOT NULL') ->setParameters( [ 'course' => $course, - 'visibility' => ResourceLink::VISIBILITY_DELETED, ] ) ; diff --git a/src/CoreBundle/Resources/views/LearnPath/list.html.twig b/src/CoreBundle/Resources/views/LearnPath/list.html.twig index f3cff6233b..6c07581dd4 100644 --- a/src/CoreBundle/Resources/views/LearnPath/list.html.twig +++ b/src/CoreBundle/Resources/views/LearnPath/list.html.twig @@ -10,7 +10,6 @@ } } - {% set configuration = 1 %}