Recommended Posts

Metadata Exif

Version 0.9.0  (currently a WIP) for PW 2.8.+ / 3.0+

This is a new attempt to add easy EXIF reading support to ProcessWire. It should be an easy and less error prone API task. Thats why the module internally uses lowercase key names for all EXIF keys and all of your requested key names. But you are not forced to use lowercase key names, you can use any mixed chars for it as you like. You will get back an array or object with keynames / properties named the same (casesensitive) way as you have passed them in with your request.


The module adds one hook to the Pageimage: getExif($options = null)

Without any $options passed to the method, it returns all EXIF data in a RAW multidim array.

Passing a simple list with requested key names as array to it returns an array with only the values of those requested keynames.

If you want change the output from array to object, you can use the a boolean "toObject" set to true. Additionally to this and / or any other option, you need to put your requested keynames list to the option "keys".

$rawArray = $image->getExif();

$options = array('Whitebalance', 'Flash', 'ISOSpeedRatings', 'FNumber', 'UserComment');
$array = $image->getExif($options);

$options = array('toObject' => true, 'keys' => array('whitebalance', 'flash', 'isospeedratings', 'fnumber', 'usercomment'));
$object = $image->getExif($options);


Possible options are


              keys = array,
                       a list with case insensitive keynames

              thumbnail = bool,
                       you can set thumbnail explicitly as own option,
                       or you can add thumbnail as keyname to keys

              toObject = bool,
                       if is false or missing, an array is returned

              unformatted = bool,
                       if explicitly set to true, you will get back unformatted (raw) values

              returnRaw = bool,
                       when no $options are set, or no $options['keys'] are set,
                       or explicitly this param is set to true,
                       the complete RAW EXIF data is returned


A working example

    $options = array(
        'keys' => array('ISOSpeedRatings', 'FNumber', 'Flash')
    echo "<table><tr>";
    foreach($options['keys'] as $key) echo "<th>{$key}</th>";
    echo "</tr>";
    foreach($page->images as $image) {
        $exif = $image->getExif($options);
        echo "<tr>";
        foreach($exif as $value) echo "<td>$value</td>";
        echo "</tr>";
    echo "</table>";


This will output something like:

ISOSpeedRatings    FNumber    Flash
320    f/2.2    Flash did not fire, compulsory flash mode
100    f/4.0    Flash did not fire, compulsory flash mode


With the boolean option "unformatted" passed as true, the output would look like:

ISOSpeedRatings    FNumber    Flash
320    11/5    16
100    40/10    16


For some keys, that only store something like integer values, what are not very meaningful to most of us, the module contain xyzFormatted methods. This way, it is easy to enhance and maintain the module in the future. :)


Help wanted

It would be nice if some of you test the module and report back if the intended API suites your needs. And you are very welcome to post suggestions for which keys you want to have a ...Formatted method.


Thank you!


  • Like 11

Share this post

Link to post
Share on other sites

Hi, Thanks for the great module.

I'm trying to use this for a single image, however, i'm unable to get this to work for just the first image in multi-image field.

Share this post

Link to post
Share on other sites

how does your code look like for getting the (first) image from your field and getting the exif information?

Share this post

Link to post
Share on other sites

and what is the outcome?

can you try



Share this post

Link to post
Share on other sites
5 minutes ago, horst said:


returns NULL

Share this post

Link to post
Share on other sites
$image = $page->galleryimages->first();

please verify that:

  • your field has at least one image
  • that the image is of type jpeg
  • that it (the original version) contains EXIF-data
  • that it contain an entry FNumber


Share this post

Link to post
Share on other sites
1 minute ago, horst said:

please verify that:

  • your field has at least one image
  • that the image is of type jpeg
  • that it (the original version) contains EXIF-data
  • that it contain an entry FNumber


Yes I can confirm all of this. The multi-image field has two images. Using your example array from the first post of this thread I can see Exif information being returned for both. Both files are jpg type files.

See array information returned from your working example code below.


Screenshot at Nov 27 17-12-27.png

Share this post

Link to post
Share on other sites
17 minutes ago, horst said:


returns - int(2)


$image = $page->galleryimages->first();

Returns - nothing.



returns - string(36) "/photography/site/assets/files/1031/" 



returns - array(2) { ["apc_0046.jpg"]=> NULL ["img_0335.jpg"]=> NULL }

Share this post

Link to post
Share on other sites

your url does not contain an image name, it only points to the folder.

and the last var_dump should get the EXIf from the single image, not from the image collection!!

Share this post

Link to post
Share on other sites


Notice: Undefined offset: 0 in /Applications/XAMPP/xamppfiles/htdocs/photography/site/modules/MetadataExif/MetadataExif.module on line 44
array(5) { ["FILE"]=> array(6) { ["FileName"]=> string(12) "apc_0046.jpg" ["FileDateTime"]=> int(1511813872) ["FileSize"]=> int(1160717) ["FileType"]=> int(2) ["MimeType"]=> string(10) "image/jpeg" ["SectionsFound"]=> string(30) "ANY_TAG, IFD0, THUMBNAIL, EXIF" } ["COMPUTED"]=> array(9) { ["html"]=> string(26) "width="2797" height="3496"" ["Height"]=> int(3496) ["Width"]=> int(2797) ["IsColor"]=> int(1) ["ByteOrderMotorola"]=> int(0) ["ApertureFNumber"]=> string(5) "f/2.2" ["Copyright"]=> string(20) "photography" ["Thumbnail.FileType"]=> int(2) ["Thumbnail.MimeType"]=> string(10) "image/jpeg" } ["IFD0"]=> array(10) { ["ImageDescription"]=> string(36) "time flies" ["Make"]=> string(5) "Apple" ["Model"]=> string(14) "iPhone 6s Plus" ["XResolution"]=> string(5) "240/1" ["YResolution"]=> string(5) "240/1" ["ResolutionUnit"]=> int(2) ["Software"]=> string(49) "Adobe Photoshop Lightroom Classic 7.0 (Macintosh)" ["DateTime"]=> string(19) "2017:11:27 16:17:21" ["Copyright"]=> string(20) "" ["Exif_IFD_Pointer"]=> int(302) } ["THUMBNAIL"]=> array(6) { ["Compression"]=> int(6) ["XResolution"]=> string(4) "72/1" ["YResolution"]=> string(4) "72/1" ["ResolutionUnit"]=> int(2) ["JPEGInterchangeFormat"]=> int(896) ["JPEGInterchangeFormatLength"]=> int(17303) } ["EXIF"]=> array(26) { ["ExposureTime"]=> string(6) "1/2400" ["FNumber"]=> string(5) "22/10" ["ExposureProgram"]=> int(2) ["ISOSpeedRatings"]=> int(25) ["ExifVersion"]=> string(4) "0230" ["DateTimeOriginal"]=> string(19) "2017:07:10 11:47:22" ["DateTimeDigitized"]=> string(19) "2017:07:10 11:47:22" ["ShutterSpeedValue"]=> string(16) "11228819/1000000" ["ApertureValue"]=> string(15) "2275007/1000000" ["BrightnessValue"]=> string(8) "8488/781" ["ExposureBiasValue"]=> string(3) "0/1" ["MeteringMode"]=> int(5) ["Flash"]=> int(16) ["FocalLength"]=> string(5) "83/20" ["SubjectLocation"]=> array(4) { [0]=> int(2015) [1]=> int(1511) [2]=> int(2217) [3]=> int(1330) } ["SubSecTimeOriginal"]=> string(3) "208" ["SubSecTimeDigitized"]=> string(3) "208" ["ColorSpace"]=> int(1) ["SensingMethod"]=> int(2) ["SceneType"]=> string(1) "" ["ExposureMode"]=> int(0) ["WhiteBalance"]=> int(0) ["FocalLengthIn35mmFilm"]=> int(29) ["UndefinedTag:0xA432"]=> array(4) { [0]=> string(5) "83/20" [1]=> string(5) "83/20" [2]=> string(4) "11/5" [3]=> string(4) "11/5" } ["UndefinedTag:0xA433"]=> string(5) "Apple" ["UndefinedTag:0xA434"]=> string(39) "iPhone 6s Plus back camera 4.15mm f/2.2" } }







string(48) "/photography/site/assets/files/1031/apc_0046.jpg"


Sorry about that, i corrected my mistake. see above.

Share this post

Link to post
Share on other sites

Sorry, now I see that you are using a single value string as $options. This is not possible.

Please refer to the examples above: you need to pass an array to the function, also if you want to retrieve a single value:

$image->getExif( array('FNumber') );


Share this post

Link to post
Share on other sites

echo $page->galleryimages->first()->getExif( array('FNumber') );

Returned :


Notice: Array to string conversion in /Applications/XAMPP/xamppfiles/htdocs/photography/site/templates/clientgallery.php on line 9


Share this post

Link to post
Share on other sites

you need to echo out your desired array key, or object property, depending on what you choosed.

You choosed an array for the return, therefore you need to output the key "FNumber" of the array:

$result = $page->galleryimages->first()->getExif( array('FNumber') );

// output the value for one key of the returned array
echo $result['FNumber'];

// if you have choosen the result as object, you would use
echo $result->FNumber;

This all you can read in the initial post of this thread.

  • Thanks 1

Share this post

Link to post
Share on other sites

Hi Horst,

With your instructions and help, I got it to work.

Thanks again for working with me in understanding proper methods for rendering the values I needed.


For Future reference for anyone else(including myself if I forget )... There are two methods for rendering data from Metadata EXIF module.

Array Method:

// output the value for one key of the returned array
$firstGalleryImage = $page->galleryimages->first()->getExif( array('FNumber') );
echo $firstGalleryImage['FNumber'];

Object Method:

// if you have choosen the result as object, you would use
$options = array('toObject' => true, 'keys' => array('FNumber'));
$object = $page->galleryimages->first()->getExif($options);
echo $object->FNumber; 


  • Like 1

Share this post

Link to post
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 usualCommission
      Hi everyone. I just completed first draft work on a module that I wrote. It creates a page for a user with a specified role when a user admin page is saved. I'm sure it could use some more work and would like any feedback on implementation, code quality, and any better utilization of the PW API.
      The problem I aimed to solve is that we are using the fantastic MarkupBlog module and wanted a way to easily specify an author for a post when the person who created the page is not the author we want shown on the post (example: one blog author ghostwriting for another blog author). This module allowed me to create a blog-author role and on user admin save create a page under the "Authors" page in the tree. After that each blog post gets a page select field that lists the Authors page children for selection.
      More detail:
      If a user is deleted from the CMS, their page remains in the page tree. This is to prevent problems if there are other pages that depend on that page. A hidden field is added to the User admin page that holds the id for the page created. When creating a page for a user, it checks if there is a page already created with that user's name- if it is, that page is used and the hidden field is updated with that page's ID. If a user's page is deleted from the tree, a new page is created on user admin save and the hidden ID field is updated with the new ID. Module installs and uninstalls properly including adding and removing the hidden ID field from the User system template. The module settings allow for choosing more than one role to create pages. Again since this is my first rodeo with ProcessWire module development I would love feedback and critique. The code is available here:

    • By MilenKo
      Hello guys. I feel a bit ashamed of this fact but I just needed to add an image with some text wrapped around it using CKEditor and I simply can't achieve that. I have an image field attached to the template (called images) and it is showing promptly the uploaded images. I am able to manipulate the image and I set its alignment to left side. Then I add some text and I can see in CKEditor that the text is properly surrounding the image. However, once I save the page and view it, it shows the text under the image or only a line is staying asside the image and the rest is under the image again. As far as the field used for the text (body) is set to TextMultiLang and I did not added any text formatters and chose the content type to be HTML/Markup.
      In my CSS I've added some of the PW styles for the alignement as per some posts here but that did not help either. Something I am missing again?
    • By dadish
      NOTE: This thread originally started in the Pub section of the forum. Since we moved it into the Plugin/Modules section I edited this post to meet the guidelines but also left the original content so that the replies can make sense.  
      ProcessGraphQL seamlessly integrates to your ProcessWire web app and allows you to serve the GraphQL api of your existing content. You don't need to apply changes to your content or it's structure. Just choose what you want to serve via GraphQL and your API is ready.
      Warning: The module supports PHP version >= 5.5 and ProcessWire version >= 3.
      Zip Download Github Repo ScreenCast PW modules Page Please refer to the Readme to learn more about how to use the module.
      Original post starts here...
      Hi Everyone! I became very interested in this GraphQL thing lately and decided to learn a bit about it. And what is the better way of learning a new thing than making a ProcessWire module out of it! 
      For those who are wondering what GraphQL is, in short, it is an alternative to REST. I couldn't find the thread but I remember that Ryan was not very happy with the REST and did not see much value in it. He offered his own AJAX API instead, but it doesn't seem to be supported much by him, and was never published to official modules directory. While ProcessWire's API is already amazing and allows you to quickly serve your content in any format with less than ten lines of code, I think it might be convenient to install a module and have JSON access to all of your content instantly. Especially this could be useful for developers that use ProcessWire as a framework instead of CMS.
      GraphQL is much more flexible than REST. In fact you can build queries in GraphQL with the same patterns you do with ProcessWire API.
      Ok, Ok. Enough talk. Here is what the module does after just installing it into skyscrapers profile.

      It supports filtering via ProcessWire selectors and complex fields like FieldtypeImage or FieldtypePage. See more demo here
      The module is ready to be used, but there are lots of things could be added to it. Like supporting any type of fields via third party modules, authentication, permissions on field level, optimization and so on. I would love to continue to develop it further if I would only know that there is an interest in it. It would be great to hear some feedback from you. I did not open a thread in modules section of the forum because I wanted to be sure there is interest  in it first.
      You can install and learn about it more from it's repository. It should work with PHP >=5.5 and ProcessWire 3.x.x. The support for 2.x.x version is not planned yet.
      Please open an issue if you find bugs or you want some features added in issue tracker. Or you can share your experience with the module here in this thread.
    • By kongondo
      Pages Export
      This module is for specifically exporting ProcessWire 2.x sites for later importing into ProcessWire 3.x.
      Github: Project Page
      Modules Directory: Pending
      Credits: Ryan Cramer
      As I make my modules ProcessWire 3.x-compatible only, I've had the need to re-create/mirror their ProcessWire 2.x test sites in respective ProcessWire 3.x sites. These ProcessWire 3.x sites (one for each module) were already in place and I didn't feel like re-doing them by exporting/importing site profiles. I also like working with JSON rather than other export formats. So, I decided to write a custom pages export/import script for moving the ProcessWire 2.x sites to their respective ProcessWire 3.x counterpart sites. I'd just finished the export side of things when I came across a post in the forums that reminded me that ProcessWire 3.x already boasts a pages export/import feature, although some of it is still in development. Great! I like its API (PagesExportImport.php) and GUI (ProcessPagesExportImport.module) so no need to re-invent the wheel. 
      I still had the small problem of making sure my JSON export would be compatible with the JSON input that the ProcessWire 3.x import expects. No need to re-invent the wheel, again! I ditched my custom script and instead ported the export functionalities of ProcessWire 3.x Pages Export/Import for use in ProcessWire 2.2 - 2.7, specifically to help migrate older sites to ProcessWire 3.x.
      The module and class have been tested and work in ProcessWire 2.2, 2.3, 2.4, 2,5, 2.6 and 2.7.  The module is currently tagged as 'in development' until Pages Import feature of ProcessWire 3.x is released as stable. Nonetheless, I have not encountered any issues so far in either the export or the ProcessWire 3.x import. I think Ryan is waiting until he can support more complex field types before tagging the ProcessWire 3.x Pages Export/Import as production-ready.
      This is not a ProcessWire 3.x module and will never be. It has no such need . Just in case you forget and try to install it in a ProcessWire 3.x site, the module will throw a WireException(). I will also not be porting the ProcessWire 3.x import functionality for use in ProcessWire 2.x. That will defeat the purpose here; to move sites to ProcessWire 3.x and not the other way round.
      Supported Fields
      All non-complex fields such as integer, text, textarea, etc Page fields Repeaters File and Image fields I think these cover most needs. Note: not yet tested with Multilingual fields.
      To ensure exports will be compatible with ProcessWire 3.x Pages Import, where necessary, the module borrows (and amends as needed) methods from ProcessWire 3.x for use in ProcessWire 2.x. For instance, ProcessWire 3.x Pages Export/Import uses the new(-ish) $file functions found in WireFileTools. Rather than copy and include such files, the module only borrowed and amended needed methods. These are listed below.
      From /wire/core/Functions.php: wireInstanceOf(), wireClassName() and wireClassParents() From /wire/core/Fieldtype.php: getImportValueOptions() and getDatabaseSchema() From /wire/core/WireFileTools.php: zip(), chmod() and mkdir() From /wire/core/WireHttp.php: sendFile From /wire/modules/Fieldtype/FieldtypeFile.module: exportValue() and exportDescription() From /wire/modules/Fieldtype/FieldtypeImage.module: exportValue() From /wire/modules/Fieldtype/FieldtypePage.module: exportValue() and exportValuePage() From /wire/modules/Fieldtype/FieldtypeRepeater.module: exportValue() From /wire/core/Fieldtype/WireTempDir.php: create(), createName() and getTempDir() All the export methods from the /wire/core/PagesExportImport.php class ProcessPagesExport.module
      All the export methods from /wire/modules/process/ProcessPagesExportImport/ProcessPagesExportImport.module Newer methods such as $this->wire() will gracefully degrade to the older wire() function, ensuring smooth and uniform operation in ProcessWire 2.2  - 2.7.
      This module and class is for supersusers only and has only 1 aim; to export ProcessWire 2.x sites ready for importing into ProcessWire 3.x sites. You can either install (like any other module) and use the process module (ProcessPagesExport.module) or skip the install and just include and use the class (PagesExport.php) to export your sites.
      Both the module (Export GUI) and API require that you are logged in as a supersuser before you can use them. The PagesExport class has a gateway method and option not found in the original class (PagesExportImport). The method export() allows access to the three export methods in the original class, i.e. pagesToArray(), exportJSON() and exportZip(). See example usage below.
      GUI/Process Module
      On install, the module will create a new admin page Export Pages. Please note that unlike the original code, this page is created directly under /admin/ and not /admin/pages/. Click on Export Pages to start.
      Nothing much has changed from the original ProcessPagesExportImport.
      In older ProcessWire versions where InputfieldSelector was not available, the module will instead present you with a text input to specify a valid (for that version of ProcessWire!) selector for finding pages. The other two methods for adding pages (add pages manually or add by parent) are still available.
      Custom JS ensures older installs without showIf functionality still get the inputfield dependency treatment.
      export($items, $options) 
      PageArray $items: The PageArray to export.
      Array $options: In addition to the options in the original class that you can pass to pagesToArray(), there are two more options here. mode to specify the export type (array or json or zip) and fieldNamesExclude, to specify fields to exclude from the export. Note that the original class option fieldNames still works. It's for specifying the fields to include in the export.
      // API USAGE // get and include PagesExport class /* @note: you'll need to include the path differently if you are using the class directly without installing the Process module */ $path = $config->paths->ProcessPagesExport . 'PagesExport.php'; require_once($path); // create new instance of the class $siteExport = new PagesExport(); // find items to export /* a. export whole site! (minus admin and children) careful! in some cases, better to export in batches */ //$items = $pages->get('/')->find('has_parent!=2'); // export a batch of pages $items = $pages->find('template=basic-page|computer'); /* you could also use these methods directly #$data = $siteExport->pagesToArray($items); #$data = $siteExport->exportJSON($items); #$data = $siteExport->exportZIP($items); */ $options = array( // @kongondo addition: export to screen as 'array' or 'json' OR export to zip // zip will be saved in /site/assets/backups/PagesExport/ 'mode' => 'array',// array or json or ZIP // export only these field names, when specified 'fieldNames' => array('images', 'files', 'multi_pages'), // @kongondo addition: exclude fields from export. Here we exclude 'body' field 'fieldNamesExclude' => array('body'), ); // get the export $data = $siteExport->export($items, $options); if(is_array($data)) { echo '<pre> EXPORTED SITE USING pagesToArray '; print_r($data); echo '</pre>'; } // JSON export else echo $data; Screenshots
      See also the links to Ryan's blog posts above.
      ProcessWire 2.2

      ProcessWire 2.4

      ProcessWire 2.5

      ProcessWire 2.7

      Video Demo
      (Sorry, long and boring) 
      Demo shows various exports from ProcessWire 2.x and their importing into ProcessWire 3.x. Remember the old Skyscrapers site profile? See how a whole Skyscrapers site gets exported from a ProcessWire 2.7.3 site and imported into a ProcessWire 3.x starting from here.
    • By flydev
      Originaly developped by Jeff Starr, Blackhole is a security plugin which trap bad bots, crawlers and spiders in a virtual black hole.
      Once the bots (or any virtual user!) visit the black hole page, they are blocked and denied access for your entire site.
      This helps to keep nonsense spammers, scrapers, scanners, and other malicious hacking tools away from your site, so you can save precious server resources and bandwith for your good visitors.
      How It Works
      You add a rule to your robots.txt that instructs bots to stay away. Good bots will obey the rule, but bad bots will ignore it and follow the link... right into the black hole trap. Once trapped, bad bots are blocked and denied access to your entire site.

      The main benefits of Blackhole include:
       Bots have one chance to obey your site’s robots.txt rules. Failure to comply results in immediate banishment.
      Disable Blackhole for logged in users Optionally redirect all logged-in users Send alert email message Customize email message Choose a custom warning message for bad bots Show a WHOIS Lookup informations Choose a custom blocked message for bad bots Choose a custom HTTP Status Code for blocked bots Choose which bots are whitelisted or not  
      Install the module Create a new page and assign to this page the template "blackhole" Create a new template file "blackhole.php" and call the module $modules->get('Blackhole')->blackhole(); Add the rule to your robot.txt Call the module from your home.php template $modules->get('Blackhole')->blackhole();  Bye bye bad bots!