Skip to content

c_item_property to nodes conversion

Julio Montoya edited this page Oct 25, 2021 · 12 revisions

The table c_item_property has been removed in Chamilo v2, instead we use the following Resource-related entities:

  • AbstractResource
  • ResourceNode

For example, in the announcements tool, we have queries like this:

Source from 1.11.x:

https://github.com/chamilo/chamilo-lms/blob/1.11.x/main/inc/lib/AnnouncementManager.php#L2056-L2101

<?php

$condition_session = api_get_session_condition(
    $session_id,
    true,
    true,
    'announcement.session_id'
);


if (api_get_group_id() == 0) {
    $group_condition = '';
} else {
    $group_condition = " AND (ip.to_group_id='".api_get_group_id()."' OR ip.to_group_id = 0 OR ip.to_group_id IS NULL)";
}

$sql = "SELECT 
            announcement.*, 
            ip.visibility, 
            ip.to_group_id, 
            ip.insert_user_id
        FROM $tbl_announcement announcement 
        INNER JOIN $tbl_item_property ip
        ON (announcement.c_id = ip.c_id AND announcement.id = ip.ref)
        WHERE
            announcement.c_id = $courseId AND
            ip.c_id = $courseId AND                    
            ip.tool = 'announcement' AND
            ip.visibility <> '2'
            $group_condition
            $condition_session
        GROUP BY ip.ref
        ORDER BY display_order DESC
        LIMIT 0, $maximum";

You can see the $group_condition and $condition_session variables at the end have been built before building the query, and they are relatively complex. Adding them to the query is a complex operation by itself.

The process has been streamlined in v2, but requires some previous knowledge to use efficiently. Let's analyse the process that is required to completely convert this tool to the new Resource-type structure...

Steps:

1. AbstractResource

Set up a class like follows, which implements the methods required by the interface. As you can see, each tool-based class should extend AbstractResource and implement ResourceInterface.

Extending AbstractResource (src/CoreBundle/Entity/AbstractResource.php) will ensure some methods are readily available (addCourseLink(), getParent(), getResourceNode(), getResourceName(), etc).

Implementing ResourceInterface will remind you to implement those basic methods: __toString(), getResourceIdentifier(), getResourceName(), setResourceName(), getResourceNode(), setResourceNode(). Those (except the first two) are already defined in the AbstractResource definition, but it's good to have the definition of what methods must be covered at a minimum. This list might change over time.

<?php

namespace Chamilo\CourseBundle\Entity;

class CAnnouncement extends AbstractResource implements ResourceInterface
{
}

The CAnnouncement class represents the entity and the actions that can be taken on an announcement object so Chamilo can work with it.

2. ResourceRepository

The CAnnouncementRepository class, in contrast, represents the methods to access the announcement in a list (the database or other). So if you want to retrieve the announcement in a table full of announcements, you need a repository class for it.

<?php

namespace Chamilo\CourseBundle\Repository;
use Chamilo\CourseBundle\Entity\CAnnouncement;
use Doctrine\Persistence\ManagerRegistry;

final class CAnnouncementRepository extends ResourceRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, CAnnouncement::class);
    }
}

3. Declare the tool in the Container class

Because we are dealing with legacy code, we need to register this repository in the container for easy use:

<?php

namespace Chamilo\CoreBundle\Framework;

class Container
{
    /**
     * @return CAnnouncementRepository
     */
    public static function getAnnouncementRepository()
    {
        return self::$container->get('Chamilo\CourseBundle\Repository\CAnnouncementRepository');
    }

4. Change legacy code to v2 code

Legacy code:

 $sql = "SELECT $select
    FROM $tbl_announcement announcement
    INNER JOIN $tbl_item_property ip
    ON (announcement.id = ip.ref AND ip.c_id = announcement.c_id)
    WHERE
        announcement.c_id = $courseId AND
        ip.c_id = $courseId AND
        ip.tool = 'announcement' AND
        (
            ip.to_user_id = $user_id OR
            ip.to_group_id IS NULL OR
            ip.to_group_id IN (0, ".implode(", ", $group_memberships).")
        ) AND
        ip.visibility IN ('1', '0')
        $condition_session
        $searchCondition
    ORDER BY display_order DESC";

v2 code (no c_item_property):

<?php
        $repo = Container::getAnnouncementRepository();
        $course = api_get_course_entity($courseId);
        $session = api_get_session_entity($session_id);
        $group = api_get_group_entity(api_get_group_id());

        $qb = $repo->getResourcesByCourse($course, $session, $group);
        $qb->select('count(resource)');
        $count = $qb->getQuery()->getSingleScalarResult();

Because, getResourcesByCourse returns a QueryBuilder, we can overwrite some parameters and return the count instead of all the elements. The method getResourcesByCourse() should handle the conditions to filter by course, session or group natively, so you don't have to set the complex conditions we saw at the beginning anymore.

Create a new Resource.

For this example, we will use the CAnnouncement resource.

Before:

<?php
        $params = [
            'c_id' => $courseId,
            'content' => $newContent,
            'title' => $title,
            'end_date' => $end_date,
            'display_order' => $order,
            'session_id' => (int) $sessionId,
        ];

        $last_id = Database::insert($tbl_announcement, $params);

After:

<?php

        $course = api_get_course_entity($courseId);
        $session = api_get_session_entity($sessionId);

        $announcement = new CAnnouncement();
        $announcement
            ->setContent($newContent)
            ->setTitle($title)
            ->setEndDate(new DateTime($end_date))
            ->setDisplayOrder($order)
            ->setParent($course)
            ->addCourseLink($course, $session)
        ;

        $repo = Container::getAnnouncementRepository();
        $repo->create($announcement);

Any field not treated by the parent AbstractResource class (like setEndDate() in this case) will have to be implemented manually in the resource class (CAnnouncement in this case).

Update a Resource.

<?php

  $repo = Container::getAnnouncementRepository();
  $announcement = $repo->find($id);
  /** @var CStudentPublication $announcement */
  if ($announcement) {
      $announcement->setTitle('new title');
      $repo->update($announcement);
  }
Clone this wiki locally