1

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.

3 Answers 3

2
+25

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/<fileType:\w+>/<fileName:\S+>',
    '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.

5
  • 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
    Jun 27, 2018 at 19:29
  • 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 Jun 27, 2018 at 21:00
  • 1
    Thanks for your help I've sorted it out now thanks to the inspiration you gave me. I've written a blog post about the solution to help anyone with stumbles on a similar problem in the future.
    – H2ONOCK
    Jun 30, 2018 at 13:47
  • 1
    Awesome that you wrote a blog post covering your solution! Jul 2, 2018 at 3:10
  • Cheers, I've just copied and pasted it as the answer too.
    – H2ONOCK
    Jul 2, 2018 at 8:01
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/

1
  • Thanks, I'm aware of that but for this use-case I'm not really too concerned.
    – H2ONOCK
    Jun 29, 2018 at 10:49
1

My solution

  1. To keep the ‘index.html’ file of the magazines inside templates as per usual

  2. Store the other assets inside a private directory outside of the public folder

  3. 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

<?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<issue>.*)/(?P<folder>.*)/(?P<file>.*)' => ['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)

<?php
namespace Craft;

class MagazineAssetsController extends BaseController
{
    public function init()
    {
        if (!craft()->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…

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

My config/general.php file:

<?php
    return [
        //etc.
        'environmentVariables' => [
            '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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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