Skip to content
Grav 2.0 is officially stable. Read the announcement →
Archive

Shortcode: image photo path

Started by Muut Archive 10 years ago · 14 replies · 536 views
10 years ago

Making progress with my first shortcode plugin. However, there is one hurdle that's giving me a hard time.

Trying to create a client-friendly method to add photos with captions into an article. My shortcode is essentially an HTML figure element with an image, citation (photo credit) and caption. The issue I'm having is an incomplete image path. The img src is resolving to one folder above where it is actually being stored -- in the article folder along with item.md.

In researching a solution, I'm coming to the conclusion that some regular expression code-fu or a possible re-direct needs to be applied. Any help with code explanation would be greatly appreciated.

Currently, the shortcode thinks that the image is located at:
http://localhost/siteRoot/blogRoot/sectionRoot/image.jpg

The correct path should be:
http://localhost/siteRoot/blogRoot/sectionRoot/specificArticle/image.jpg

Here is the twig code with the php code below:

TWIG
<figure class="figur-wrapper clearfix">
    <div class="figur {{ width }} {{ position }}" id="{{ hash ~ key }}">
       <img src="{{ image }}"  class="cover" alt="captioned photo">
      <cite>{{ cite }}</cite>
        <div class="caption">{{ shortcode.getContent() }}</div>
    </div>
</figure>
PHP
<?php

namespace Grav\Plugin\Shortcodes;

use Thunder\Shortcode\Shortcode\ShortcodeInterface;

class FigurShortcode extends Shortcode
{
    function init()
    {
        $this->shortcode->getHandlers()->add('figur', function(ShortcodeInterface $sc) {

            // Add assets
            $this->shortcode->addAssets('css', 'plugin://shortcode-greg/css-compiled/sc-sandboxTwo.css');

            $hash = $this->shortcode->getId($sc);

            $output = $this->twig->processTemplate('partials/sc-figur.html.twig', [
                'hash' => $hash,
                'position' => $sc->getParameter('position', 'right'),
                'width' => $sc->getParameter('width', 'one-third'),
                'image' => $sc->getParameter('image', ''),
                'cite' => $sc->getParameter('cite', 'Image by author'),
                'shortcode' => $sc,
            ]);

            return $output;
        });
10 years ago

Came upon this searching for a solution to the same problem. My thought is to expand an existing solution which uses regex to parse out the component parts of the img-element and re-arranging these into a <figure> including <figcaption>.

Now that we have a good plugin for applying PCRE-functions as filters, the replacement should be fairly much easier. You could essentially apply the filter on whichever template needed it. My approach is this:

Take processed Markdown, which outputs this:

TXT
<img title="Palestinian refugees escaping the 1948 palestine war" alt="Palestinian refugees 1948" src="http://somewhere.dev/pages/featured/1948.jpg">

Use a regex-pattern (found online):

HTML
/<img[^>]*?alt=\x22([^\x22]*)\x22[^>]*?src=\x22([^\x22]*)[^>]*?>|<img[^>]*?src=\x22([^\x22]*)\x22[^>]*?alt=\x22([^\x22]*)\x22[^>]*?>/ig

Replace with this:
<figure>$0<figcaption>$1</figcaption></figure>

And in PHP:

HTML
preg_replace("/<img[^>]*?alt=\x22([^\x22]*)\x22[^>]*?src=\x22([^\x22]*)[^>]*?>|<img[^>]*?src=\x22([^\x22]*)\x22[^>]*?alt=\x22([^\x22]*)\x22[^>]*?>/",
                    "<figure>$0<figcaption>$1</figcaption></figure>",
                    $buffer)

I wrapped this in a plugin, like so:

PHP
<?php
namespace Grav\Plugin;

use Grav\Common\Data;
use Grav\Common\Plugin;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\Event\Event;

class ImgCaptionsPlugin extends Plugin
{
    public static function getSubscribedEvents() {
        return [
            'onPageContentProcessed' => ['onPageContentProcessed', 0]
        ];
    }

    public function onPageContentProcessed(Event $event)
    {
        $page = $event['page'];
        $pluginsobject = (array) $this->config->get('plugins');
        $pageobject = $this->grav['page'];
        if (isset($pluginsobject['imgcaptions'])) {
            if ($pluginsobject['imgcaptions']['enabled']) {
                $buffer = $page->content();
                $url = $page->url();
                $buffer = preg_replace("/<img[^>]*?alt=\x22([^\x22]*)\x22[^>]*?src=\x22([^\x22]*)[^>]*?>|<img[^>]*?src=\x22([^\x22]*)\x22[^>]*?alt=\x22([^\x22]*)\x22[^>]*?>/",
                    "<figure>$0<figcaption>$1</figcaption></figure>",
                    $buffer);
                $page->setRawContent($buffer);
            }
        }
    }
}

The effect is that all images specified in Markdown (![Alt text](/path/to/img.jpg "Optional title")) are processed, wrapped in <figure>, and the content of the alt-attribute wrapped in <figcaption>.

Ideally it would be an option of the plugin to allow for a limited selection of templates to which it applies, but in my case this was not (at least currently) necessary.

10 years ago

In earnest I'd like to apply a HTMLParser instead of Regex, but as a quick path it works.

10 years ago

Utilizing a fairly old HTMLParser, PHP Simple HTML DOM Parser (v1.5), I did a quick proof-of-concept of adding in a srcset-attribute to accommodate responsive sizes of images. Like so:

PHP
<?php
namespace Grav\Plugin;

use Grav\Common\Data;
use Grav\Common\Plugin;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\Event\Event;
include_once('vendor/simplehtmldom/simple_html_dom.php');

class ImgSrcsetPlugin extends Plugin
{
    public static function getSubscribedEvents() {
        return [
            'onPageContentProcessed' => ['onPageContentProcessed', -1]
        ];
    }
    public function onPageContentProcessed(Event $event) 
    {
        $page = $event['page'];
        $pluginsobject = (array) $this->config->get('plugins');
        $pageobject = $this->grav['page'];
        $widths = array('320', '480', '640',  '960',  '1280',  '1600',  '1920',  '2240');
        if (isset($pluginsobject['imgsrcset'])) {
            if ($pluginsobject['imgsrcset']['enabled']) {
                $url = $page->url();
                $buffer = str_get_html($page->content());
                $images = $buffer->find('img');
                foreach($images as $image) {
                    $file = pathinfo($image->src);
                    $srcsets = '';
                    foreach($widths as $width) {
                        $srcsets .= '';
                        $srcsets .= $file['dirname'].'/'.$file['filename'].'-'.$width.'.'.$file['extension'].' '.$width.'w, ';
                    }
                    $srcsets = rtrim($srcsets, ", ");
                    $src = 'src="'.$file['dirname'].'/'.$file['filename'].'.'.$file['extension'].' '.$srcsets;
                    $new = $image;
                    $image->srcset = $srcsets;
                    $image->sizes = '100vw';
                    $buffer = str_replace($image, $new, $buffer);
                }
                $page->setRawContent($buffer);
            }
        }
    }
}

The parser is rather slow, but it's dependencies are far less than newer alternatives. Ideally it would use the dom-extension of PHP, but including third-party libraries > installing PHP-extensions server-wide.

Does Grav have a preferred HTMLParser (ie. in use at some point, thus a dependancy)? Or does anyone have a suggestion of a faster/newer one that has little overhead?

10 years ago

HTML Dom parsing is notoriously slow. Something I try to stay away from.

10 years ago

I often prefer using regex for "simplicity", but Dom-parsing is not prone to error from imperfect patterns.

10 years ago

I tried a newer parser, which by its own comparison is faster than other alternatives, and the time compared with regex seems negligible. Required Composer to run, but the 32 seconds saved in rendering (compared to Simple HTML DOM) is definitely worth it. Implementation for reference:

PHP

<?php
namespace Grav\Plugin;

use Grav\Common\Data;
use Grav\Common\Plugin;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\Event\Event;
require __DIR__ . '/vendor/autoload.php';
use DiDom\Document;

class ImgSrcsetPlugin extends Plugin
{
    public static function getSubscribedEvents() {
        return [
            'onPageContentProcessed' => ['onPageContentProcessed', -1]
        ];
    } 
    public function onPageContentProcessed(Event $event)
    {
        $page = $event['page'];
        $pluginsobject = (array) $this->config->get('plugins');
        $pageobject = $this->grav['page'];
        if (isset($pluginsobject['imgsrcset'])) {
            if ($pluginsobject['imgsrcset']['enabled']) {
                $doc = new Document($page->content());
                $widths = array('320', '480', '640',  '960',  '1280',  '1600',  '1920',  '2240');
                $images = $doc->find('img');
                foreach($images as $image) {
                    $file = pathinfo($image->src);
                    $srcsets = '';
                    foreach($widths as $width) {
                        $srcsets .= '';
                        $srcsets .= $file['dirname'].'/'.$file['filename'].'-'.$width.'.'.$file['extension'].' '.$width.'w, ';
                    }
                    $srcsets = rtrim($srcsets, ", ");
                    $image->setAttribute('srcset', $srcsets);
                    $image->setAttribute('sizes', '100vw');
                }
                $page->setRawContent($doc);
            }
        }
    }
}
---
10 years ago

Gingah, will be very interested to see how this plugin develops. Having access to the new(ish) HTML picture element in Grav would be awesome. Bonus points if it's a client-friendly implementation.
I understand that the picture spec is daunting -- so best of luck!

10 years ago

There is some "controversy" around the picture element, and my understanding is that currently the <figure> element with nested <img> including srcset's is preferred. Presumably this is because of the current status of <picture>'s implementation (also this, but syntactically I find it preferable.

I suppose a switch between implementation's is easy to do, but my current focus is on the DiDom-parser. Whilst its clearly fast enough to warrant use, I did have some issues running it on my live server - though I haven't had the time to troubleshoot this yet.

10 years ago

The technical issue is a bit of a quandary. Post-cache it will return a server error:

TXT
PHP Fatal error:  Method DiDom\Document::__toString() must not throw an exception in /root/system/src/Grav/Common/Page/Page.php on line 575

Which is this line:

PHP
$divider_pos = mb_strpos($this->content, "<p>{$delimiter}</p>");

I am not quite sure why the summary divider would cause an exception here, as $page->content() is only iterated over to add the srcset- and sizes attributes to the img-elements.

10 years ago

Gingah... Well done. I just had a chance to test-drive your ImgCaptions plugin. It does, indeed, perform exactly as described on the tin. Image with caption is converted to valid figure, img and figcaption elements in the `HTML.

Images are specified with standard markdown format -- the exception being the addition of of quoted text -- which would be the caption text. For example:

TXT
![Jewelry and Lipstick](diamondJewlry.png?cropZoom=300,200 "This is a text that will be used as the caption for the photograph. My sight said the hot VR goggles to be sure.")

Your solution works quite well. My problem is that, perhaps the end result is a bit too vanilla? Developers would be able to write global styles for figure and figcaption but nothing more.

The shortcode that I wrote requires a bit more effort from article authors. However, this extra effort pays off in that the figure element becomes a styled entity (see attached) that is integrated 2016-05-20_10-30-44 2016-05-20_10-31-57 into the page layout. For exa mple, background color, floats, wrapping text and width alternatives.

TXT
[figur width="one-third" position="fig-right" image="enclosingFolder/photoName.jpg" cite="Acme Photo Service"]Photo caption would go here. This is a custom GRAV `shortcode` to render an HTML fig/figcaption element. Options include figure **width** (one-third or one-half) and figure **position** (fig-left or fig-right). Image path must be specified with parent folder and file name.[/figur]

My shortcode is far from perfect. Ultimately, I prefer your more straightforward approach as being more friendly to non-technical authors --- I just wish that there were more styling options. Maybe this is not possible?

10 years ago

I have an idea for the ImgCaptions-plugin which takes any classes originally set on the Markdown image and applies it to the <figure>-element instead. This would allow ultimately all custom styling and easy layout fixes, as any effect meant for the image would now work for the figure, given that the CSS is correctly defined.

Needs a bit more testing though, but that is my thought for the next version. The ImgSrcset-plugin is a bit more plug-and-play, as it just generates the needed srcset-attribute based on a set of rules and assumptions about files. For the next version of that, however, I will implement the <picture>-element as an alternative.

Suggested topics

Topic Participants Replies Views Activity
Archive · by Deleted User, 9 years ago
0 1368 9 years ago
Archive · by Muut Archive, 9 years ago
2 940 9 years ago
Archive · by Muut Archive, 9 years ago
2 4069 9 years ago
Archive · by Muut Archive, 9 years ago
1 2960 9 years ago
Archive · by Muut Archive, 9 years ago
3 1125 9 years ago