Link: Add shortcuts for homepage links and validate urLs - refs #4767

pull/5134/head
christianbeeznst 10 months ago
parent 70178f39b2
commit e039da98d6
  1. 2
      assets/vue/components/links/LinkCategoryForm.vue
  2. 10
      assets/vue/components/links/LinkForm.vue
  3. 22
      assets/vue/components/links/LinkItem.vue
  4. 17
      assets/vue/services/linkService.js
  5. 24
      assets/vue/views/links/LinksList.vue
  6. 49
      src/CoreBundle/Controller/Api/CLinkDetailsController.php
  7. 66
      src/CoreBundle/Controller/Api/CheckCLinkAction.php
  8. 37
      src/CoreBundle/Controller/Api/CreateCLinkAction.php
  9. 2
      src/CoreBundle/Controller/Api/CreateCLinkCategoryAction.php
  10. 5
      src/CoreBundle/Controller/Api/GetLinksCollectionController.php
  11. 44
      src/CoreBundle/Controller/Api/UpdateCLinkAction.php
  12. 2
      src/CoreBundle/Controller/Api/UpdateCLinkCategoryAction.php
  13. 29
      src/CoreBundle/Controller/ResourceController.php
  14. 72
      src/CoreBundle/Migrations/Schema/V200/Version20240202122300.php
  15. 18
      src/CourseBundle/Entity/CLink.php
  16. 6
      src/CourseBundle/Entity/CLinkCategory.php
  17. 2
      src/CourseBundle/Repository/CShortcutRepository.php

@ -86,7 +86,7 @@ const fetchCategory = async () => {
if (props.categoryId) {
try {
let category = await linkService.getCategory(props.categoryId)
formData.title = category.categoryTitle
formData.title = category.title
formData.description = category.description
} catch (error) {
console.error('Error fetching category:', error)

@ -25,7 +25,7 @@
v-model="formData.category"
:options="categories"
:label="t('Select a category')"
option-label="categoryTitle"
option-label="title"
option-value="iid"
hast-empty-value
/>
@ -129,7 +129,7 @@ onMounted(() => {
const fetchCategories = async () => {
try {
categories.value = await linkService.getCategories()
categories.value = await linkService.getCategories(parentResourceNodeId.value)
} catch (error) {
console.error('Error fetching categories:', error)
}
@ -142,10 +142,10 @@ const fetchLink = async () => {
formData.url = response.url
formData.title = response.title
formData.description = response.description
formData.showOnHomepage = response.showOnHomepage
formData.showOnHomepage = response.onHomepage
formData.target = response.target
formData.parentResourceNodeId = response.value
formData.resourceLinkList = response.value
formData.parentResourceNodeId = response.parentResourceNodeId
formData.resourceLinkList = response.resourceLinkList
if (response.category) {
formData.category = parseInt(response.category["@id"].split("/").pop())
}

@ -9,6 +9,20 @@
/>
{{ link.title }}
</a>
<BaseIcon
v-if="isLinkValid.isValid"
icon="check"
size="small"
class="text-green-500"
:title="t('Link is valid')"
/>
<BaseIcon
v-else-if="isLinkValid.isValid === false"
icon="alert"
size="small"
class="text-red-500"
:title="t('Link is not valid')"
/>
</h6>
</div>
<div class="flex gap-2" v-if="securityStore.isAuthenticated && isCurrentTeacher">
@ -65,7 +79,7 @@ import BaseIcon from "../basecomponents/BaseIcon.vue"
import { isVisible, VISIBLE } from "./linkVisibility"
import { useSecurityStore } from "../../store/securityStore"
import { useStore } from "vuex"
import { computed } from "vue"
import { computed, watch } from "vue"
const store = useStore()
const securityStore = useSecurityStore()
@ -78,7 +92,11 @@ defineProps({
type: Object,
required: true,
},
})
isLinkValid: {
type: Object,
default: () => ({})
},
});
const emit = defineEmits(["check", "edit", "toggle", "moveUp", "moveDown", "delete"])
</script>

@ -15,7 +15,7 @@ export default {
* @param {Number|String} linkId
*/
getLink: async (linkId) => {
const response = await axios.get(ENTRYPOINT + 'links/' + linkId)
const response = await axios.get(ENTRYPOINT + 'links/' + linkId + '/details/')
return response.data
},
@ -70,8 +70,8 @@ export default {
return response.data
},
getCategories: async () => {
const response = await axios.get(ENTRYPOINT + 'link_categories')
getCategories: async (parentId) => {
const response = await axios.get(`${ENTRYPOINT}link_categories?resourceNode.parent=${parentId}`)
return response.data['hydra:member']
},
@ -120,4 +120,15 @@ export default {
const response = await axios.put(endpoint, {visible})
return response.data
},
/**
* Checks if the URL is valid.
* @param {String} url The URL to be checked.
* @param linkId
*/
checkLink: async (url, linkId) => {
const endpoint = `${ENTRYPOINT}links/${linkId}/check`;
const response = await axios.get(endpoint, { params: { url } });
return response.data;
},
}

@ -13,13 +13,13 @@
type="black"
@click="redirectToCreateLinkCategory"
/>
<BaseButton
<!--BaseButton
:label="t('Export to PDF')"
icon="file-pdf"
type="black"
@click="exportToPDF"
/>
<StudentViewButton />
<StudentViewButton /-->
</BaseToolbar>
<LinkCategoryCard v-if="isLoading">
@ -69,6 +69,7 @@
>
<LinkItem
:link="link"
:isLinkValid="linkValidationResults[link.iid]"
@check="checkLink(link.iid, link.url)"
@delete="confirmDeleteLink(link)"
@edit="editLink"
@ -128,6 +129,7 @@
>
<LinkItem
:link="link"
:isLinkValid="linkValidationResults[link.iid]"
@check="checkLink(link.iid, link.url)"
@delete="confirmDeleteLink(link)"
@edit="editLink"
@ -214,6 +216,8 @@ const categoryToDelete = ref(null)
const isLoading = ref(true)
const linkValidationResults = ref({});
onMounted(() => {
linksWithoutCategory.value = []
categories.value = {}
@ -237,8 +241,8 @@ function confirmDeleteLink(link) {
async function deleteLink() {
try {
await linkService.deleteLink(linkToDelete.value.id)
isDeleteLinkDialogVisible.value = true
linkToDelete.value = null
isDeleteLinkDialogVisible.value = false
notifications.showSuccessNotification(t("Link deleted"))
await fetchLinks()
} catch (error) {
@ -247,8 +251,14 @@ async function deleteLink() {
}
}
function checkLink(id, url) {
// Implement the logic to check the link using the provided id and url
async function checkLink(id, url) {
try {
const result = await linkService.checkLink(url, id);
linkValidationResults.value = { ...linkValidationResults.value, [id]: { isValid: result.isValid } };
} catch (error) {
console.error("Error checking link:", error);
linkValidationResults.value = { ...linkValidationResults.value, [id]: { isValid: false, message: error.message || 'Link validation failed' } };
}
}
async function toggleVisibility(link) {
@ -256,16 +266,12 @@ async function toggleVisibility(link) {
const visibility = toggleVisibilityProperty(!link.linkVisible)
let newLink = await linkService.toggleLinkVisibility(link.iid, isVisible(visibility))
notifications.showSuccessNotification(t("Link visibility updated"))
linksWithoutCategory.value
.filter((l) => l.iid === link.iid)
.forEach((l) => (l.linkVisible = visibilityFromBoolean(newLink.linkVisible)))
Object.values(categories.value)
.map((c) => c.links)
.flat()
.filter((l) => l.iid === link.iid)
.forEach((l) => (l.linkVisible = visibilityFromBoolean(newLink.linkVisible)))
} catch (error) {
console.error("Error deleting link:", error)
notifications.showErrorNotification(t("Could not change visibility of link"))
}
}

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Controller\Api;
use Chamilo\CourseBundle\Entity\CLink;
use Chamilo\CourseBundle\Repository\CShortcutRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class CLinkDetailsController extends AbstractController
{
public function __invoke(CLink $link, CShortcutRepository $shortcutRepository): Response
{
$shortcut = $shortcutRepository->getShortcutFromResource($link);
$isOnHomepage = null !== $shortcut;
$parentResourceNodeId = null;
if ($link->getResourceNode() && $link->getResourceNode()->getParent()) {
$parentResourceNodeId = $link->getResourceNode()->getParent()->getId();
}
$resourceLinkList = [];
if ($link->getResourceLinkEntityList()) {
foreach ($link->getResourceLinkEntityList() as $resourceLink) {
$resourceLinkList[] = [
'visibility' => $resourceLink->getVisibility(),
'cid' => $resourceLink->getCourse()->getId(),
'sid' => $resourceLink->getSession()->getId()
];
}
}
$details = [
'url' => $link->getUrl(),
'title' => $link->getTitle(),
'description' => $link->getDescription(),
'onHomepage' => $isOnHomepage,
'target' => $link->getTarget(),
'parentResourceNodeId' => $parentResourceNodeId,
'resourceLinkList' => $resourceLinkList,
'category' => $link->getCategory()?->getIid(),
];
return $this->json($details, Response::HTTP_OK);
}
}

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Controller\Api;
use Chamilo\CoreBundle\Settings\SettingsManager;
use Chamilo\CourseBundle\Entity\CLink;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class CheckCLinkAction extends AbstractController
{
public function __invoke(CLink $link, Request $request, SettingsManager $settingsManager): JsonResponse
{
$url = $request->query->get('url');
$result = $this->checkUrl($url, $settingsManager);
return new JsonResponse(['isValid' => $result]);
}
private function checkUrl(string $url, SettingsManager $settingsManager): bool
{
// Check if curl is available.
if (!\in_array('curl', get_loaded_extensions())) {
return false;
}
// set URL and other appropriate options
$defaults = [
CURLOPT_URL => $url,
CURLOPT_FOLLOWLOCATION => true, // follow redirects
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 4,
];
// Check for proxy settings in your application configuration
$proxySettings = $settingsManager->getSetting('platform.proxy_settings', true);
if ($proxySettings && isset($proxySettings['curl_setopt_array'])) {
$defaults[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
$defaults[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
}
// Create a new cURL resource
$ch = curl_init();
curl_setopt_array($ch, $defaults);
// grab URL and pass it to the browser
ob_start();
$result = curl_exec($ch);
ob_get_clean();
// close cURL resource, and free up system resources
curl_close($ch);
// Check for any errors
if ($result === false || curl_getinfo($ch, CURLINFO_HTTP_CODE) != 200) {
return false;
}
return true;
}
}

@ -6,22 +6,27 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Controller\Api;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CourseBundle\Entity\CLink;
use Chamilo\CourseBundle\Entity\CLinkCategory;
use Chamilo\CourseBundle\Repository\CLinkRepository;
use Chamilo\CourseBundle\Repository\CShortcutRepository;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
class CreateCLinkAction extends BaseResourceFileAction
{
public function __invoke(Request $request, CLinkRepository $repo, EntityManager $em): CLink
public function __invoke(Request $request, CLinkRepository $repo, EntityManager $em, CShortcutRepository $shortcutRepository, Security $security): CLink
{
$data = json_decode($request->getContent(), true);
$url = $data['url'];
$title = $data['title'];
$description = $data['description'];
$categoryId = (int) $data['category'];
$onHomepage = isset($data['showOnHomepage']) ? (int) $data['showOnHomepage'] : 0;
$onHomepage = isset($data['showOnHomepage']) && (bool) $data['showOnHomepage'];
$target = $data['target'];
$parentResourceNodeId = $data['parentResourceNodeId'];
$resourceLinkList = json_decode($data['resourceLinkList'], true);
@ -52,6 +57,34 @@ class CreateCLinkAction extends BaseResourceFileAction
$link->setResourceLinkArray($resourceLinkList);
}
$em->persist($link);
$em->flush();
$this->handleShortcutCreation($resourceLinkList, $em, $security, $link, $shortcutRepository, $onHomepage);
return $link;
}
private function handleShortcutCreation(
array $resourceLinkList,
EntityManager $em,
Security $security,
CLink $link,
CShortcutRepository $shortcutRepository,
bool $onHomepage
): void {
$firstLink = reset($resourceLinkList);
if (isset($firstLink['sid']) && isset($firstLink['cid'])) {
$sid = $firstLink['sid'];
$cid = $firstLink['cid'];
$course = $cid ? $em->getRepository(Course::class)->find($cid) : null;
$session = $sid ? $em->getRepository(Session::class)->find($sid) : null;
/** @var User $currentUser */
$currentUser = $security->getUser();
if ($onHomepage) {
$shortcutRepository->addShortCut($link, $currentUser, $course, $session);
}
}
}
}

@ -22,7 +22,7 @@ class CreateCLinkCategoryAction extends BaseResourceFileAction
$resourceLinkList = json_decode($data['resourceLinkList'], true);
$linkCategory = (new CLinkCategory())
->setCategoryTitle($title)
->setTitle($title)
->setDescription($description)
;

@ -47,6 +47,7 @@ class GetLinksCollectionController extends BaseResourceFileAction
[
'id' => $link->getIid(),
'title' => $link->getTitle(),
'description' => $link->getDescription(),
'url' => $link->getUrl(),
'iid' => $link->getIid(),
'linkVisible' => $link->getFirstResourceLink()->getVisibility(),
@ -67,7 +68,8 @@ class GetLinksCollectionController extends BaseResourceFileAction
$categoryInfo = [
'id' => $categoryId,
'name' => $category->getCategoryTitle(),
'title' => $category->getTitle(),
'descritption' => $category->getDescription(),
'visible' => $category->getFirstResourceLink()->getVisibility(),
];
$dataResponse['categories'][$categoryId]['info'] = $categoryInfo;
@ -79,6 +81,7 @@ class GetLinksCollectionController extends BaseResourceFileAction
$items[] = [
'id' => $link->getIid(),
'title' => $link->getTitle(),
'description' => $link->getDescription(),
'url' => $link->getUrl(),
'iid' => $link->getIid(),
'linkVisible' => $link->getFirstResourceLink()->getVisibility(),

@ -6,24 +6,28 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Controller\Api;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CourseBundle\Entity\CLink;
use Chamilo\CourseBundle\Entity\CLinkCategory;
use Chamilo\CourseBundle\Repository\CLinkRepository;
use Chamilo\CourseBundle\Repository\CShortcutRepository;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
class UpdateCLinkAction extends BaseResourceFileAction
{
public function __invoke(CLink $link, Request $request, CLinkRepository $repo, EntityManager $em): CLink
public function __invoke(CLink $link, Request $request, CLinkRepository $repo, EntityManager $em, CShortcutRepository $shortcutRepository, Security $security): CLink
{
$data = json_decode($request->getContent(), true);
$url = $data['url'];
$title = $data['title'];
$description = $data['description'];
$categoryId = (int) $data['category'];
$onHomepage = isset($data['showOnHomepage']) ? (int) $data['showOnHomepage'] : 0;
$onHomepage = isset($data['showOnHomepage']) && (bool) $data['showOnHomepage'];
$target = $data['target'];
$parentResourceNodeId = $data['parentResourceNodeId'];
$resourceLinkList = json_decode($data['resourceLinkList'], true);
$link->setUrl($url);
@ -38,14 +42,36 @@ class UpdateCLinkAction extends BaseResourceFileAction
}
}
if (!empty($parentResourceNodeId)) {
$link->setParentResourceNode($parentResourceNodeId);
}
$em->persist($link);
$em->flush();
if (!empty($resourceLinkList)) {
$link->setResourceLinkArray($resourceLinkList);
}
$this->handleShortcutCreationOrDeletion($resourceLinkList, $em, $security, $link, $shortcutRepository, $onHomepage);
return $link;
}
private function handleShortcutCreationOrDeletion(
array $resourceLinkList,
EntityManager $em,
Security $security,
CLink $link,
CShortcutRepository $shortcutRepository,
bool $onHomepage
): void {
$firstLink = reset($resourceLinkList);
if (isset($firstLink['sid']) && isset($firstLink['cid'])) {
$sid = $firstLink['sid'];
$cid = $firstLink['cid'];
$course = $cid ? $em->getRepository(Course::class)->find($cid) : null;
$session = $sid ? $em->getRepository(Session::class)->find($sid) : null;
/** @var User $currentUser */
$currentUser = $security->getUser();
if ($onHomepage) {
$shorcut = $shortcutRepository->addShortCut($link, $currentUser, $course, $session);
} else {
$removed = $shortcutRepository->removeShortCut($link);
}
}
}
}

@ -21,7 +21,7 @@ class UpdateCLinkCategoryAction extends BaseResourceFileAction
$parentResourceNodeId = $data['parentResourceNodeId'];
$resourceLinkList = json_decode($data['resourceLinkList'], true);
$linkCategory->setCategoryTitle($title);
$linkCategory->setTitle($title);
$linkCategory->setDescription($description);
if (!empty($parentResourceNodeId)) {

@ -21,6 +21,7 @@ use Chamilo\CoreBundle\Traits\GradebookControllerTrait;
use Chamilo\CoreBundle\Traits\ResourceControllerTrait;
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
use Chamilo\CourseBundle\Entity\CTool;
use Chamilo\CourseBundle\Repository\CLinkRepository;
use Chamilo\CourseBundle\Repository\CShortcutRepository;
use Chamilo\CourseBundle\Repository\CToolRepository;
use Doctrine\Common\Collections\ArrayCollection;
@ -171,8 +172,10 @@ class ResourceController extends AbstractResourceController implements CourseCon
* @return RedirectResponse|void
*/
#[Route('/{tool}/{type}/{id}/link', name: 'chamilo_core_resource_link', methods: ['GET'])]
public function linkAction(Request $request, RouterInterface $router)
public function linkAction(Request $request, RouterInterface $router, CLinkRepository $cLinkRepository)
{
$tool = $request->get('tool');
$type = $request->get('type');
$id = $request->get('id');
$resourceNode = $this->getResourceNodeRepository()->find($id);
@ -180,15 +183,25 @@ class ResourceController extends AbstractResourceController implements CourseCon
throw new FileNotFoundException('Resource not found');
}
$repo = $this->getRepositoryFromRequest($request);
if ($repo instanceof ResourceWithLinkInterface) {
$resource = $repo->getResourceFromResourceNode($resourceNode->getId());
$url = $repo->getLink($resource, $router, $this->getCourseUrlQueryToArray());
if ('course_tool' === $tool && 'links' === $type) {
$cLink = $cLinkRepository->findOneBy(['resourceNode' => $resourceNode]);
if ($cLink) {
$url = $cLink->getUrl();
return $this->redirect($url);
} else {
throw new FileNotFoundException('CLink not found for the given resource node');
}
} else {
$repo = $this->getRepositoryFromRequest($request);
if ($repo instanceof ResourceWithLinkInterface) {
$resource = $repo->getResourceFromResourceNode($resourceNode->getId());
$url = $repo->getLink($resource, $router, $this->getCourseUrlQueryToArray());
return $this->redirect($url);
}
return $this->redirect($url);
}
$this->abort('No redirect');
$this->abort('No redirect');
}
}
/**

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
use Chamilo\CourseBundle\Entity\CLink;
use Chamilo\CourseBundle\Repository\CLinkRepository;
use Chamilo\CourseBundle\Repository\CShortcutRepository;
use Doctrine\DBAL\Schema\Schema;
class Version20240202122300 extends AbstractMigrationChamilo
{
public function getDescription(): string
{
return 'Create shortcuts for c_link entries with on_homepage = 1';
}
public function up(Schema $schema): void
{
$container = $this->getContainer();
$doctrine = $container->get('doctrine');
$em = $doctrine->getManager();
$connection = $em->getConnection();
$admin = $this->getAdmin();
$linkRepo = $container->get(CLinkRepository::class);
$shortcutRepo = $container->get(CShortcutRepository::class);
$sql = 'SELECT * FROM c_link WHERE on_homepage = 1';
$stmt = $connection->prepare($sql);
$result = $stmt->executeQuery();
while ($row = $result->fetchAssociative()) {
$linkId = $row['iid'];
/* @var CLink $link */
$link = $linkRepo->find($linkId);
if (!$link) {
error_log("Link with ID $linkId not found");
continue;
}
$course = $link->getFirstResourceLink()->getCourse();
$session = $link->getFirstResourceLink()->getSession();
$shortcut = $shortcutRepo->getShortcutFromResource($link);
if (null === $shortcut) {
try {
$shortcutRepo->addShortCut($link, $admin, $course, $session);
error_log("Shortcut created for link ID $linkId");
} catch (\Exception $e) {
error_log("Failed to create shortcut for link ID $linkId: " . $e->getMessage());
}
} else {
error_log("Shortcut already exists for link ID $linkId");
}
}
$em->flush();
}
public function down(Schema $schema): void
{
}
}

@ -15,6 +15,8 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use Chamilo\CoreBundle\Controller\Api\CheckCLinkAction;
use Chamilo\CoreBundle\Controller\Api\CLinkDetailsController;
use Chamilo\CoreBundle\Controller\Api\CreateCLinkAction;
use Chamilo\CoreBundle\Controller\Api\GetLinksCollectionController;
use Chamilo\CoreBundle\Controller\Api\UpdateCLinkAction;
@ -52,6 +54,22 @@ use Symfony\Component\Validator\Constraints as Assert;
deserialize: false
),
new Get(security: "is_granted('VIEW', object.resourceNode)"),
new Get(
uriTemplate: '/links/{iid}/details',
controller: CLinkDetailsController::class,
openapiContext: [
'summary' => 'Gets the details of a link, including whether it is on the homepage',
],
security: "is_granted('VIEW', object.resourceNode)"
),
new Get(
uriTemplate: '/links/{iid}/check',
controller: CheckCLinkAction::class,
openapiContext: [
'summary' => 'Check if a link URL is valid',
],
security: "is_granted('VIEW', object.resourceNode)"
),
new Delete(security: "is_granted('DELETE', object.resourceNode)"),
new Post(
controller: CreateCLinkAction::class,

@ -20,6 +20,7 @@ use Chamilo\CoreBundle\Controller\Api\CreateCLinkCategoryAction;
use Chamilo\CoreBundle\Controller\Api\UpdateCLinkCategoryAction;
use Chamilo\CoreBundle\Controller\Api\UpdateVisibilityLinkCategory;
use Chamilo\CoreBundle\Entity\AbstractResource;
use Chamilo\CoreBundle\Entity\Listener\ResourceListener;
use Chamilo\CoreBundle\Entity\ResourceInterface;
use Chamilo\CourseBundle\Repository\CLinkCategoryRepository;
use Doctrine\Common\Collections\ArrayCollection;
@ -125,10 +126,11 @@ use Symfony\Component\Validator\Constraints as Assert;
'groups' => ['link_category:write'],
],
)]
#[ApiFilter(SearchFilter::class, properties: ['category_title' => 'partial', 'resourceNode.parent' => 'exact'])]
#[ApiFilter(SearchFilter::class, properties: ['title' => 'partial', 'resourceNode.parent' => 'exact'])]
#[ApiFilter(OrderFilter::class, properties: ['iid', 'resourceNode.title', 'resourceNode.createdAt', 'resourceNode.updatedAt'])]
#[ORM\Table(name: 'c_link_category')]
#[ORM\Entity(repositoryClass: CLinkCategoryRepository::class)]
#[ORM\EntityListeners([ResourceListener::class])]
class CLinkCategory extends AbstractResource implements ResourceInterface, Stringable
{
#[ApiProperty(identifier: true)]
@ -138,7 +140,7 @@ class CLinkCategory extends AbstractResource implements ResourceInterface, Strin
#[ORM\GeneratedValue]
protected ?int $iid = null;
#[Groups(['link_category:read', 'link_category:write'])]
#[Groups(['link_category:read', 'link_category:write', 'link_category:browse'])]
#[Assert\NotBlank]
#[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)]
protected string $title;

@ -55,7 +55,7 @@ final class CShortcutRepository extends ResourceRepository
$shortcut = $this->getShortcutFromResource($resource);
if (null !== $shortcut) {
$em->remove($shortcut);
$em->flush();
//$em->flush();
return true;
}

Loading…
Cancel
Save