Handling resource URLs like "theme://images/..." in PHP

I’m writing a plugin to facilitate creating SVG symbols for use in Twig. For instance, this could be an desired HTML output:

<!DOCTYPE html>
<html>
  <head>
    <title>SVG Symbols</title>
    <style>
     body { margin: 20px; }
     .svg-large { width: 500px; fill: yellow;}
     .svg-small { width: 200px; fill: red; }
     .svg-tiny { width: 100px; fill: #888; }
    </style>
  </head>
  <body xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Define SVG symbols in the document. -->
    <svg style="display:none;">
      <symbol id="scary-smiley">
	<circle cx="10" cy="10" r="9.5" stroke-width="1"
		stroke="black" />
	<circle cx="6" cy="7" r="1.5" fill="black"/>
	<circle cx="14" cy="7" r="1.5" fill="black"/>
	<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Teeth_by_David_Shankbone.jpg/320px-Teeth_by_David_Shankbone.jpg"
	       width="10" height="5.2" x="5" y="11"/>
      </symbol>
    </svg>
    <h1>SVG Example</h1>
    <!--
	 Then you can use the symbols as often as you want.
	 Note that the CSS above sets the width only, the height
	 scales accordingly. Firefox and Chrome (at least) require
	 the viewbox to be set on the referencing SVG element for
	 this to work as expected.
    -->    
    <svg class="svg-large" viewBox="0 0 20 20">
      <use xlink:href="#scary-smiley"/>
    </svg>
    <svg class="svg-small" viewBox="0 0 20 20">
      <use xlink:href="#scary-smiley"/>
    </svg>
    <svg class="svg-tiny" viewBox="0 0 20 20">
      <use xlink:href="#scary-smiley"/>
    </svg>
  </body>
</html>

(Note the image element inside the SVG!)

My plugin tries to mimic Grav’s asset manager, so I can do:

{# Add an SVG file as an asset. #}
{% do add_svg_symbol('theme://images/scary-smiley.svg', 'scary-smiley') %}

{# Insert it all as symbols wrapped in <svg style="display: none"> as above. #}
{{ svg_symbols() }}

{# Use them in the document. #}
{{ use_svg_symbol('scary-smiley', 'svg-large') }}

Most of it is done. What’s missing is properly handling the URL pointing to the SVG file. I would like to use URLs like theme://image/my-file.svg

The plugin then needs to a) resolve that to the file’s path (so it can process the SVG) and b) create an URL like http://user/themes/<my theme>/images/ from that (so it can replace xlink:href URLs inside image tags in the SVG.

I’m not all that good with PHP (and PHP always seems to do what I expect the least). I was wondering whether I should try extending the Assets class?

Otherwise, are there any library functions to facilitate this? The Assets class has a lot of code for dealing with this and I’d rather not just copy and paste it …

I know about HTTP URL…But what is use of SVG file in handling URL?

I see, my use of metasyntactic variables was probably misleading. I changed my post to include a fully working example. I hope that makes it more clear.

I hope somebody can give me a hint. Most of the logic is already written. I’d hate to abandon this just because I can’t figure out how access theme files in a reliable and maintainable way.

So, I tried this: In my plugin I create an instance of \Grav\Common\Assets in order to access some of its functions; so far I’ve tried my luck with Assets::addTo. I’m trying to call it with a property of my own class as an argument. To no avail.

An instance of the following class is exposed to Twig as a variable named svg. I’m calling the test method with {% do svg.test() %}.

class SvgAssets {
    public $svg = array();
    protected $assets_handler = null;
    protected $config = null;
    
    public function test () {
        $this->add("theme://images/example.svg");
        dump($this->svg); // This just dumps an empty array.
        return 'test';
    }

    public function add($asset, $priority = null, $pipeline = true,
                                 $group = null, $loading = null) {
        // Copy & paste of `addCss` from \Grav\Common\Assets,
        // modified to pass $this->svg as first argument.
        return $this->assets_handler->addTo($this->svg, $asset, $priority,
                                            $pipeline, $loading, $group);
    }

    // [...]
    // Other functions
    // [..]
    
    public function __construct ($config) {
        $this->assets_handler = new \Grav\Common\Assets ();
        $this->config = $config;
    }
}

Like I wrote, PHP and me aren’t best buddies, so I assume I’m actually confused about how this works. I was expecting Assets::addTo to modify the array stored in SvgAssets::svg.

(All this is just me searching in the dark. What I actually need is a way to resolve URLs like "theme://images/example.svg" to both the file path and the corresponding http://....)

I’m embarrassed to be talking to myself (again). But after some more source code diving and head scratching, it seems that I found the solution. Maybe somebody else needs this in the future, so I’m posting it here.

It turns out that much of what happens in the Asset class is not relevant for just getting the URL or file path. Instead, Grav comes with a “locator” instance that provides the method findResource. So:

        $resource = "theme://images/example.svg";

        $locator = $this->grav['locator'];

        // $locator->findResource returns either an absolute
        // file path (if second argument is true) or a path
        // relative to the Grav folder. It also checks
        // whether the file or directory passed as the first
        // argument actually exists. If not, it returns
        // false.
        $file_path = $locator->findResource($resource, true);

        $relative_url = $locator->findResource($resource, false);
        $base_url = $this->grav['base_url'];
        $resource_url = $base_url . '/' . $relative_url;

        dump($file_path);
        dump($resource_url);
1 Like

Thank you for sharing this, I was useful to me today.

1 Like