8

So am working on a widget which allows the client to choose an image from their library of images and then insert it into the widget's params. Here is the block class code I am using for the image chooser and below is the widget parameter declaration in widget.xml

<parameter name="widget_image_chooser" xsi:type="block" visible="true" sort_order="20" required="true">
    <label translate="true">Category</label>
    <block class="Company\Module\Block\Widget\Image\Chooser">
        <data>
            <item name="button" xsi:type="array">
                <item name="open" xsi:type="string">Select Image</item>
            </item>
        </data>
    </block>
</parameter>

Now the problem is when the widget is inserted to a CMS Page or Block, it is inserted as a media widget instance, breaking the code because there can't be double quotes within double quotes, see below.

{{widget type="Company\Module\Block\Widget\LityVideo" identifier="https://vimeo.com/169559548" widget_image_chooser="{{media url="wysiwyg/13876414_1349854728361522_4781756811153391947_n.jpg"}}"}}

Is there a way of solving this without having to use javascript? I haven't been able to find the Model which renders the widgets in the CMS in the backend.

4 Answers 4

15

There seems to be a lot of people looking for a solution for this so here is my solution to my own answer.

The first thing you need to do is create a new plugin (interceptor) under [COMPANY]/[MODULE]/etc/di.xml

<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Widget\Model\Widget">
        <plugin name="test1" type="[COMPANY]\[MODULE]\Model\Widget" sortOrder="1" disabled="false"/>
    </type>
</config>

Then create the interceptor logic, you need to use the before method, this way you can change the parameters being passed to \Magento\Widget\Model\Widget, getWidgetDeclaration so something like this.

namespace [COMPANY]\[MODULE]\Model;

use \Magento\Widget\Model\Widget as BaseWidget;

class Widget
{
    public function beforeGetWidgetDeclaration(BaseWidget $subject, $type, $params = [], $asIs = true)
    {
        // I rather do a check for a specific parameters
        if(key_exists("widget_image_chooser", $params)) {

            $url = $params["widget_image_chooser"];
            if(strpos($url,'/directive/___directive/') !== false) {

                $parts = explode('/', $url);
                $key   = array_search("___directive", $parts);
                if($key !== false) {

                    $url = $parts[$key+1];
                    $url = base64_decode(strtr($url, '-_,', '+/='));

                    $parts = explode('"', $url);
                    $key   = array_search("{{media url=", $parts);
                    $url   = $parts[$key+1];

                    $params["widget_image_chooser"] = $url;
                }
            }
        }

        return array($type, $params, $asIs);
    }
}
1
  • In Magento 2.3.5 you can use param 'force_static_path' = 1 for fix links on front instead of your plugin. But you need to create new plugin to set this param. See github.com/magento/magento2/blob/…
    – goral
    Jul 9, 2020 at 12:50
1

A improved solution to this as of magento 2.2.2 would be to do the below. within the plugin, to filter the directive for frontend use.

[COMPANY][MODULE]\etc\di.xml

<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Widget\Model\Widget">
        <plugin name="test1" type="[COMPANY]\[MODULE]\Model\Widget" sortOrder="1" disabled="false"/>
    </type>
</config>

[COMPANY][MODULE]\Model\Widget.php

namespace [COMPANY]\[MODULE]\Model;

use \Magento\Widget\Model\Widget as BaseWidget;

class Widget
{
    protected $_filter;

    public function __construct(
        \Magento\Cms\Model\Template\Filter $filter
    )
    {
        $this->_filter = $filter;
    }

    public function beforeGetWidgetDeclaration(BaseWidget $subject, $type, $params = [], $asIs = true)
    {

        if ($type == "[COMPANY]\[MODULE]\Block\Widget\[ELEMENT CLASS]") {
            // I rather do a check for a specific parameters
            foreach ($params as $paramKey => $value) {

                $url = $params[$paramKey];
                if (strpos($url, '/directive/___directive/') !== false) {

                    $parts = explode('/', $url);
                    $key = array_search("___directive", $parts);
                    if ($key !== false) {

                        $url = $parts[$key + 1];
                        $url = base64_decode(strtr($url, '-_,', '+/='));

                        $parts = explode('"', $url);
                        $key = array_search("{{media url=", $parts);
                        $url = $parts[$key + 1];

                        $imagePath = $this->_filter->filter($url);
                        $params[$paramKey] = $imagePath;
                    }
                }
            }
        }

        return array($type, $params, $asIs);
    }
}
0

For Magento 2.3.5 and newer, that issue can be solved in a simple but not very obvious way. In the Image\Chooser class, it is necessary to add the following line to the text input element definition:

$input->addCustomAttribute('data-force_static_path', 1);

That will force the Media Browser to generate static links related to the website root (starting from /media).

-1

Thanks for posting your solution. It did not do what I excpected it to do. For me, it changed the $params['widget_image_chooser'] value to: wysiwyg/Boat.jpg. Ofcourse you can use this in your template.

Because I do not know the consequences of changing the url, I used your interceptor to replace the double quotes by single quotes. For me, this solves my problem.

My modified interceptor:

public function beforeGetWidgetDeclaration(BaseWidget $subject, $type, $params = [], $asIs = true)
{
    $imageParmeterName = 'photo'; //The parameter name as defined in widget.xml

    // I rather do a check for a specific parameters
    if (key_exists($imageParmeterName, $params)) {

        $url = $params[$imageParmeterName];
        if (strpos($url, '/directive/___directive/') !== false) {

            $parts = explode('/', $url);
            $key = array_search("___directive", $parts);
            if ($key !== false) {

                $key++;
                $url = $parts[$key];
                $imgSrc = base64_decode(strtr($url, '-_,', '+/=')); //{{media url="wysiwyg/Boat.jpg"}}
                $imgSrc = str_replace('"', "'", $imgSrc); //Replace " with ' to avoid breaking html
                $parts[$key] = base64_encode($imgSrc); //{{media url='wysiwyg/Boat.jpg'}}
                $url = implode('/', $parts); //Rebuild url

                $params[$imageParmeterName] = $url;
            }
        }
    }

    return array($type, $params, $asIs);
}

Edit: It seems this does not work as intended for some reason. Eventhough I do not modify the structure of the url, this 'solution' causes a backend image link to appear on the frontend.. I suspect this might be another issue.

1
  • Yeah i got the backend image link issue, was only in production however within my dev area everything seemed fine. Did you ever solve that?
    – harri
    Jun 22, 2018 at 12:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.