Jump to content

Protect original Images in site/assets/files/


Recommended Posts

Hi, on a site I want to disable access to original images and only allow to access thumbnails and watermarked image variations.

A good solution for protecting original images can be found a bit down in this thread:


Old content of this initial post:


To create the watermarked images I use the PiM, what creates filenames with prefix "pim_". For individual thumbnails I use the Thumbnails-Module and define the prefix like "thumb_".

A simple way would be to use a htaccess file in top of the assets folder with something like this for example:

The only downside is that I cannot access the original images from the admin backend. While it isn't an option to use a proxy-script from the front-end, it could be an option for the admin.

Any ideas how I could access the originals when in admin backend?

# deny access to all jpegs and pngs
<FilesMatch "^.*\.(jpg|png)$">
    Deny from all

# allow access to pim_ - variations (my watermarked images)
<FilesMatch "^pim_.*\.(jpg|png)$">
    Allow from all

# allow access to thumb_ - prefixed files (my individual thumbnails)
<FilesMatch "^thumb_.*\.(jpg|png)$">
    Allow from all

# allow access to 100px thumbnails as used by default in PW-Admin when images-field is configured to display thumbnails
<FilesMatch "^.*\.0x100\.(jpg|png)$">
    Allow from all
<FilesMatch "^.*\.100x0\.(jpg|png)$">
    Allow from all


  • Like 3
Link to comment
Share on other sites

Hi Horst, 

maybe you could set up a rule which takes care of the referrer, something like this?

RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?yourdomain.com [NC]

Only if the referrer equals your domain, allow access to the originals. Problem could be that your server does not use http referrers...

Link to comment
Share on other sites

 Only if the referrer equals your domain, allow access to the originals. Problem could be that your server does not use http referrers...

Maybe something like domain/processwire, but a referrer header could be set to anything one want, I guess.

Better could be to use the REMOTE_ADDR. Does someone know how a variable for apache could be set by script? If this could be done everytime a superuser log in I could set this IP to a apache / environment variable for which access is allowed.

Link to comment
Share on other sites

Private file handling is your solution. You would abstract all files from public access by doing something like... 




Then in your private file handler you can check permissions and show the users the correct image according to their access level. I'm not sure if idea is native to PW.

  • Like 1
Link to comment
Share on other sites

@sshaw: what you are describing is what I have called proxy-script in my opening post. I don't want that for frontend output, because it is not needed and I want avoid overhead. All images that should have public access can be accessed directly. Others (the originals) are blocked to the public. Thats fine.

The only downside is that an admin in backend also cannot access the originals.

What I'm looking for is how I can define this in htaccess file:

all images that are now blocked for direct access should be not served but handled by a proxy script.

Link to comment
Share on other sites

To do it your way, maybe use RedirectMatch in place of your first FilesMatch
RedirectMatch \.(png|gif|jpg|jpeg)$ http://example.com/image_handler.php
I think mod rewrite will be your ideal solution, several examples below that will get you close. Personally I'd rewrite all image requests to my proxy script, then store my allowed prefixes in an array and simply loop through the array. It's not the best solution, but it's simple and allows for some scalability. 
in .htaccess 
Redirect all images to proxy script
RewriteEngine On
RewriteBase /
RewriteRule ^(.*)\.(jpg|png|jpeg|gif)$ watermark.php?image=$1.$2 [NC,L]
in your proxy script watermark.php
$prefixes[] = "thumb_";
$prefixes[] = "100x0";

foreach ($prefixes[] as $prefix) {
    if((substr(0,strlen($prefix)) == $prefix) xor ($user->isSuperuser())) {
        //return the image
    } else {
        //return a different image (maybe one that says access to this file is denied)
Some rewrite examples that may help:
Redirect requests that don't originate from the host www.example.com (stop hotlinking) 
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !www.example.com [NC]
RewriteRule \.(gif|jpg|png)$ /images/go-away.png [R,NC]
Check for a login cookie and redirect if not logged in
RewriteEngine On
RewriteBase /
RewriteCond %{HTTP_COOKIE} !CookieName= [NC]
RewriteRule .* http://www.example.com/members/login.php [L]
Link to comment
Share on other sites

coming back to this: I have made good progress!
I do not use the htaccess file in site/assets/files/ anymore but have edited the htaccess file in pw root folder. Somewhere at top of the mod_rewrite directives I have added my lines that should redirect requests to original images to a proxy-script and let others pass through:

htaccess with Pim1 and PW < 2.5.11



  # -----------------------------------------------------------------------------------------------
  # Optional: Set a rewrite base if rewrites aren't working properly on your server.
  # And if your site directory starts with a "~" you will most likely have to use this.
  # -----------------------------------------------------------------------------------------------

  RewriteBase /
  # RewriteBase /pw/
  # RewriteBase /~user/

    # -----------------------------------------------------------------------------------------------
    # CUSTOMSETTING : redirect original images to proxy-script - /pwimg.php?fn=...
    # -----------------------------------------------------------------------------------------------

    RewriteCond %{REQUEST_FILENAME} -f
    RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/(.*?)/
    RewriteCond %{REQUEST_FILENAME} \.(jpg|jpeg|gif|png)$ [NC]
    RewriteCond %{REQUEST_FILENAME} !/pim_
   #RewriteCond %{REQUEST_FILENAME} !/(thumb_|thumb2_)
    RewriteCond %{REQUEST_FILENAME} !.*/.*?\.([0-9]+)x([0-9]+)\.(jpg|png|jpeg|gif)$ [NC]

    RewriteRule ^(.*)$ pwimg.php?fn=$1 [L,QSA]

  # -----------------------------------------------------------------------------------------------
  # Access Restrictions: Keep web users out of dirs that begin with a period
  # -----------------------------------------------------------------------------------------------




 .htaccess with PW 2.5.11+ / PW 3+

        # -----------------------------------------------------------------------------------------------
        # CUSTOMSETTING : redirect original images to proxy-script - /pwimg.php?fn=...
        # -----------------------------------------------------------------------------------------------

        RewriteCond %{REQUEST_FILENAME} -f
        RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/(.*?)/

        RewriteCond %{REQUEST_FILENAME} \.(jpg|jpeg|gif|png)$ [NC]
        RewriteCond %{REQUEST_FILENAME} !-piacrop
        RewriteCond %{REQUEST_FILENAME} !-piacontain
        RewriteCond %{REQUEST_FILENAME} !-pim2-full
        RewriteCond %{REQUEST_FILENAME} !-blogthumb
        RewriteCond %{REQUEST_FILENAME} !.*/.*?\.([0-9]+)x([0-9]+)\.(jpg|png|jpeg|gif)$ [NC]
        RewriteRule ^(.*)$ pwimg.php?fn=$1 [L]

Now from all existing images the originals get redirected to the proxy-script and the others will delivered directly by apache. Requests to none existing imagefiles get answered by a 404.
So as everything seems to work fine, the RewriteConditions could be optimized a bit.
---- pwimg.php ----


// check filename
$imgFilename = isset($_GET['fn']) ? preg_replace('/[^a-zA-Z0-9_\-\/\.@]/', '', $_GET['fn']) : false;
$imgFilename = is_file(dirname(__FILE__) . "/$imgFilename") && is_readable(dirname(__FILE__) . "/$imgFilename") ? dirname(__FILE__) . "/$imgFilename" : false;
if (false == $imgFilename) {
    header('HTTP/1.1 404 Not Found');

// check imagetype
$imgType = getImageType($imgFilename);
if (false == $imgType) {
    header('HTTP/1.1 403 Forbidden');
    header('Content-type: image/jpeg');

// bootstrap PW
require_once(dirname(__FILE__) . '/index.php');

// check user-account
if (! wire('user')->hasRole('superuser|editor')) {
    header('HTTP/1.1 403 Forbidden');
    header('Content-type: ' . $imgType);

// collect infos
$maxAge = (60 * 60 * 2); // 2 hours
$imgTimestamp = filemtime($imgFilename);
$imgExpiration = intval(time() + $maxAge);

// create headers
$imgHeaders = array();
$imgHeaders[] = 'Content-type: ' . $imgType;
$imgHeaders[] = 'Content-Length: ' . filesize($imgFilename);
$imgHeaders[] = 'Date: ' . gmdate('D, d M Y H:i:s',time()) . ' GMT';
$imgHeaders[] = 'Last-Modified: ' . gmdate('D, d M Y H:i:s',$imgTimestamp) . ' GMT';
$imgHeaders[] = 'Expires: ' . gmdate('D, d M Y H:i:s', $imgExpiration) . ' GMT';
$imgHeaders[] = 'pragma: cache';
$imgHeaders[] = "Cache-Control: no-transform, private, s-maxage={$maxAge}, max-age={$maxAge}";

// send headers
foreach($imgHeaders as $imgHeader) header($imgHeader);

// send file
$errorCode = @readfile($imgFilename) === FALSE ? 1 : 0;

// and exit

// --- functions ---

function getImageType($fn, $returnAsInteger = false) {

    $types1 = array(1 => 'gif', 2 => 'jpg', 3 => 'png');
    $types2 = array('gif' => 1, 'jpg' => 2, 'jpeg' => 2, 'png' => 3);

    if (function_exists('exif_imagetype') && isset($types1[@exif_imagetype($fn)])) {
        $success = $types1[exif_imagetype($fn)];
    if (!isset($success) && function_exists('getimagesize')) {
        $info = @getimagesize($fn);
        if (isset($info[2]) && isset($types1[$info[2]])) {
            $success = $types1[$info[2]];
    if (!isset($success)) {
        $extension = strtolower(pathinfo($fn, PATHINFO_EXTENSION));
        if (isset($types2[$extension])) {
            $success = $types1[$types2[$extension]];

    if (!isset($success)) return false;

    return true === $returnAsInteger ? $types2[$success] : $success;
  • Like 5
Link to comment
Share on other sites

  • 3 years later...

Hi @horst,

Im looking for an exact same solution, website is showing images, but can not be accessed through going directly to the URL.

I tried your solution, but also the page where the image is displayed is getting a 404 response for this image.

I also have de pwimg.php as a template and page /pwimg/ instead of a 'normal' php file, is that maybe a problem?

I hope you can help me with this!

Link to comment
Share on other sites

Please read & follow my solution exactly. The way you described doesn't work & it isn't meant to do so.
It describes a way to only protect the original images. Therefor you need to deny all access to them via htaccess file. In a second step, you enable access for superusers (or who ever you like) via a proxy (php) script. Nothing (!) is embedded into your PW-installation, only the htaccess file.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Similar Content

    • By quickjeff
      Hi Everyone, 
      As many of you know, iPhone photos save in .heic unless the user changes the settings in their phone. 
      We have built a web app on top of processwire in which a user can save images from their phone. 
      The issue we are having is that there seems to be an error when uploading and saving. 
      The .heic files won't save and processwire throws an error. The environment is LAMP (Bitnami and all libraries have been installed to support this.) 
      Checking to see if someone has also encountered this here and how they solved it.
    • By Robin S
      Select Images
      An inputfield that allows the visual selection and sorting of images, intended for use with the FieldtypeDynamicOptions module. Together these modules can be used to create a kind of "image reference" field.

      Integration with FieldtypeDynamicOptions
      InputfieldSelectImages was developed to be used together with FieldtypeDynamicOptions (v0.1.3 or newer):
      Create a Dynamic Options field. Choose "Select Images" as the "Inputfield type". Select Images appears in the "Multiple item selection" category but you can set "Maximum number of items" to 1 if you want to use Select Images for single image selections. Define selectable options for the field via a FieldtypeDynamicOptions::getSelectableOptions hook. See some examples below. FieldtypeDynamicOptions is recommended but is not a strict requirement for installing InputfieldSelectImages in case you want to use an alternative way to store the field data.
      Selection of Pageimages
      In this example the field allows selection of Pageimages that are in the "images" field of the home page.
      The field will store URLs to the Pageimages so it works as a kind of "image reference" field. You can use the "Format as Pagefile/Pageimage object(s)" option for the Dynamic Options field to have the formatted value of the field be automatically converted from the stored Pageimage URLs to Pageimage objects.
      $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "select_images" if($field->name === 'select_images') { $options = []; // Get Pageimages within the "images" field on the home page foreach($event->wire()->pages(1)->images as $image) { // Add an option for each Pageimage // When the key is a Pageimage URL the inputfield will automatically create a thumbnail // In this example the label includes the basename and the filesize /** @var Pageimage $image */ $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } $event->return = $options; } }); Selection of image files not associated with a Page
      When not working with Pageimages you must add a "data-thumb" attribute for each selectable option which contains a URL to a thumbnail/image.
      In this example the field allows selection of image files in a "/pics/" folder which is in the site root.
      $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "select_images" if($field->name === 'select_images') { $options = []; // Get files that are in the /pics/ folder $root = $event->wire()->config->paths->root; $path = $root . 'pics/'; $files = $event->wire()->files->find($path); // Add an option for each file foreach($files as $file) { $basename = str_replace($path, '', $file); $url = str_replace($root, '/', $file); // The value must be an array with the following structure... $options[$url] = [ // The label for the image 'label' => $basename, 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => $url, ], ]; } $event->return = $options; } }); The field values don't have to be image URLs
      The values stored by the Dynamic Options field don't have to be image URLs. For example, you could use the images to represent different layout options for a page, or to represent widgets that will be inserted on the page.
      Also, you can use external URLs for the thumbnails. In the example below the options "calm" and "crazy" are represented by thumbnails from placecage.com.
      $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "calm_or_crazy" if($field->name === 'calm_or_crazy') { $options = []; // Add options that are illustrated with thumbnails from placecage.com $options['calm'] = [ // The label for the option 'label' => 'Nicolas Cage is a calm man', 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => 'https://www.placecage.com/260/260', ] ]; $options['crazy'] = [ // The label for the option 'label' => 'Nicolas Cage is a crazy man', 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => 'https://www.placecage.com/c/260/260', ] ]; $event->return = $options; } }); Field configuration
      You can define labels for the button, notices, etc, that are used within the inputfield if the defaults don't suit.

    • By Robin S
      Process Images
      A basic, proof-of-concept Textformatter module for ProcessWire. When the Textformatter is applied to a rich text field it uses Simple HTML DOM to find <img> tags in the field value and passes each img node through a hookable TextformatterProcessImages::processImg() method.
      This is a very simple module that doesn't have any configurable settings and doesn't do anything to the field value unless you hook the TextformatterProcessImages::processImg() method.
      Hook example
      When added to /site/ready.php the hook below will replace any Pageimages in a rich text field with a 250px square variation and wrap the <img> tag in a link to the original full-size image.
      For help with Simple HTML DOM refer to its documentation.
      $wire->addHookAfter('TextformatterProcessImages::processImg', function(HookEvent $event) { // The Simple HTML DOM node for the <img> tag /** @var \simple_html_dom_node $img */ $img = $event->arguments(0); // The Pageimage in the <img> src, if any (will be null for external images) /** @var Pageimage $pageimage */ $pageimage = $event->arguments(1); // The Page object in case you need it /** @var Page $page */ $page = $event->arguments(2); // The Field object in case you need it /** @var Field $field */ $field = $event->arguments(3); // Only for images that have a src corresponding to a PW Pageimage if($pageimage) { // Set the src to a 250x250 variation $img->src = $pageimage->size(250,250)->url; // Wrap the img in a lightbox link to the original $img->outertext = "<a class='lightboxclass' href='{$pageimage->url}'>{$img->outertext}</a>"; } });  
      GitHub: https://github.com/Toutouwai/TextformatterProcessImages
      Modules directory: https://processwire.com/modules/textformatter-process-images/
    • By spercy16
      These issues should be fairly easy for any intermediate to advanced ProcessWire developer to answer. I'm new to PHP and relatively new to ProcessWire and just need a bit of help. What I'm trying to do is bring in a couple of cards from my Projects page to display on my home page. I finally got the code right to bring in the cards but right now they're using my original images instead of my resized "variations". So firstly, I would like to know how to reference the variations of my images instead of using the original. Secondly, I need to grab only four of the cards from the Project page and not import in all ten. It should be just two small changes to my code to do these things (I would imagine). Here is the code I currently have for that section:
      <?php // https://processwire.com/api/arrays/ // check if the array of images has items if (count($pages->get("/projects/")->images)) : // get array of images from the field $images = $pages->get("/projects/")->images; $count = 0; // iterate over each one foreach ($images as $image) : $count++; $sectionText = $pages->get("/projects/")->get("paragraph_$count"); $img = $image; $buttonCode = $pages->get("/projects/")->get("url_$count"); ?> <span id="card<?php echo $count?>" class="card"> <img class="cardThumb" src="<?php echo $img->url; ?>" alt="<?php echo $image->description; ?> Thumbnail" /> <div class="cardBody"> <div class="cardText"> <h2><?php echo $img->description; ?></h2> <?php echo $sectionText; ?> </div> <div class="primaryBtn"> <a href="https://www.paypal.com/donate?hosted_button_id= <?php echo $buttonCode; ?> &source=url"> <button> <i class="fas fa-donate"></i> Donate </button> </a> </div> </div> </span> <?php endforeach; endif; ?> Thanks in advance for any help!
    • By skeltern
      WebP image support is great and works fine. But once created I've issues to get rid of all API generated WebP variations.
      The backend image field variations "Delete" works and I can remove all variations JPEG plus WebP. Image list is clean but all WebP API variations are still stored in file system (for instance files/12345/84.900x675.webp etc). I can only use ImageSizer with temp 'force' option to request fresh WebP variations or have to delete WebP files from folders. No other way so far. Tested with 2 sites and latest master PW 3.0.165.
      Is there somewhere a "magic button" or config/setup thing to solve my sticky WebP issue?
  • Create New...