0

Problem

Hello, I am using Symfony CMF 1.2, liip/imagine-bundle 1.3, symfony-cmf/media-bundle 1.2. I want to add 2 additional image fields to my block that extends ImagineBlock because for every image I upload there will be a mobile and tablet version of the image which is not a simple resize, the aspect ratio or whatnot is not similar. I cannot just crop/resize without affecting the quality of the image.

Attempts

My block

namespace xx\BlockBundle\Document;

use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCR;
use Symfony\Cmf\Bundle\BlockBundle\Doctrine\Phpcr\ImagineBlock;
use Symfony\Cmf\Bundle\MediaBundle\Doctrine\Phpcr\Image;
use Symfony\Cmf\Bundle\MediaBundle\ImageInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * Class ClickableBlock
 * @package xx\BlockBundle\Document
 * @PHPCR\Document(referenceable=true)
 */
class ClickableBlock extends ImagineBlock
{
    /**
     * @PHPCR\Child(nodeName="image-mobile", cascade={"persist"})
     * @var Image
     */
    protected $imageMobile;

    /**
     * @PHPCR\Child(nodeName="image-tablet", cascade={"persist"})
     * @var Image
     */
    protected $imageTablet;

    public function setIsPublishable($publishable)
    {
        $this->setPublishable($publishable);
    }

    /**
     * @return Image
     */
    public function getImageMobile()
    {
        return $this->imageMobile;
    }

    /**
     * @return Image
     */
    public function getImageTablet()
    {
        return $this->imageTablet;
    }

    /**
     * Set the imageMobile for this block.
     *
     * @param ImageInterface|UploadedFile|null $image optional the imageMobile to update
     * @return $this
     * @throws \InvalidArgumentException If the $image parameter can not be handled.
     */
    public function setImageMobile($image = null)
    {
        return $this->processImage($image, 'image-mobile', $this->imageMobile);
    }

    /**
     * Set the imageTablet for this block.
     *
     * @param ImageInterface|UploadedFile|null $image optional the imageTablet to update
     * @return $this
     * @throws \InvalidArgumentException If the $image parameter can not be handled.
     */
    public function setImageTablet($image = null)
    {
        return $this->processImage($image, 'image-tablet', $this->imageTablet);
    }

    /**
     * @param ImageInterface|UploadedFile|null $image
     * @param string $imageName
     * @param Image $imageRef
     * @return $this
     */
    protected function processImage($image, $imageName, $imageRef)
    {
        if (!$image) {
            return $this;
        }

        if (!$image instanceof ImageInterface && !$image instanceof UploadedFile) {
            $type = is_object($image) ? get_class($image) : gettype($image);

            throw new \InvalidArgumentException(sprintf(
                'Image is not a valid type, "%s" given.',
                $type
            ));
        }

        if ($imageRef) {
            // existing imageTablet, only update content
            $imageRef->copyContentFromFile($image);
        } elseif ($image instanceof ImageInterface) {
            $image->setName($imageName); // ensure document has right name
            $imageRef = $image;
        } else {
            $imageRef = new Image();
            $imageRef->copyContentFromFile($image);
        }

        return $this;
    }
}

Admin:

namespace xx\BlockBundle\Admin;

use xx\BlockBundle\Document\ClickableBlock;
use xx\MainBundle\Form\Common\FormMapper as CommonFormMapper;
use Cocur\Slugify\Slugify;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Cmf\Bundle\BlockBundle\Admin\Imagine\ImagineBlockAdmin;

class ClickableBlockAdmin extends ImagineBlockAdmin
{
    /**
     * {@inheritdoc}
     */
    public function toString($object)
    {
        return $object instanceof ClickableBlock && $object->getLabel()
            ? $object->getLabel()
            : parent::toString($object);
    }

    /**
     * {@inheritdoc}
     */
    public function prePersist($document)
    {
        parent::prePersist($document);
        $this->InitialiseDocument($document);
    }

    /**
     * @param $document
     */
    private function InitialiseDocument(&$document)
    {
        $documentManager = $this->getModelManager();
        $parentDocument = $documentManager->find(null, '/cms/xx/block');

        $document->setParentDocument($parentDocument);
        $slugifier = new Slugify();
        $document->setName($slugifier->slugify($document->getLabel()));
    }

    /**
     * {@inheritdoc}
     */
    public function preUpdate($document)
    {
        parent::preUpdate($document);
        $this->InitialiseDocument($document);
    }

    /**
     * {@inheritdoc}
     */
    protected function configureFormFields(FormMapper $formMapper)
    {
        parent::configureFormFields($formMapper);

        if (null === $this->getParentFieldDescription()) {
            $imageRequired = ($this->getSubject() && $this->getSubject()->getParentDocument()) ? false : true;
            $formMapper
                ->with('form.group_general')
                ->remove('parentDocument')
                ->remove('filter')
                ->add('parentDocument', 'hidden', ['required' => false, 'data' => 'filler'])
                ->add('name', 'hidden', ['required' => false, 'data' => 'filler'])
                ->add('imageMobile', 'cmf_media_image', array('required' => $imageRequired))
                ->add('imageTablet', 'cmf_media_image', array('required' => $imageRequired))
                ->end();

            // Append common fields to FormMapper
            $commonFormMapper = new CommonFormMapper($formMapper);
            $formMapper = $commonFormMapper->getPublishingFields();
        }
    }

}

Note I am unable to inject service container to this class (via constructor/method), that is why am using hardcoded node path and instantiated Slugify class instead of using it's service for now. I am all ears for a solution to this also. Ref -

xx.main.admin.pageadmin.container:
    class: xx\MainBundle\Admin\PageAdmin
    calls:
        - [setContainer,[ @service_container ]]
#        arguments: ["@service_container"]

The annotations on the image fields are based on the following config I found in \vendor\symfony-cmf\block-bundle\Resources\config\doctrine-phpcr\ImagineBlock.phpcr.xml:

<doctrine-mapping
    xmlns="http://doctrine-project.org/schemas/phpcr-odm/phpcr-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://doctrine-project.org/schemas/phpcr-odm/phpcr-mapping
    https://github.com/doctrine/phpcr-odm/raw/master/doctrine-phpcr-odm-mapping.xsd"
    >

    <document
        name="Symfony\Cmf\Bundle\BlockBundle\Doctrine\Phpcr\ImagineBlock"
        referenceable="true"
        translator="attribute"
        >

        <node name="node"/>

        <locale name="locale"/>

        <field name="label" type="string" translated="true" nullable="true"/>
        <field name="linkUrl" type="string" translated="true" nullable="true"/>
        <field name="filter" type="string" nullable="true"/>

        <child name="image" node-name="image">
            <cascade>
                <cascade-persist/>
            </cascade>
        </child>

    </document>

</doctrine-mapping>

Result

While the default "image" field persists normally, the other two added image fields are not taken into consideration since when I debug on prePersist I see that both fields are null while image field contains its uploaded file.

I tried adding a normal text field which saved and displayed normally on my page.

I use YAML in my project, so I am not sure how exactly the given XML translates, if ever it is the correct mapping to define.

Please help. :)

Cœur
  • 37,241
  • 25
  • 195
  • 267
Rishi
  • 667
  • 1
  • 6
  • 13
  • the mapping looks a bit confused to me. you have annotation mapping in your document, but also show an xml mapping (which is incomplete because it does not map your new image fields). i am not sure about precedence but try having only annotations or only xml. inheritance of metadata should work if you have annotations but the base class is mapped with xml. it ony does not work if you have an intermediary non-mapped class between yours and the cmf image block. – dbu Jun 13 '15 at 09:47
  • oh, maybe i misread and you only do annotations. but in your document, i think you never assign the imageMobile to any field i think. doctrine phpcr-odm is not active record, so you need to either assign the child to the parent (or persist it with the document manager, which is not the way to go inside the document class). as it is i think doctrine is not seeing that there would be a new image document. – dbu Jun 13 '15 at 09:49

2 Answers2

1

A colleague found the issue which was the following:

protected function processImage($image, $imageName, $imageRef)

should be

protected function processImage($image, $imageName, &$imageRef)

$imageRef was not passed by reference making it always null. Silly me. Let's hope this code at least helps other people. :)

Rishi
  • 667
  • 1
  • 6
  • 13
0

For the admin question: phpcr-odm admins have a rootPath for exactly the purpose of what you are doing. you could add to your service definition like this:

<call method="setRootPath">
    <argument>%cmf_content.persistence.phpcr.content_basepath%</argument>
</call>

and then you do $this->getRootPath()

dbu
  • 1,497
  • 9
  • 8
  • Thanks for this precision. I will use the rootPath but what about the slugify service or any other service I want to use in the Admin class? In my code I had to do this $slugifier = new Slugify(); $document->setName($slugifier->slugify($document->getLabel())); I wanted to use its service instead. – Rishi Jun 16 '15 at 07:13
  • you can just add constructor arguments and change the service definition accordingly. a sonata admin is just a service – dbu Jun 17 '15 at 08:35