Skip to content

Commit

Permalink
Refactor and improvements to $sanitizer->validateFile() method. Also …
Browse files Browse the repository at this point in the history
…added new options for 'getArray' and 'dryrun'.
  • Loading branch information
ryancramerdesign committed Sep 24, 2020
1 parent 17d7828 commit 5ea913f
Showing 1 changed file with 100 additions and 30 deletions.
130 changes: 100 additions & 30 deletions wire/core/Sanitizer.php
Expand Up @@ -4706,59 +4706,129 @@ public function getTextTools() {
*/

/**
* Validate a file using FileValidator modules
* Validate and sanitize a file using FileValidator modules
*
* Note that this is intended for validating file data, not file names.
* This is intended for validating file data, not file names. Depending on the FileValidator
* modules that are used, they may sanitize the file in order ot make it valid.
*
* IMPORTANT: This method returns NULL if it can't find a validator for the file. This does
* not mean the file is invalid, just that it didn't have the tools to validate it.
* IMPORTANT: This method returns NULL if it can’t find a validator for the file. This does
* not mean the file is invalid, just that it didn't have the tools to validate it. If the
* getArray option is specified then it would return a blank array rather than null.
*
* **getArray option** (3.0.167+):
* When specifying true for the `getArray` option this method will return an associative array
* of validation results indexed by module name. The values for each module name will be either
* true (file validates as-is), 1 (file valid after it was sanitized), or false (file not valid
* and cannot be sanitized). A blank array is returned if no modules could perform the validation.
*
* **dryrun option** (3.0.167+):
* When specifying true for the `dryrun` option please note that no validation is performed and
* instead the method returns true or false as to whether or not the file can be validated. It
* only looks at the file extension, so the file need not exist. Meaning it’s also okay to specify
* filename like “test.jpg” without path, when using this option. If using the dryrun option with
* the `getArray` option then it will return an array of module names that would perform the
* validation for the given file type (or blank array if none).
*
* #pw-group-files
*
* @param string $filename Full path and filename to validate
* @param array $options When available, provide array with any one or all of the following:
* - `page` (Page): Page object associated with $filename.
* - `field` (Field): Field object associated with $filename.
* - `pagefile` (Pagefile): Pagefile object associated with $filename.
* @return bool|null Returns TRUE if valid, FALSE if not, or NULL if no validator available for given file type.
* - `page` (Page): Page object associated with $filename. (default=null)
* - `field` (Field): Field object associated with $filename. (default=null)
* - `pagefile` (Pagefile): Pagefile object associated with $filename. (default=null)
* - `getArray` (bool): Return array of results rather than a boolean? (default=false) Added 3.0.167
* - `dryrun` (bool|int): Specify true to only return if the file can be validated with this method,
* without actually performing any validation. (default=false). Added 3.0.167
* @return bool|array|null Returns one of the following, depending on use of dryrun and getArray options:
* - Boolean true if valid, false if not.
* - NULL if no validator available for given file type or file does not exist.
* - If dryrun option is used, returns boolean (or array of strings if getArray option is true).
* - If getArray option is used, returns associative array of results or blank array if no validators.
*
*/
public function validateFile($filename, array $options = array()) {

$defaults = array(
'page' => null,
'field' => null,
'pagefile' => null,
'dryrun' => false,
'getArray' => false,
);

$options = array_merge($defaults, $options);
$filename = (string) $filename;
$modules = $this->wire()->modules;
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$validators = $this->wire('modules')->findByPrefix('FileValidator', false);
$validatorNames = array();
$validatorResults = array();
$isValid = null;
foreach($validators as $validatorName) {
$info = $this->wire('modules')->getModuleInfoVerbose($validatorName);
$getArray = $options['getArray'];
$dryrun = $options['dryrun'] || !empty($options['dryRun']);

if(!strlen($extension) || (!$dryrun && !is_file($filename))) {
return $getArray ? array() : null;
}

// find modules that can validate extension
foreach($modules->findByPrefix('FileValidator', false) as $validatorName) {
$info = $modules->getModuleInfoVerbose($validatorName);
if(empty($info) || empty($info['validates'])) continue;

foreach($info['validates'] as $ext) {
if($ext[0] == '/') {
if(!preg_match($ext, $extension)) continue;
} else if($ext !== $extension) {
continue;
}
$validator = $this->wire('modules')->get($validatorName);
if(!$validator) continue;
if(!empty($options['page'])) $validator->setPage($options['page']);
if(!empty($options['field'])) $validator->setField($options['field']);
if(!empty($options['pagefile'])) $validator->setPagefile($options['pagefile']);
$isValid = $validator->isValid($filename);
if(!$isValid) {
// move errors to Sanitizer class so they can be retrieved
foreach($validator->errors('clear array') as $error) {
$this->wire('log')->error($error);
$this->error($error);
}
break;
if($ext === $extension) {
$validatorNames[$validatorName] = $validatorName;
} else if($ext[0] === '/' && preg_match($ext, $extension)) {
$validatorNames[$validatorName] = $validatorName;
} else {
// module does not validate extension
}
}

// when doing a dryrun we only need to know if at least one module can run
if($dryrun && !$getArray && count($validatorNames)) break;
}
return $isValid;

// if doing a dryrun then just return whether or not validation is possible
if($dryrun) return ($getArray ? $validatorNames : count($validatorNames) > 0);

// if no validators can validate extension then early exit
if(empty($validatorNames)) return ($getArray ? array() : null);

// execute modules that can validate extension and get results
foreach($validatorNames as $validatorName) {
/** @var FileValidatorModule $validator */
$validator = $modules->get($validatorName);
if(!$validator) continue; // not likely

if(!empty($options['page'])) $validator->setPage($options['page']);
if(!empty($options['field'])) $validator->setField($options['field']);
if(!empty($options['pagefile'])) $validator->setPagefile($options['pagefile']);

$valid = $validator->isValid($filename);
$validatorResults[$validatorName] = $valid; // false, true or 1

if($valid) {
// true (bool): file is valid as-is
// 1 (int): file is valid as a result of sanitization
// in either case, continue on to the next applicable FileValidator module
continue;
}

// at this point we’ve determined file is not valid
$isValid = false;

// move errors to Sanitizer class so they can be retrieved
foreach($validator->errors('clear array') as $error) {
$this->wire()->log->error($error);
$this->error($error);
}

// unless we are returning an array of results, we can stop now for invalid files
if(!$getArray) break;
}

return $getArray ? $validatorResults : $isValid;
}

/**********************************************************************************************************************
Expand Down

0 comments on commit 5ea913f

Please sign in to comment.