<p>My solution</p>

<ol>
<li><p>To keep the ‘index.html’ file of the magazines inside templates as per usual</p></li>
<li><p>Store the other assets inside a private directory outside of the public folder</p></li>
<li><p>Write a plugin that would look for requests to these files and serve them from the private place instead.</p></li>
</ol>

<p>I began by wireframing the plugin; I created a new folder inside craft/plugins named ‘magazineassets’. Inside this folder I added another folder named ‘controllers’ and a file named ‘MagazineAssetsPlugin.php’.</p>

<pre><code>cd plugins
mkdir magazineassets magazineassets/controllers magazineassets/MagazineAssetsPlugin.php
</code></pre>

<p>MagazineAssets.php</p>

<pre><code>&lt;?php
namespace Craft;

class MagazineAssetsPlugin extends BasePlugin
{
    public function getName()
    {
            return 'Magazine Assets';
    }

    public function getVersion()
    {
        return '1.0';
    }

    public function registerSiteRoutes()
    {
        return [
            'members/magazine/(?P&lt;issue&gt;.*)/(?P&lt;folder&gt;.*)/(?P&lt;file&gt;.*)' =&gt; ['action' =&gt; 'magazineAssets/getMagAsset'],
        ];
    }
}
</code></pre>

<p>Once this was written, I went into the website’s admin panel and enabled the plugin.</p>

<p>The method of interest here is the ‘registerSiteRoutes’ one. What this does is looks for any requests to members/magazine/[issue]/[folder]/[file] and ‘sends’ them to a controller that we haven’t written yet…
MagazineAssetsController.php</p>

<p>(stored inside the, earlier created, ‘controllers’ folder)</p>

<pre><code>&lt;?php
namespace Craft;

class MagazineAssetsController extends BaseController
{
    public function init()
    {
        if (!craft()-&gt;plugins-&gt;getPlugin('MagazineAssets'))
            throw new Exception('Couldn\'t find the Magazine Assets Plugin!');
    }

    public function actionGetMagAsset(array $variables = [])
    {
        $path = craft()-&gt;config-&gt;get('environmentVariables')['internalAssetsPath'].'magazines'.DIRECTORY_SEPARATOR.$variables['issue'].DIRECTORY_SEPARATOR.$variables['folder'].DIRECTORY_SEPARATOR.$variables['file'];

        if (IOHelper::fileExists($path)) {
                $content = IOHelper::getFileContents($path);
                craft()-&gt;request-&gt;sendFile($path, $content, ['forceDownload' =&gt; false]);
        } else {
            die('file not found at: '.$path);
        }
    }
}
</code></pre>

<p>The method of interest here is the ‘actionGetMagAsset’ function which matches the ‘action’ that we are now routing to via the ‘registerSiteRoutes()’ plugin method.</p>

<p>There’s no doubt in my mind that there’s probably some fancy way of writing that path using some baked in CraftCMS methods but I a) wasn’t certain what they are and b) wanted to be very rigid and certain that I definitely had the right path.</p>

<p>The path begins using a config setting (at config/general.php) that I already had thanks to another plugin that I was already using followed by ‘/magazines/[issue]/[folder]/[file]’ (sound familiar?). The config is below but I guess I could have just as easily written it as if I didn’t have the config or didn’t want it…</p>

<pre><code>&lt;?php
    $path = CRAFT_BASE_PATH."../files/".'magazines'.DIRECTORY_SEPARATOR.$variables['issue'].DIRECTORY_SEPARATOR.$variables['folder'].DIRECTORY_SEPARATOR.$variables['file'];
?&gt;
</code></pre>

<p>My config/general.php file:</p>

<pre><code>&lt;?php
    return [
        //etc.
        'environmentVariables' =&gt; [
            'internalAssetsPath' =&gt; CRAFT_BASE_PATH . "../files/",
        ]
    ];
?&gt;
</code></pre>

<p>I then used Craft’s built in InputOutput helper class to display the file if it exists and tested via the front-end.</p>

<p>Boom! All working now. Assets are loaded from the files folder, my file structure is like this (simplified obviously):</p>

<pre><code>craft
    plugins
        magazineassets
            controllers
                MagazineAssetsController.php
            MagazineAssetsPlugin.php
    templates
            members
                magazine
                    july-2018
                        mag
                            index.html &lt;--- the magazine that needs the assets
</code></pre>

<p>Yeah, okay, but the magazine is still public!</p>

<p>Right you are, it took two seconds to fix that. Into craft/templates/members/magazine/july-2018/mag/index.html I went and simply added…</p>

<pre><code>{% requireLogin %}
</code></pre>

<p>…to the top of the file.</p>

<p>I hope this post helps anyone who faces a similar conundrum.</p>

CC BY-SA 4.0
2018-07-02T08:01:24.247Z
2018-07-02T16:11:59.797Z
2018-07-02T16:11:59.797Z
H2ONOCK
<p>Please note that the way you have this structured, all of your static resources such as images, JavaScripts, CSS, etc. are going to be run through PHP and Craft before being served up.</p>

<p>This is going to be quite inefficient; static resources such as images, JavaScript, CSS, etc. (anything that <em>doesn't</em> have Twig code inside of it) shouldn't be served up from the <code>templates/</code> directory.</p>

<p>Rather, they should be in your web root. I would reconsider the way you have your static resources located in <code>templates/</code></p>

CC BY-SA 4.0
2018-06-29T10:49:25.230Z
Thanks, I'm aware of that but for this use-case I'm not really too concerned.
H2ONOCK
CC BY-SA 4.0
2018-06-28T04:08:48.410Z
2018-06-28T04:08:48.410Z
andrew.welch
<p>You'll need to create custom route to access those files. This is just an example of a custom route in my current module, so you need to find a pattern that matches the one you receive from the tool</p>

<pre><code>$event-&gt;rules[] = [
    'pattern' =&gt; '/userfiles/&lt;fileType:\w+&gt;/&lt;fileName:\S+&gt;',
    'route'   =&gt; 'thaff/profile/get-file'
];
</code></pre>

<p>And your Controller action could be something like this</p>

<pre><code>public function actionGetFile($view, $fileName)
{
    // create an alias to your folderpath $view should be thumb/mobile-ext or mobile depending on the route
    $localPath = Craft::getAlias('@yourFolderAlias') . DIRECTORY_SEPARATOR . $view;

    // you can include several checks if the image should be visible for the current user or not...

    $resp = Craft::$app-&gt;getResponse();
    $resp-&gt;headers-&gt;set('Content-Type' /*, insert your mime-type here */);
    $resp-&gt;format = Response::FORMAT_RAW;
    // check if the file exists
    if ($localPath &amp;&amp; file_exists($localPath . $fileName)) {
        // open the file
        $handle = fopen($localPath . $fileName, 'rb');
        // send the result to your response object
        $resp-&gt;stream = $handle;
        // send the response and close the file
        $resp-&gt;send();
        @fclose($handle);
    }
}
</code></pre>

<p>Note: this is just a little code snipped of my current project and I removed many conditional checks so it's easier to read. </p>

CC BY-SA 4.0
2018-06-27T19:29:38.160Z
Thanks for your help, so I need to write a new plugin that contains code based on the above do I? Or can this be placed somewhere else? I can add the custom route via admin can't I? Where would I put the controller?
H2ONOCK
CC BY-SA 4.0
2018-06-27T21:00:31.960Z
Those questions don't belong here - please read the documentation. Such things are asked daily and can't be answered in a comment. You'll need a custom plugin or module for it
Robin Schambach
CC BY-SA 4.0
2018-06-30T13:47:36.300Z
Thanks for your help I've sorted it out now thanks to the inspiration you gave me. I've written a [blog post](https://www.james-nock.co.uk/blog/tutorials/2018/06/30/craftcms-flipbuilder-plugin/) about the solution to help anyone with stumbles on a similar problem in the future.
H2ONOCK
CC BY-SA 4.0
2018-07-02T03:10:33.603Z
Awesome that you wrote a blog post covering your solution!
andrew.welch
CC BY-SA 4.0
2018-07-02T08:01:55.700Z
Cheers, I've just copied and pasted it as the answer too.
H2ONOCK
CC BY-SA 4.0
2018-06-27T09:33:08.500Z
2018-06-27T09:33:08.500Z
Robin Schambach
<p>I have some 'Flip PDF' generated folders that I need only logged in users to be able to access. If I put them in templates I get the problem that the .jpg images <code>500</code>. Is there a way of allowing .jpg images to be served from within the templates folder? This isn't as simple as using an 'internal assets' plugin and adding them manually as all of the flip pdf JS is created automatically and I don't want to have to edit it every time a new one needs to be uploaded.</p>

<p>I've searched high and low for the answer to this over the last few weeks as well as asking on slack but I've had no luck.</p>

<p>Edit: maybe there's a .htaccess file I can add in the template sub directory that will prevent images being loaded in the normal 'craft' way?</p>

<p>The folder structure looks like this:</p>

<pre><code>craft
templates
    emag
        july-2018
            files
                mobile
                    *.jpg
                mobile-ext
                    *.jpg
                thumb
                    *.jpg
                    etc.
            mag
                javascript
                    *.js (loads fine)
                index.html &lt;--- the 'magazine' that needs the jpg's
</code></pre>

<p>The contents inside 'july-2018' is what the software gives me. All works fine, except for the .jpg images.</p>

<templating><image>
Flip PDF embed inside templates
# Flip PDF embed inside templates

(Asked by: username_0 on 2018-06-24)

I have some 'Flip PDF' generated folders that I need only logged in users to be able to access. If I put them in templates I get the problem that the .jpg images 500. Is there a way of allowing .jpg images to be served from within the templates folder? This isn't as simple as using an 'internal assets' plugin and adding them manually as all of the flip pdf JS is created automatically and I don't want to have to edit it every time a new one needs to be uploaded.
I've searched high and low for the answer to this over the last few weeks as well as asking on slack but I've had no luck.
Edit: maybe there's a .htaccess file I can add in the template sub directory that will prevent images being loaded in the normal 'craft' way?
The folder structure looks like this:
craft
templates
    emag
        july-2018
            files
                mobile
                    *.jpg
                mobile-ext
                    *.jpg
                thumb
                    *.jpg
                    etc.
            mag
                javascript
                    *.js (loads fine)
                index.html <--- the 'magazine' that needs the jpg's

The contents inside 'july-2018' is what the software gives me. All works fine, except for the .jpg images.

**Tags:** templating, image

---

## 3 answers

### Answer by username_0 on 2018-07-02. Score: 1 | Accepted answer

My solution

To keep the ‘index.html’ file of the magazines inside templates as per usual
Store the other assets inside a private directory outside of the public folder
Write a plugin that would look for requests to these files and serve them from the private place instead.

I began by wireframing the plugin; I created a new folder inside craft/plugins named ‘magazineassets’. Inside this folder I added another folder named ‘controllers’ and a file named ‘MagazineAssetsPlugin.php’.
cd plugins
mkdir magazineassets magazineassets/controllers magazineassets/MagazineAssetsPlugin.php

MagazineAssets.php
.*)/(?P.*)/(?P.*)' => ['action' => 'magazineAssets/getMagAsset'],
        ];
    }
}

Once this was written, I went into the website’s admin panel and enabled the plugin.
The method of interest here is the ‘registerSiteRoutes’ one. What this does is looks for any requests to members/magazine/[issue]/[folder]/[file] and ‘sends’ them to a controller that we haven’t written yet…
MagazineAssetsController.php
(stored inside the, earlier created, ‘controllers’ folder)
plugins->getPlugin('MagazineAssets'))
            throw new Exception('Couldn\'t find the Magazine Assets Plugin!');
    }

    public function actionGetMagAsset(array $variables = [])
    {
        $path = craft()->config->get('environmentVariables')['internalAssetsPath'].'magazines'.DIRECTORY_SEPARATOR.$variables['issue'].DIRECTORY_SEPARATOR.$variables['folder'].DIRECTORY_SEPARATOR.$variables['file'];

        if (IOHelper::fileExists($path)) {
                $content = IOHelper::getFileContents($path);
                craft()->request->sendFile($path, $content, ['forceDownload' => false]);
        } else {
            die('file not found at: '.$path);
        }
    }
}

The method of interest here is the ‘actionGetMagAsset’ function which matches the ‘action’ that we are now routing to via the ‘registerSiteRoutes()’ plugin method.
There’s no doubt in my mind that there’s probably some fancy way of writing that path using some baked in CraftCMS methods but I a) wasn’t certain what they are and b) wanted to be very rigid and certain that I definitely had the right path.
The path begins using a config setting (at config/general.php) that I already had thanks to another plugin that I was already using followed by ‘/magazines/[issue]/[folder]/[file]’ (sound familiar?). The config is below but I guess I could have just as easily written it as if I didn’t have the config or didn’t want it…


My config/general.php file:
 [
            'internalAssetsPath' => CRAFT_BASE_PATH . "../files/",
        ]
    ];
?>

I then used Craft’s built in InputOutput helper class to display the file if it exists and tested via the front-end.
Boom! All working now. Assets are loaded from the files folder, my file structure is like this (simplified obviously):
craft
    plugins
        magazineassets
            controllers
                MagazineAssetsController.php
            MagazineAssetsPlugin.php
    templates
            members
                magazine
                    july-2018
                        mag
                            index.html <--- the magazine that needs the assets

Yeah, okay, but the magazine is still public!
Right you are, it took two seconds to fix that. Into craft/templates/members/magazine/july-2018/mag/index.html I went and simply added…
{% requireLogin %}

…to the top of the file.
I hope this post helps anyone who faces a similar conundrum.


---

### Answer by username_1 on 2018-06-28. Score: 2

Please note that the way you have this structured, all of your static resources such as images, JavaScripts, CSS, etc. are going to be run through PHP and Craft before being served up.

This is going to be quite inefficient; static resources such as images, JavaScript, CSS, etc. (anything that *doesn't* have Twig code inside of it) shouldn't be served up from the `templates/` directory.

Rather, they should be in your web root. I would reconsider the way you have your static resources located in `templates/`

> **Comments:**
> **username_0**: Thanks, I'm aware of that but for this use-case I'm not really too concerned.  

---

### Answer by username_2 on 2018-06-27. Score: 2

You'll need to create custom route to access those files. This is just an example of a custom route in my current module, so you need to find a pattern that matches the one you receive from the tool
$event->rules[] = [
    'pattern' => '/userfiles//',
    'route'   => 'thaff/profile/get-file'
];

And your Controller action could be something like this
public function actionGetFile($view, $fileName)
{
    // create an alias to your folderpath $view should be thumb/mobile-ext or mobile depending on the route
    $localPath = Craft::getAlias('@yourFolderAlias') . DIRECTORY_SEPARATOR . $view;

    // you can include several checks if the image should be visible for the current user or not...

    $resp = Craft::$app->getResponse();
    $resp->headers->set('Content-Type' /*, insert your mime-type here */);
    $resp->format = Response::FORMAT_RAW;
    // check if the file exists
    if ($localPath && file_exists($localPath . $fileName)) {
        // open the file
        $handle = fopen($localPath . $fileName, 'rb');
        // send the result to your response object
        $resp->stream = $handle;
        // send the response and close the file
        $resp->send();
        @fclose($handle);
    }
}

Note: this is just a little code snipped of my current project and I removed many conditional checks so it's easier to read.

> **Comments:**
> **username_0**: Thanks for your help, so I need to write a new plugin that contains code based on the above do I? Or can this be placed somewhere else? I can add the custom route via admin can't I? Where would I put the controller?  
> **username_2**: Those questions don't belong here - please read the documentation. Such things are asked daily and can't be answered in a comment. You'll need a custom plugin or module for it  
> **username_0**: Thanks for your help I've sorted it out now thanks to the inspiration you gave me. I've written a [blog post](https://www.james-nock.co.uk/blog/tutorials/2018/06/30/craftcms-flipbuilder-plugin/) about the solution to help anyone with stumbles on a similar problem in the future.  
> **username_1**: Awesome that you wrote a blog post covering your solution!  
> **username_0**: Cheers, I've just copied and pasted it as the answer too.  

---

