diff --git a/public/main/admin/course_category.php b/public/main/admin/course_category.php index f0d35fd8a78..bf86f4bf0fa 100644 --- a/public/main/admin/course_category.php +++ b/public/main/admin/course_category.php @@ -40,7 +40,9 @@ CourseCategory::reorganizeTreePos($parentId); Display::addFlash(Display::return_message(get_lang('Deleted'))); } - header('Location: '.api_get_self().'?category='.Security::remove_XSS($category)); + header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php'.( + !empty($parentId) ? '?id='.(int) $parentId : '' + )); exit; case 'export': $courses = CourseCategory::getCoursesInCategory($categoryId); @@ -48,7 +50,7 @@ $name = api_get_local_time().'_'.$categoryInfo['code']; $courseList = []; - /* @var \Chamilo\CoreBundle\Entity\Course $course */ + /* @var Course $course */ foreach ($courses as $course) { $courseList[] = [$course->getTitle()]; } @@ -57,7 +59,9 @@ Export::arrayToCsvSimple($courseList, $name, false, $header); } else { Display::addFlash(Display::return_message(get_lang('No courses found for this category'), 'warning')); - header('Location: '.api_get_self().'?category='.Security::remove_XSS($categoryId)); + header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php'.( + !empty($parentId) ? '?id='.(int) $parentId : '' + )); exit; } break; @@ -67,7 +71,19 @@ } else { Display::addFlash(Display::return_message(get_lang('Cannot move category up'), 'error')); } - header('Location: '.api_get_self().'?category='.Security::remove_XSS($category)); + header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php'.( + !empty($parentId) ? '?id='.(int) $parentId : '' + )); + exit; + case 'moveDown': + if (CourseCategory::moveNodeDown($categoryId, $_GET['tree_pos'], $parentId)) { + Display::addFlash(Display::return_message(get_lang('Update successful'))); + } else { + Display::addFlash(Display::return_message(get_lang('Cannot move category down'), 'error')); + } + header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php'.( + !empty($parentId) ? '?id='.(int) $parentId : '' + )); exit; case 'add': if (isset($_POST['formSent']) && $_POST['formSent']) { @@ -76,7 +92,7 @@ $_POST['title'], $_POST['auth_course_child'], $_POST['description'], - $parentId, + $_POST['parent_id'] ?? null, ); if (isset($_FILES['image']) && $categoryEntity) { @@ -84,7 +100,7 @@ CourseCategory::saveImage($categoryEntity, $_FILES['image'], $crop); } Display::addFlash(Display::return_message(get_lang('Item added'))); - header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php?id='.$parentId); + header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php'.(!empty($_POST['parent_id']) ? '?id='.(int) $_POST['parent_id'] : '')); exit; } break; @@ -95,7 +111,8 @@ $_REQUEST['title'], $_REQUEST['auth_course_child'], $_REQUEST['code'], - $_REQUEST['description'] + $_REQUEST['description'], + $_REQUEST['parent_id'] ?? null ); // Delete Picture Category @@ -111,7 +128,7 @@ } Display::addFlash(Display::return_message(get_lang('Update successful'))); - header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php?id='.$parentId); + header('Location: '.api_get_path(WEB_CODE_PATH).'admin/course_category.php'.(!empty($_POST['parent_id']) ? '?id='.(int) $_POST['parent_id'] : '')); exit; } break; @@ -173,7 +190,24 @@ ), ]; $form->addGroup($group, null, get_lang('Allow adding courses in this category?')); - + if ('add' === $action && !empty($categoryId)) { + $form->addHidden('parent_id', $categoryId); + $form->addLabel(get_lang('Parent category'), $parentInfo['title'].' ('.$parentInfo['code'].')'); + } else { + $allCategories = CourseCategory::getAllCategories(); + $parentOptions = ['' => get_lang('No parent')]; + foreach ($allCategories as $cat) { + if ('edit' === $action && $cat['id'] == $categoryId) { + continue; + } + $parentOptions[$cat['id']] = '('.$cat['code'].') '.$cat['title']; + } + $form->addSelect( + 'parent_id', + get_lang('Parent category'), + $parentOptions + ); + } $form->addHtmlEditor( 'description', get_lang('Description'), @@ -199,10 +233,20 @@ if ('edit' === $action && !empty($categoryInfo)) { $text = get_lang('Save'); $form->setDefaults($categoryInfo); + $form->setDefaults([ + 'parent_id' => $categoryInfo['parent_id'] ?? '', + ]); $form->addButtonSave($text); } else { $text = get_lang('Add category'); - $form->setDefaults(['auth_course_child' => 'TRUE']); + $defaultValues = [ + 'auth_course_child' => 'TRUE', + ]; + + if ('add' === $action && !empty($categoryId)) { + $defaultValues['parent_id'] = $categoryId; + } + $form->setDefaults($defaultValues); $form->addButtonCreate($text); } $form->display(); diff --git a/public/main/inc/lib/course_category.lib.php b/public/main/inc/lib/course_category.lib.php index 14b2c793fc2..afbe6cd70d8 100644 --- a/public/main/inc/lib/course_category.lib.php +++ b/public/main/inc/lib/course_category.lib.php @@ -87,14 +87,12 @@ public static function getCategoryById($categoryId) public static function getAllCategories() { $tbl_category = Database::get_main_table(TABLE_MAIN_CATEGORY); - $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE); - $table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE_CATEGORY); $conditions = " INNER JOIN $table a ON (t1.id = a.course_category_id)"; $whereCondition = " AND a.access_url_id = ".api_get_current_access_url_id(); $allowBaseCategories = ('true' === api_get_setting('course.allow_base_course_category')); if ($allowBaseCategories) { - $whereCondition = " AND (a.access_url_id = ".api_get_current_access_url_id()." OR a.access_url_id = 1) "; + $whereCondition = " AND (a.access_url_id = ".api_get_current_access_url_id()." OR a.access_url_id = 1)"; } $sql = "SELECT @@ -103,21 +101,19 @@ public static function getAllCategories() t1.code, t1.parent_id, t1.tree_pos, - t1.children_count, - COUNT(DISTINCT t3.code) AS number_courses - FROM $tbl_category t1 - $conditions - LEFT JOIN $tbl_course t3 - ON t3.category_id=t1.id - WHERE 1=1 - $whereCondition - GROUP BY - t1.title, - t1.code, - t1.parent_id, - t1.tree_pos, - t1.children_count - ORDER BY t1.parent_id, t1.tree_pos"; + t1.children_count + FROM $tbl_category t1 + $conditions + WHERE 1=1 + $whereCondition + GROUP BY + t1.id, + t1.title, + t1.code, + t1.parent_id, + t1.tree_pos, + t1.children_count + ORDER BY t1.parent_id, t1.tree_pos"; $result = Database::query($sql); @@ -205,7 +201,7 @@ public static function updateParentCategoryChildrenCount($categoryId, $delta = 1 Database::query($sql); } - public static function edit($categoryId, $name, $canHaveCourses, $code, $description): ?CourseCategoryEntity + public static function edit($categoryId, $name, $canHaveCourses, $code, $description, $parentId = null): ?CourseCategoryEntity { $repo = Container::getCourseCategoryRepository(); $category = $repo->find($categoryId); @@ -221,6 +217,10 @@ public static function edit($categoryId, $name, $canHaveCourses, $code, $descrip ->setAuthCourseChild($canHaveCourses) ; + if (!empty($parentId)) { + $category->setParent(Container::getCourseCategoryRepository()->find($parentId)); + } + $repo->save($category); // Updating children @@ -272,6 +272,38 @@ public static function moveNodeUp($categoryId, $treePos, $parentId): bool return true; } + public static function moveNodeDown($categoryId, $treePos, $parentId): bool + { + $table = Database::get_main_table(TABLE_MAIN_CATEGORY); + $categoryId = (int) $categoryId; + $treePos = (int) $treePos; + + $parentIdCondition = "parent_id IS NULL"; + if (!empty($parentId)) { + $parentIdCondition = "parent_id = '".Database::escape_string($parentId)."'"; + } + + self::reorganizeTreePos($parentId); + + $sql = "SELECT id, tree_pos + FROM $table + WHERE $parentIdCondition AND tree_pos > $treePos + ORDER BY tree_pos ASC + LIMIT 1"; + + $result = Database::query($sql); + $nextCategory = Database::fetch_array($result); + + if (!$nextCategory) { + return false; + } + + Database::query("UPDATE $table SET tree_pos = {$nextCategory['tree_pos']} WHERE id = $categoryId"); + Database::query("UPDATE $table SET tree_pos = $treePos WHERE id = {$nextCategory['id']}"); + + return true; + } + public static function reorganizeTreePos($parentId): void { $table = Database::get_main_table(TABLE_MAIN_CATEGORY); @@ -384,21 +416,51 @@ public static function listCategories(array $categorySource = []): string $column++; } $row++; - $mainUrl = api_get_path(WEB_CODE_PATH).'admin/course_category.php?category='.$categoryCode; + $baseUrl = api_get_path(WEB_CODE_PATH).'admin/course_category.php'; + $baseParams = []; + if (!empty($categorySource['id'])) { + $baseParams['id'] = (int) $categorySource['id']; + } $editIcon = Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit')); $exportIcon = Display::getMdiIcon(ActionIcon::EXPORT_CSV, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('ExportAsCSV')); $deleteIcon = Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')); - $moveIcon = Display::getMdiIcon(ActionIcon::UP, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Up in same level')); - $urlId = api_get_current_access_url_id(); + + $positions = array_map(fn($c) => $c->getTreePos(), $categories); + $minTreePos = min($positions); + $maxTreePos = max($positions); + foreach ($categories as $category) { $categoryId = $category->getId(); $code = $category->getCode(); - $editUrl = $mainUrl.'&id='.$categoryId.'&action=edit'; - $moveUrl = $mainUrl.'&id='.$categoryId.'&action=moveUp&tree_pos='.$category->getTreePos(); - $deleteUrl = $mainUrl.'&id='.$categoryId.'&action=delete'; - $exportUrl = $mainUrl.'&id='.$categoryId.'&action=export'; + $treePos = $category->getTreePos(); + $editUrl = $baseUrl.'?'.http_build_query(array_merge($baseParams, [ + 'action' => 'edit', + 'id' => $categoryId, + ])); + + $moveUpUrl = $baseUrl.'?'.http_build_query(array_merge($baseParams, [ + 'action' => 'moveUp', + 'id' => $categoryId, + 'tree_pos' => $treePos, + ])); + + $moveDownUrl = $baseUrl.'?'.http_build_query(array_merge($baseParams, [ + 'action' => 'moveDown', + 'id' => $categoryId, + 'tree_pos' => $treePos, + ])); + + $deleteUrl = $baseUrl.'?'.http_build_query(array_merge($baseParams, [ + 'action' => 'delete', + 'id' => $categoryId, + ])); + + $exportUrl = $baseUrl.'?'.http_build_query(array_merge($baseParams, [ + 'action' => 'export', + 'id' => $categoryId, + ])); $actions = []; @@ -410,7 +472,25 @@ function ($entry) use ($urlId) { if ($inUrl->count() > 0) { $actions[] = Display::url($editIcon, $editUrl); - $actions[] = Display::url($moveIcon, $moveUrl); + + if ($treePos > $minTreePos) { + $actions[] = Display::url( + Display::getMdiIcon(ActionIcon::UP, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Move up')), + $moveUpUrl + ); + } else { + $actions[] = Display::getMdiIcon(ActionIcon::UP, 'ch-tool-icon-disabled', null, ICON_SIZE_SMALL, get_lang('Move up')); + } + + if ($treePos < $maxTreePos) { + $actions[] = Display::url( + Display::getMdiIcon(ActionIcon::DOWN, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Move down')), + $moveDownUrl + ); + } else { + $actions[] = Display::getMdiIcon(ActionIcon::DOWN, 'ch-tool-icon-disabled', null, ICON_SIZE_SMALL, get_lang('Move down')); + } + $actions[] = Display::url($exportIcon, $exportUrl); $actions[] = Display::url( $deleteIcon,