terminal42 / contao-node

Manage content centrally as nodes and reuse them everywhere.
MIT License
29 stars 8 forks source link

Contao 5.3 - introduce support for nested elements #51

Open dennisbohn opened 8 months ago

dennisbohn commented 8 months ago

When an element group is included in a node and I want to edit the child elements, I currently get an error message.

image

Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException:
Node of folder type cannot have content elements

  at vendor/symfony/security-http/Firewall/ExceptionListener.php:136
  at Symfony\Component\Security\Http\Firewall\ExceptionListener->handleAccessDeniedException(object(ExceptionEvent), object(AccessDeniedException))
     (vendor/symfony/security-http/Firewall/ExceptionListener.php:103)
  at Symfony\Component\Security\Http\Firewall\ExceptionListener->onKernelException(object(ExceptionEvent), 'kernel.exception', object(TraceableEventDispatcher))
     (vendor/symfony/event-dispatcher/Debug/WrappedListener.php:116)
  at Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke(object(ExceptionEvent), 'kernel.exception', object(TraceableEventDispatcher))
     (vendor/symfony/event-dispatcher/EventDispatcher.php:220)
  at Symfony\Component\EventDispatcher\EventDispatcher->callListeners(array(object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener)), 'kernel.exception', object(ExceptionEvent))
     (vendor/symfony/event-dispatcher/EventDispatcher.php:56)
  at Symfony\Component\EventDispatcher\EventDispatcher->dispatch(object(ExceptionEvent), 'kernel.exception')
     (vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php:139)
  at Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch(object(ExceptionEvent), 'kernel.exception')
     (vendor/symfony/http-kernel/HttpKernel.php:239)
  at Symfony\Component\HttpKernel\HttpKernel->handleThrowable(object(AccessDeniedException), object(Request), 1)
     (vendor/symfony/http-kernel/HttpKernel.php:91)
  at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
     (vendor/symfony/http-kernel/Kernel.php:197)
  at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
     (public/index.php:44)

If I comment out lines 71 - 73 in the "ContentListener.php", I can edit the entries.

https://github.com/terminal42/contao-node/blob/842fc326c1b5c97341645989dfef18eaa05cb00a/src/EventListener/ContentListener.php#L71-L73

aschempp commented 8 months ago

@leofeyer @ausi any idea why this is happening?

ausi commented 8 months ago

I guess this is caused by $nodeId = $dc->id; on line 63 as $dc->id refers to the ID of the content element and not the node.

Toflar commented 8 months ago

Yeah, this extension is not compatible with nested elements at the moment. Simple as that. I'll tag this as a feature because we'll have to introduce support for them, in whichever way we'll do that.

dennisbohn commented 8 months ago

This fix worked for me.

https://github.com/terminal42/contao-node/compare/main...dennisbohn:contao-node:main

The ContentListener.php class contains an additional method to iterate through content elements.

    private function findNodeIdByContentId($contentId): int
    {
        $pid = $contentId;
        $ptable = 'tl_content';

        // Recursive node id finder
        while ($ptable === 'tl_content') {
            list($pid, $ptable) = $this->db->fetchNumeric('SELECT pid, ptable FROM tl_content WHERE id=?', [$pid]);
        }

        return $pid;
    }

The onLoadCallback method has been changed to the new method.

    public function onLoadCallback(DataContainer $dc): void
    {
        switch (Input::get('act')) {
            case 'edit':
            case 'delete':
            case 'show':
            case 'copy':
            case 'copyAll':
            case 'cut':
            case 'cutAll':
                $nodeId = $this->findNodeIdByContentId($dc->id);
                break;

            case 'paste':
                if ('create' === Input::get('mode')) {
                    $nodeId = $dc->id;
                } else {
                    $nodeId = $this->findNodeIdByContentId($dc->id);
                }
                break;

            case 'create':
                // Nested element
                if (Input::get('ptable') === 'tl_content') {
                    $nodeId = $this->findNodeIdByContentId(Input::get('pid'));
                } else {
                    $nodeId = $dc->id;
                }
                break;

            default:
                // Ajax requests such as toggle
                if (Input::get('field') && ($id = Input::get('cid') ?: Input::get('id'))) {
                    $nodeId = $this->findNodeIdByContentId($id);
                // Nested element
                } else if (Input::get('ptable') === 'tl_content') {
                    $nodeId = $this->findNodeIdByContentId($dc->id);
                } else {
                    $nodeId = $dc->id;
                }
                break;
        }

        $type = $this->db->fetchOne('SELECT type FROM tl_node WHERE id=?', [$nodeId]);

        // Throw an exception if the node is not present or is of a folder type
        if (!$type || NodeModel::TYPE_FOLDER === $type) {
            throw new AccessDeniedException('Node of folder type cannot have content elements');
        }

        $this->checkPermissions((int) $nodeId);
    }

Create, copy, edit, delete, moving, info and toggle works fine inside and outside the element group. Nested element groups are also possible. But it feels that it needs some more testing. I'm not sure what all needs to be taken into account when it comes to ajax requests.