Skip to content

DirectoryIterator without is_dir() check in Pageimages::getAllVariations() and PageimageVariations::find() #2329

Description

@WebWorkingMan

Hey @ryancramerdesign

and here's an second error, that Claude reported during his investigation, wich I mentioned in my previous issue report.

Lets me please know if it's OK for you, sending issues this way.

Cheers Jens.


Short description of the issue

Pageimages::getAllVariations() constructs a \DirectoryIterator over the page's files
directory without checking that the directory exists. If it does not — which is the normal
state for a saved page that has never had a file uploaded, and a transient state for a page
being cloned — the request dies with an unhandled
UnexpectedValueException: DirectoryIterator::__construct(...): Failed to open directory.

wire/core/Pageimages/Pageimages.php:143 (3.0.271 dev; before the core file reorganization:
wire/core/Pageimages.php:143):

foreach(new \DirectoryIterator($this->path()) as $file) {

Pagefiles::path()PagefilesManager::___path() only computes the path, it never creates
the directory, so this is reachable through the documented public API.

The same unguarded pattern exists a second time, in
wire/core/Pageimages/PageimageVariations.php:335 inside find():

$dir = new \DirectoryIterator($this->pagefiles->path);

is_dir does not appear anywhere in either file.

Expected behavior

A missing files directory means "no variations". getAllVariations() returns an empty array
(respectively an empty result / count of 0 from PageimageVariations::find()); the request
continues.

Actual behavior

Unhandled UnexpectedValueException, request aborted.

Steps to reproduce the issue

  1. Create a page with an images field and save it. Do not upload anything, so
    site/assets/files/<id>/ is never created.
  2. Call $page->images->getAllVariations(); (API, template, or Tracy console).
  3. UnexpectedValueException: DirectoryIterator::__construct(/path/to/site/assets/files/<id>/): Failed to open directory.

For the second location, the same happens whenever Pageimage::getVariations() runs while the
files directory of the page holding the image is absent — e.g. during a clone, before the files
have been copied across.

Optional: Screenshots/Links that demonstrate the issue

From a production site, 18 logged crashes between 09/2025 and 08/2026 in two shapes.

1. Duplicating an item inside a (Matrix) Repeater — 11 occurrences:

/cms/page/edit/?id=<id>&field=<repeater>&repeater_clone=<id>&repeater_clone_to=<id>
→ UnexpectedValueException: DirectoryIterator::__construct(
     /path/to/site/assets/files/<id>/): Failed to open directory
   (wire/core/Pageimages.php:143)

This one is particularly unpleasant in practice: the clone itself completes in the database,
only the rendering of the response is aborted. The editor sees nothing happen and clicks again
— in the logs, four times in a row on one day and four times again a week later, each attempt
leaving another orphaned repeater page behind.

2. Saving a newly created page — 7 occurrences, via
PageimageDebugInfo::getBasicDebugInfo()Pageimage::getVariations()
PageimageVariations::find()DirectoryIterator (i.e. the second code location above).
This variant only occurs in a debug context ($config->debug / TracyDebugger). In one case it
aborted a save mid-flight, leaving the page with incomplete multi-language status flags — which
then looked to everyone like an editor had forgotten a language tab.

Optional: Suggestion for a possible fix

An is_dir() guard in both places, e.g.:

$path = $this->path();
if(is_dir($path)) {
    foreach(new \DirectoryIterator($path) as $file) {
        // ...
    }
}

Setup/Environment

  • ProcessWire version: observed on 3.0.255; both code locations are unchanged on the current
    dev branch (3.0.271, checked 2026-08-31)
  • (Optional) PHP version: 8.x
  • (Optional) Any 3rd party modules that are installed and could be related to the issue:
    TracyDebugger (triggers the PageimageDebugInfo variant; the getAllVariations() variant
    needs no modules), FieldtypeRepeaterMatrix

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions